最近一直思考一个问题:形成闭包时,是如何确定子函数对父函数的变量引用关系的?
例如:
function foo(){
    var a = 1;
    return function(){
  console.log(a)
  }
}
var bar = foo(); 
因为foo函数正常执行完成之后上下文完成出栈就会释放上下文,foo返回了一个匿名函数但是并未执行,将该匿名函数的内存地址赋值给了bar,bar还未执行那么是怎么知道bar函数中有使用a变量的呢?
 
解答:js中一切皆对象,函数对象拥有一系列可以被代码访问的属性和一系列仅供引擎访问的内部属性,在创建时该对象便有一个内部属性为[scope],该内部属性包含了函数被创建的作用域中对象的集合,这个集合称为函数的作用域链,它决定了哪些数据能被函数访问。
        当一个函数被创建后,它的作用域链会被创建此函数的作用域中的可访问的变量对象填满(也就是当前环境的作用域链)。
 
例如定义这样一个函数:
<script>
function a(x,y){
    Var b = x+y;
    return b
}
</script>
函数创建时:它的作用域链会填入一个全局对象(这里只列举了一部分)
接上面的代码,在执行时:
var d = a(1,2)
 
编译阶段:创建上下文,包括变量对象,this指向,作用域链
executionContext:{
    Variable Object :vars, functions,arguments
    Scope chain: variable object + parents scopes
    thisArg:object
}
该函数的编译阶段会创建该函数的上下文(execution context),包括变量对象,this指向,作用域链。
(编译阶段做的事情只简单描述,不清楚的可以看:https://www.cnblogs.com/pianruijie/p/11454598.html)
1.它的作用域链初始化为当前运行函数的内部属性[scope]包含的对象这里就是executionContext.scopeChain = a[scope]。
2.创建变量对象,包括函数内部的函数变量及var变量,也就是我们所说的提前声明 
    这里就是executionContext.variableObject = {arguments:{x:undefined, y:undefined},b:undeifined}
3.当前函数上下文的变量对象推入作用域链,此时executionContext.scopeChain = [a.variableObject,a[scope]]
4.确定thisArg
 
 
明白了最重要的这点:函数的作用域链是在创建阶段确定的!!!,就可以明白闭包的引用关系
function foo(){
    var a = 1;
    return function(){}
}
var bar = foo();
匿名函数 在创建时就确定了[scope]属性,包括global的变量对象和foo函数的变量对象(a),bar指向了该匿名函数的内存地址,导致该匿名函数有了引用关系,所以导致foo的上下文虽然销毁了但是变量对象一直保留在了内存中,GC阶段也无法回收,如果这样的情况过多就会造成内存泄漏
所以正确的做法是当我们使用完bar函数之后,手动执行bar=null,这样释放掉匿名函数的引用关系就可以被安全回收了
 
 
posted on 2019-09-06 18:22  101-28  阅读(211)  评论(0编辑  收藏  举报