话不多说先来看两段代码
function a(){
var num=10;
return function(){
console.log(num++)
}
}
var b=a();
b(); //10
b(); //11
b(); //12
b=null; //手动释放内存,消除对匿名函数的引用
function a(){
var num=10;
return function(){
console.log(num++)
}
}
a()(); //10
a()(); //10
a()(); //10
下面开始讲解代码:
在讲解之前,首先请了解一下关于函数作用域方面的知识,可以参考本人之前写的一片短文https://segmentfault.com/a/11...
//当函数a定义时 会创建一个[[scope]]属性,仅供javascript引擎内部使用
//伪代码
a.[[scope]]={
GO:{ //全局对象globel object
this:window,
window:{...},
document:{...},
a:(function)
...
}
}
当函数a调用的时候,会创建一个a的执行环境,每个执行环境对应一个变量对象。首先会创一个它自己的活动对象【Activation Object】(这个对象中包含了this、参数(arguments)、局部变量(包括命名的参数)的定义,当然全局对象是没有arguments的)和一个变量对象的作用域链[[scope chain]],然后,把这个执行环境的[[scope]]按顺序复制到[[scope chain]]里,最后把这个活动对象推入到[[scope chain]]的顶部。这样[[scope chain]]就是一个有序的栈,这样保了对执行环境有权访问的所有变量和对象的有序访问。
//函数调用时候
a.ex={ //a的执行环境,包括a的活动对象和a的作用域链
AO:{
this:window,
arguments:[],
num:undefined
},
[[scope chain]]:{
AO:当前函数活动对象,
GO:{...} //全局对象,从a.[[scope]]中复制过来的
}
//进入a的执行环境时,匿名函数被定义.
匿名函数.[[scope]]={
AO:{ //函数a的活动对象
this:window,
arguments:[],
num:undefined
},
GO:{...}
}
关键点来了
上述两段代码中:
第一段代码在执行a()的时候,将a函数返回的匿名函数赋给了变量b,并且连续调用三次b
第二段代码在执行a()的时候,没有对a函数返回的匿名函数进行赋值。
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
第一段代码因为函数a的活动对象被匿名函数的[[scope]]引用,匿名函数又被a外的变量b引用,所以在全局环境下始终保持对a活动对象的引用,所以a的活动对象无法被回收。
第二段代码由于匿名函数并没有被a外的其他地方所引用,所以在函数a执行完毕后,其活动对象也跟随a的执行环境的销毁而销毁。
用图表示
值得注意的是,虽然执行环境和函数scope属性中都保存这作用域链,但这两个并不是一个性质的。
明确一点区别
[[Scope]]属性是函数创建时产生的,会一直存在
而执行环境在函数执行时产生,函数执行结束便会销毁
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。