JavaScript中的函数是一种特殊的对象;函数对象中有些属性可以访问,但有些不可访问,这些属性仅供JavaScript引擎存取;[[scope]]
就是其中一个不可访问属性。
[[scope]]
指的就是我们所说的作用域,其中存储了执行期上下文的集合
,这个集合呈链式链接,我们把这种链式链接叫做作用域链
(scope chain)。
执行期上下文:当函数执行时(准确的说是执行前一刻),会创建一个称为执行期上下文
的内部对象(AO)。一个执行期上下文
定义了一个函数执行时的环境;函数每次执行时的执行期上下文
都是独一无二的,所以多次调用一个函数会创建多个执行期上下文
;函数每次执行时,都会把新生成的执行期上下文
填充到作用域链
的最顶端。当函数执行完毕,它所产生的执行期上下文
被销毁。
例1:
function a(){
function b (){
var num = 3;
console.log(num);
}
var num = 2;
console.log(num);
b();
}
var num = 1;
console.log(num);
a();
JavaScript执行的过程中,作用域链大致为如下:
a被定义时:
a.[[scope]] --> 0 : GO{ num : 1 , ... }
a被执行时:
a.[[scope]] --> 0 : a 的AO{ num : 2 , ... }
1 : GO{ num : 1 , ... }
//a被执行时,b才被定义
b被定义时:
b.[[scope]] --> 0 : a 的AO{ num : 2 , ... }
1 : GO{ num : 1 , ... }
b被执行时:
b.[[scope]] --> 0 : b 的AO{ num : 3 , ... }
1 : a 的AO{ num : 2 , ... }
2 : GO{ num : 1 , ... }
//当b函数执行完后,销毁的是 b的AO
//当a函数执行完后,销毁的是 a的AO
顶端
依次向下查找。当内部函数被保存到外部时,将会形成闭包。闭包会导致原有作用域链不释放,造成内存泄漏(内存被占用)。
例2:
function test(){
var temp = 100;
function a(){
temp++;
console.log(temp);
}
return a ;
}
var demo = test();
demo(); // 101
demo(); // 102
test()
的返回值是a
函数,a
函数被赋给了demo
,所以demo
函数的作用域链就是a
函数的作用域链。
a
函数的作用域链:
a.[[scope]] --> 0 : test 的AO{ temp : 100 , ... }
1 : GO{ ... }
demo
函数的作用域链:
demo.[[scope]] --> 0 : test 的AO{ temp : 100 , ... }
1 : GO{ ... }
可见,demo
函数的作用域链里有对test
函数生成的AO
的引用,即使test
函数执行完了,test
函数生成的AO
也没有被释放。
demo
函数执行时的作用域链:
//demo执行第一次
demo.[[scope]] --> 0 : demo 的AO1 { ... }
1 : test 的AO{ temp : 101 , ... }
2 : GO{ ... }
//demo执行第二次
demo.[[scope]] --> 0 : demo 的AO2 { ... }
1 : test 的AO{ temp : 102 , ... }
2 : GO{ ... }
例子3:
function test(){
var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = function (){ console.log(i); }
}
return arr;
}
var myArr = test();
for (var j = 0; j < 10; j++) {
myArr[j](); // 输出10个10
}
myArr[0]()
...
myArr[9]()
定义时同用作用域链
myArr[0-9].[[scope]] --> 0 : test 的AO{ i : ?, ... }
1 : GO{ ... }
而,没有被释放的test
函数的AO
中 i = 10
,所以输出 10个10。
例子4:
function test(){
var arr = [];
for (var i = 0; i < 10; i++) {
// 立执行匿名函数
(function (k){
arr[k] = function (){ console.log(k); }
}(i));
}
return arr;
}
var myArr = test();
for (var j = 0; j < 10; j++) {
myArr[j](); // 输出 0、1、2、3、4、5、6、7、8、9
}
立执行匿名函数也是生成执行期上下文
(AO)的,
myArr[0]()
...
myArr[9]()
被定义时的作用域链:
myArr[0].[[scope]] --> 0 : 立执行匿名函数 的AO{ k : 0, ... }
0 : test 的AO{ i : ?, ... }
1 : GO{ ... }
myArr[1].[[scope]] --> 0 : 立执行匿名函数 的AO{ k : 1, ... }
0 : test 的AO{ i : ?, ... }
1 : GO{ ... }
.
.
.
闭包会导致多个执行函数公用一个共有变量,如果不是特殊需要,应尽量防止这种情况发生。