其他分享
首页 > 其他分享> > 动画:一个底层运行函数的自白!

动画:一个底层运行函数的自白!

作者:互联网

大家好,我是一个函数,一个又累,又苦,又被广大开发者使用的反派角色。

自从世界各大语言诞生的时候,我也随之诞生。虽然经常出现在人们的眼前,但是我的出生环境和身世却很少被人所知。今天不得不借助鹿哥的平台,进行一次自白。

这么多年过去了,总是被别人当做“工具人”,却总是不能够被人理解,我真的很伤心。多亏遇到了鹿哥,给了我这一次自白的机会。

在各个语言中,虽然我的存在的形态不同,但是我还是我,一个不一样的烟火,只要被人敲一下代码,我的兄弟姐妹们就被创建出来。而且我们每个函数天生的命运已经被决定这一生要去做一件事,我们不得不听天由命。


1function fn(){
2// 要执行的任务
3......
4}

1function fn(a, b){
2  let result = null;
3  result = a + b;
4  return result;
5}

其实,我们也像人类一样,需要生存的环境,空气、食物和水,但是我们很少被人所周知。下面我就亲自介绍一下我们所生存环境——栈。

我生活的栈内存中,有属于自己的两间大房子,一间房子当做仓库,存放一些变量和变量对应的值。

另一间房子就是我的工作室,执行我天生体内就有的任务代码。我的每个兄弟姐妹都和我一样,都有独立的环境和两间大房子,只不过我们的命运不同。

动画:一个底层运行函数的自白!

动画:一个底层运行函数的自白!

每当我的上级 Boss(主线程)告诉我要开始工作了,我就开始在工作室从头到尾顺序执行体内所要处理的任务。

因为栈内存空间太小,只能存储一些变量的和值,我体内的任务代码占据空间太大,栈内存里放不下。所以当我出生的时候,我体内的代码会以一堆破字符串的形式存放在堆内存中(其他大仓库),我在栈内存中只存这个堆仓库的地址就行了,这样需要占据的栈空间就小很多了。

动画:一个底层运行函数的自白!

每当我的 Boss(主线程)告诉我,开始工作了,我就开始在我的存储变量的栈仓库和存储代码字符串的堆仓库中,取出所需要执行的值和字符串。先对取出的字符串转化为可执行代码,开始在一个全新私有栈内存环境下执行。将执行完的结果会返回给外界变量兄弟接收。

为了能让大家更好的记住我的运行机制,不得不麻烦一下鹿哥把这个函数从定义到执行完毕给梳理一下。

大家好,我是鹿哥。一个迷人帅气、代码风骚、文章通俗的硬核男人。以上是函数的自白,它讲完了,该换鹿哥 BB 几句了。

我们启动运行程序,程序代码自上而下顺序执行,当遇到下面代码时,会发生什么呢?


1function fn(a, b){
2  let result = null;
3  result = a + b;
4  return result;
5}
6var a = fn(10, 10)
7console.log(a)

首先,遇到定义的函数 fn 时,会开辟一个栈内存空间,上边函数自己说了,栈内存空间会分为两部分,一部分是存储变量和对应的值。另一部分会分配给主线程用来执行代码。

动画:一个底层运行函数的自白!

由于函数是引用类型,会开辟一个堆内存空间,函数体内的所有执行代码字符串全部存放在堆中,当函数执行的时候才能叫做代码,这个时候只是存储在堆内的一堆破字符串而已。

动画:一个底层运行函数的自白!

程序继续自上而下执行,会将 a 变量存储在栈内存中,但是它对应的值就是函数执行后的返回值。


1var a = fn(10, 10)

函数开始执行,每一次函数执行的目的就是把函数体内的代码(先从堆内存中取出那一堆破字符串,然后转化为代码)执行。而且是在一个全新私有的栈内存内执行。

函数体内的代码开始自上而下顺序执行,这个全新的私有作用域栈和之前我们说到的栈内存是一样的,也是由两部分组成。

首先将代码中的变量推入到栈中,然后根据代码的运算取出相对应的变量进行求值,最后 retrun,将计算的值返回给变量 a,此时 a 在栈内存中有了对应的值。

动画:一个底层运行函数的自白!

PS:如果函数体内有 return 则直接返回 return 的值,如果没有,则返回的是 undefined。

以上就是整个函数从定义到执行最后结束的底层过程,但是我们还没有更深入,我们上边说到了栈中执行的两部分专业名词叫做 VO 阶段和 AO 阶段,再深入就会涉及到 JS 的编译过程,又可以作为一大部分去讲解。

通过上边函数底层的运行机制,我们有很多疑问得到解惑,比如为什么函数内声明的变量,函数外部不可访问?

因为函数执行,又重新创建了一个全新私有作用域,外界是不能直接访问该私有作用域栈内存中的值的,只有通过 return 的方式把值返回出去或者通过某种方式暴露出去。

函数定义的时候,函数体不执行的原因?

定义函数时,让对应的变量指向该堆内存的地址。我们只不过是在堆内存存了一堆破字符串而已,而不是代码,无法执行。只有当调用函数的时候,从堆内存中取出字符串转化为可执行的代码,函数体才可以执行。

通过这篇函数底层的运行机制,我们之前存在的一些对函数的疑问都一目了然。虽然像这种函数的底层运行机制看起来不难,但是我们自学中很难去梳理把握全面。

一些大厂的面试题,虽然看起来表面感觉很复杂,如果通过鹿哥上述对代码的整个底层运作拆解,再复杂的题目对你来说也会游刃有余。

不信来个阿里的面试题,欢迎在留言区写出你的答案和分析的过程。


 1let a = {
 2   n:1
 3}
 4let b = a;
 5
 6a.x = a = {
 7   n:2
 8}
 9console.log(a.x)
10console.log(b)

提示:对象和函数都是引用类型,都存放在堆中的。

最后

这么硬核的文章,加上骚气的鹿哥讲解,不要放弃手中点赞的权利,相信我,你是最“胖”的!

标签:自白,函数,动画,代码,内存,鹿哥,执行,变量,底层
来源: https://blog.51cto.com/15064450/2599762