作用域可以说是闭包的基础,在此之上再理解闭包就很简单了,因此我将他们放在一起。
1.作用域
1)[[scope]](作用域) :
JavaScript函数是一个特殊对象,对象中有些属性我们可以访问,但是有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们说的作用域,其中存储了运行期上下文的集合。
2)作用域链:
[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,这个链式链接就是作用域链。
3)运行期上下文:
当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文就定义了一个函数执行的环境。函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
4)查找变量:从作用域链的顶端依次向下查找
5)作用域链形成过程:
function a (){
function b(){
var bb = '234';
aa = 0;
}
var aa = 123;
b();
console.log(aa)
}
var glob = 100;
a();
1.a函数刚被定义时,里面只存了全局对象(Global Objecr,以下简称GO)
2.函数a执行时,产生一个执行期上下文(Activation Object ,以下简称AO),链到作用域顶端,当这个函数的链数组有一个以上内容时,按照自顶向下查找变量
3)b函数被创建时,得到了a函数作用域链的引用(即b函数拿到的a的AO和GO与a那里的是同一个)
4)b在被执行的时候,产生了自己的AO,链在自己的作用域链的顶端。当在b函数中去访问对象,也遵循自顶向下的原则。
分析完过程后,再看执行结果,最后输出aa为0,因为b函数执行aa = 0
这句时,会去 作用域链查找,第0位找不到aa这个变量,向下查找,找到第1位有变量aa,将其赋值为0,而这个作用域链第一位是a的AO,可以说是他们共用的一块区域,所以也就不难得出为什么输出0了
function a (){
function b(){
var bb = '234';
aa = 0;
}
var aa = 123;
b();
console.log(aa)
}
var glob = 100;
a();
2.闭包
1)概念:
这是《JavaScript权威指南》一书中对闭包的解释,
" 实现闭包:
回顾上面对作用域链过程的描述,我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用 Javascript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。
当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引]用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做圾回收,并且它所指向的变量绑定对象也不会被当做拉圾回收。"
如果你疲于看大量概念或者不太理解这段话,可以简单理解为:
当内部函数被保存到外部时,将会生成闭包,闭包会导致原有作用域链不释>放,造成内存泄漏
2)例子:
function a(){
function b(){
var bbb = 234;
console.log(aaa);
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
var demo = a();
这一句,b在a的环境下,被定义时就拥有a的scope(GO和AO 的内容),a在函数最后一句return了b的引用,并传给demo,demo和b有相同的引用,a在执行以后被销毁,但是demo(或者说b)已经拥有了a的scope,这就是闭包生成的过程。
3)应用
①解决for循环输出不正常问题
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]();
}
最后输出结果 f
10 10 10 10 10 10 10 10 10 10
因为myArr[j]();
这一句是在外部执行,函数在执行时才去变现函数体,所以此时 i 已经变成了10,myArr数组访问的arr里面的i都是同一个,所以是输出十个10,test()里面的for循环是在myArr[j]()
这一句的时候才执行(在test函数里只是定义没执行,系统不会管里面写什么),执行的是
arr[0] = function(){ console.log( 10+ " ") ;}
arr[1] = function(){ console.log( 10+ " ") ;}
arr[2] = function(){ console.log( 10+ " ") ;}
解决方法
function test(){
var arr = [];
for(var i = 0;i < 10;i ++){
(function (j) {
arr[j] = function(){
console.log(j+" ");//这个不会在定义 的时候执行,但是这是赋值语句,已经把i给了j
}
}(i));//每循环一次就执行了一次这个立即执行函数,输出j就不会有那个问题了
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10;j ++){
myArr[j]();
}
②使用闭包使变量私有化,不容易和全局重名
栗子:
var name="bcd";
var init=(function(){
var name ="abc";
function call(){
console.log(name);
}
return function (){call();}
}())
init(); //返回abc",不会返回“bcd”
即在一个团队中,多人写的代码会有重名的问题,如例子中使用闭包,将个人写的代码包裹在一个小范围,是一个避免污染全局变量的好办法。