作用域
看过上一篇重温js——执行上下文,里面提到了一个概念叫做执行环境,在里面说到了 全局环境,函数环境和eval的环境。那里面所说的环境,其实就是作用域,所以这里在稍微唠叨一下,作用域有以下:
- 全局作用域(Global scope):和前文的GO对象一样
- 函数作用域(Function scope):处于每一次执行的VO对象
- 块级作用域(es6以后补充的):es6以后定义变量的关键字变成let和const, let 和 const有自己的规则,详情查看.
既然变量有作用域,函数也有作用域,那么他们是怎么连接起来形成作用域链呢?
作用域链
js代码执行,会在call stack创建对应的作用域(执行上下文),然后每一个上下文中会有一个VO来保存当前作用域的变量. 但是VO中如果会创建函数,那么函数中有一个额外的属[[scoped]]会指向栈中最顶层的VO(AO);
上面的那一段话有点不好理解,咋们来举个例子,画个图来看看就明白了。
// 这一道题目输出的a是多少 var a = 1; function A(){ console.log(a) } function B(){ var a = 123; var C = A; C(); } B();
需要理解这个例子,咋们来画画图。
上面图中的每一帧可以理解为代码在运行过程中所作的事情。
咋们先那函数C 来分析,函数C 指向的是函数A的地址,然而函数A又是在全局的作用域中创建的,所以函数A的[[scoped]] 是指向GO对象的. 如下图红色线条:
C 中的变量查找方式C ---> A ---> GO, 形如: C ---> A ---> GO这样作用域指向的形式,我们称之为作用域链。查找对象也是严格按照作用域链来查找的。
结果:
闭包
在上面的那个例子中,我们的函数A相当于保存到了函数B的内部(B内部使用的是引用地址),实际上函数A所处的作用域还是全局作用域。
- 从广义上来说,函数内部使用函数,并且里面函数用到了外面的作用的变量。上面例子中的函数B就形成了闭包。
- 从狭义上来讲:函数内部使用了外部的变量,并且函数在其他作用域调用就形成闭包。
// 这一道题目输出的a是多少 var a = 1; function A(){ console.log(a) } function B(){ var a = 123; var C = A; C(); } B();
其实闭包在我们的开发中是非常常见的。其实还有一个问题,就是说闭包带来的问题:
内存泄漏, 这个观点是不对的。内存泄漏是指内存中的变量不能够访问并且不能被垃圾回收给清理掉。上面的那个例子中变量a可以找到吧。我们找到他的方式有很多种。
这里有的人可能需要说,我们看到的闭包是下面的这种形式?
function A(){ var a = 1223; return function(){ console.log(a) } } var afunc = A(); afunc();
上面的代码,先创建函数A,然后执行函数A,然后函数A返回一个函数。只要函数执行了,call stack 中会清空哇,哪里会有内存占用不释放呢?