话不多说先来看两段代码

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的执行环境的销毁而销毁。
用图表示

clipboard.png
值得注意的是,虽然执行环境和函数scope属性中都保存这作用域链,但这两个并不是一个性质的。
明确一点区别
[[Scope]]属性是函数创建时产生的,会一直存在
而执行环境在函数执行时产生,函数执行结束便会销毁


小脑fu
237 声望9 粉丝