说明
图解 Google V8 学习笔记
作用域和作用域链
作用域就是存放变量和函数的地方,作用域链就是将一个个作用域串起来,实现变量查找的路径。
- 全局环境有全局作用域,全局作用域中存放了全局变量和全局函数。
- 每个函数也有自己的作用域,函数作用域中存放了函数中定义的变量。
例子1:
var name = 'kaimo' var type = 'global' function foo(){ var name = 'foo' console.log(name) console.log(type) } function bar(){ var name = 'bar' var type = 'function' foo() } bar()
输出结果如下:name 的值为 foo,这个应该没什么疑问,但是 type 的值为 global,可能大家会有疑问,为什么不是 function,输出 global 的原因是什么 ?
下面先了解一下作用域的工作原理。
什么是函数作用域和全局作用域?
函数作用域:每个函数在执行时都需要查找自己的作用域,称为函数作用域,在执行阶段,执行一个函数时,当该函数需要使用某个变量或者调用了某个函数时,便会优先在该函数作用域中查找相关内容。
例子2:
var kaimo = 313 var test function kaimo_scope() { var name = 'kkk' console.log(name) console.log(type) console.log(test) var type = 'mmm' test = 1 debugger console.log(kaimo) } kaimo_scope()
我们在控制台输入上面代码,可以看到在 debugger 的时候,右侧的 Scope 项里有 Local,这个就是当前函数 kaimo_scope 的作用域。
发现 kaimo_scope 里面还有个隐藏变量 this:V8 会默认将隐藏变量 this 存放到作用域中。
而 kaimo_scope 里面用到的 test,kaimo,并没有在 Local 里,那么 V8 应该如何获取这些变量?
如果在当前函数作用域中没有查找到变量,那么 V8 会去全局作用域中去查找,这个查找的线路就称为作用域链。
全局作用域是在 V8 启动过程中就创建了,且一直保存在内存中不会被销毁的,直至 V8 退出。 而函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就随之被销毁掉了。
作用域链是怎么工作的?
我们在来分析一下例子1:
首先 V8 启动时:全局作用域里有 this,window 等全局变量。
V8 启动之后,先编译顶层代码:会将顶层定义的变量和声明的函数都添加到全局作用域中。
编译完成,V8 进入执行状态,我们可以将代码大致分开成下面两个部分
// 解析阶段-实现变量提升 var name = undefined var type = undefined function foo(){ var name = 'foo' console.log(name) console.log(type) } function bar(){ var name = 'bar' var type = 'function' foo() } // 执行阶段 name = 'kaimo' type = 'global' bar()
当 V8 执行 bar 函数的时候,也需要进行编译和执行,在编译阶段,V8 会为 bar 函数创建函数作用域:
同样,当 V8 执行 foo 函数的时候,也需要进行编译和执行,在编译阶段,V8 会为 foo 函数创建函数作用域:
作用域已经找好了,那么 foo 函数查找变量的路径到底是什么?
- 沿着 foo 函数作用域
–>
bar 函数作用域–>
全局作用域? - 沿着 foo 函数作用域
—>
全局作用域?
bar 和 foo 函数的外部代码都是全局代码,所以无论你是在 bar 函数中查找变量,还是在 foo 函数中查找变量,其查找顺序都是按照当前函数作用域 –> 全局作用域这个路径来的。
作用域链的路径就是按照词法作用域来实现的。
静态作用域:词法作用域是根据函数在代码中的位置来确定的,作用域是在声明函数时就确定好的了,所以也将词法作用域称为静态作用域,JavaScript 是基于词法作用域的。
动态作用域:并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。动态作用域是基于调用栈的,而不是基于函数定义的位置的。
我们把例子1改一下:
例子3:
var name = 'kaimo' var type = 'global' function bar(){ var name = 'bar' var type = 'function' function foo(){ var name = 'foo' console.log(name) console.log(type) } foo() } bar()
结果如下:
我们可以打个 debugger 看看,会有一个 Closure。