如何编写高质量的 JS 函数( 四 )


通过 A 图 和 B 图的比较,直接展示 this 的本质 。
5、作用域的本质是链表中的一个节点

通过 A 图 和 B 图的比较,直接秒杀 作用域 的所有用法
看 A 图,执行 A 函数时,B 函数的作用域是创建 A 函数的活动对象 AO(A)。作用域就是一个属性,一个属于 A 函数的执行环境中的属性,它的名字叫做 [scope]。
[scope] 指向的是一个函数活动对象,核心点是把这个函数对象当成一个作用域,最好理解成一个链表节点 。
PS: B 执行 B 函数时,只有 B 函数有 this 属性,这也就交叉证实了 this 只有在运行时才会存在 。
6、作用域链的本质就是链表
通过比较 A 图和 B 图的 scopeChain ,可以确定的是:
作用域链本质就是链表,执行哪个函数,链表就初始化为哪个函数的作用域,然后将该函数的 [scope] 放在表头,形成闭环链表 。作用域链是通过链表查找的,如果走了一圈还没找到,那就返回 undefined。
五、用一道面试题让你更上一层楼(走火入魔)再举一个例子,这是一道经常被问的面试题,看下面代码:
第一个程序如下:

如何编写高质量的 JS 函数

文章插图
 
第二个程序如下:

如何编写高质量的 JS 函数

文章插图
 
输出结果大家应该都知道了,结果分别是如下截图:
第一个程序,输出 10 个 10 :

如何编写高质量的 JS 函数

文章插图
 
第二个程序,输出 0 到 9 :

如何编写高质量的 JS 函数

文章插图
 
那么问题来了,其内部的原理机制是什么呢?
  • 一部分 coder 只能答到立即调用,闭包 。
  • 大多数 coder 可以答到作用域相关知识 。
  • 极少部分 coder (大佬级别) 可以从核心底层原因来分析 。
下面从核心底层原因来分析。
1、分析输出10个10代码如下:
如何编写高质量的 JS 函数

文章插图
 
只有函数在执行的时候,函数的执行环境才会生成 。依据这个规则,在完成 r = kun() 的时候,kun 函数只执行了一次,生成了对应的 AO(kun)。如下:
如何编写高质量的 JS 函数

文章插图
 
这时,在执行 kun() 之后,i 的值已经是 10 了 。请注意,kun 函数只执行了一次,也就意味着:
在 kun 函数的 AO(kun) 中的 i 属性是 10。
继续分享,kun 函数的作用域链如下:
AO(kun) --> VO(G)而且 kun 函数已经从栈顶被删除了,只留下 AO(kun)。
注意:这里的 AO(kun) 表示一个节点 。这个节点有指针和数据,其中指针指向了 VO(G) ,数据就是 kun 函数的活动对象 。
那么,当一次执行 result 中的数组的时候,会发生什么现象?
注意:result 数组中的每一个函数其作用域都已经确定了,而 JS 是静态作用域语言,其在程序声明阶段,所有的作用域都将确定 。
那么 ,result 数组中每一个函数其作用域链如下:
AO(result[i]) --> AO(kun) --> VO(G)因此 result 中的每一个函数执行时,其 i 的值都是沿着这条作用域链去查找的,而且由于 kun 函数只执行了一次,导致了 i 值是最后的结果,也就是 10。所以输出结果就是 10 个 10。
总结一下,就是 result 数组中的 10 个函数在声明后,总共拥有了 10 个链表(作用域链),都是 AO(result[i]) --> AO(kun) --> VO(G)这种形式,但是 10 个作用域链中的 AO(kun) 都是一样的 。所以导致了,输出结果是 10 个 10。
下面我们来分析输出 0 到 9 的结果 。
2、分析输出0到9代码如下:
如何编写高质量的 JS 函数

文章插图
 
首先,在声明函数 kun 的时候,就已经执行了 10 次匿名函数 。函数在执行时将生成执行环境,也就意味着,在 ECS 栈中,有 10 个 EC(kun) 执行环境,分别对应result 数组中的 10 个函数 。
下面通过伪代码来展示:
如何编写高质量的 JS 函数

文章插图
 
上面简单的用结构化的语言表示了 kun 函数在声明时的内部情况,需要注意一下两点:
第一点:每一个 EC(kun) 中的 AO(kun) 中的 i 属性值都是不一样的,比如通过上面结构化表示,可以看到:


推荐阅读