技术解析

JS 里闭包是如何 capture 外部变量的?
0
2021-08-26 09:18:59
idczone
function foo() {
  var a = 1;
  return function() {
    console.log(a);
  }
}
var f = foo() // line 7 capture a ? why
f = undefined // line 8 release a

这一段代码, 在 js 引擎执行到 7 行时候, 会在 global execution context 上面创建一个 foo 的 execution context , 这时候 foo 被执行, 当 foo 执行完后, 返回一个闭包给变量 f, 这时候闭包函数未被执行, 为何还会 capture 外部的变量 a 呢? 闭包函数不执行, 应该不会有 VariableEnvironment 和 LaxicalEnvrironment 吧, 所以闭包是怎么 capture 外部变量的呢? 我理解的哪里有问题吗?


函数在创建的时候已经引用了外部的上下文,和这个函数的执不执行没关系。

"capture"是发生在函数定义的时候,而不是执行的时候,不然每一次执行“ capture ”的变量都不一样了,闭包的意义在那。


也就是说执行到第 6 行时, 已经有了 3 个 context:
global execution context, foo execution context, closure execution context 分别对应:
GlobalExecutionContext = {
ThisBinding: global,
VariableEnvironment: { },
LexicalEnvironment: { }
}
FooExecutionContext = {
ThisBinding: foo,
VariableEnvironment: { a: undefined },
LexicalEnvironment: { }
}
ClosureExecutionContext = {
ThisBinding: closure,
VariableEnvironment: { },
LexicalEnvironment: { a: undefined }
}
在执行完第 7 行, 虽然 FooExecutionContext 被弹出栈, js 运行时发现变量 a 还有 closure 的 lexicalEnvironment 引用, 所以不会释放变量 a, 对吗?


https://hackernoon.com/javascript-execution-context-and-lexical-environment-explained-528351703922
这篇文章有一句:
> Each time you invoked a function it will create a new Function Execution Context.
也就是说, 只有在函数被执行(invoke)时候, 才会创建 execution context.
这句是对的吗?
函数定时时候就 'capture' 外部变量, 此时函数没有被执行, 也就没有 context, 那么应该怎么解释 capture ?

执行( enter ) function 的时候的过程在这里,确实会新建 execution context
https://www.ecma-international.org/ecma-262/5.1/Let localEnv be the result of calling NewDeclarativeEnvironment passing the value of the [[Scope]] internal property of F as the argument.
这里,后续用来创建 context 的 localEnv 这个东西来自于[[Scope]]这个内部属性,然后这个属性是在创建函数的时候绑定的,参考这里
https://www.ecma-international.org/ecma-262/5.1/Scope 的描述
在执行 foo,return 前,计算 function() {
console.log(a);
}
表达式的值的时候,当前的 lexical environment 也就是包含 var a=1 的东西被记录在这个函数的[[Scope]]内部属性中了,于是后面有
f.[[Scope]] => Lexical Environment{ a=1 }
阻止了 a 被 GC

在 foo 里面的匿名函数定义时,匿名函数处于 foo 的 execution context 中,它自然可以 capture 这个外部变量。
https://www.cnblogs.com/starof/p/6400261.html

更正一下后面,精确地说,应该是
f.[[Scope]] => DeclarativeEnvironment { ..., outer => Lexical Environment{ a=1 } }
如果你的 console.log(a)旁边有 var x=42,x=42 就是在...位置的

就是这样。函数执行会创建上下文没错,但是函数创建的时候,按照 ES3 的解释,会创建好包含外部作用域,保存在函数的 [[Scope]] 中,等到调用时再复制到上下文中。ES5

擦...还没打完,按错键直接就发送了

按引用
function f(){var a ={abc:1}; var o=function(){return a};a.abc=3;return o;}
f()() //{abc: 3}
function f(){var a =1; var o=function(){a=2;return a};return o;}
f()()//2
相当于 C++的 [&](){...}

一个函数,如果其作用域内不引用任何“外面的”变量,称为“封闭的”。
但如果一个函数不是封闭的,那么其内部引用的“外面的”变量,称为它的“自由变量”。
闭包就是保存了自由变量绑定的函数实例。
为什么要保存定义时所在上下文的自由变量绑定?因为 JS 是词法作用域的。
所谓词法作用域,就是指匿名函数里面的自由变量`a`,不会因为这个匿名函数到了另外一个定义了`a`的环境中去执行,而变成了另外一个值。为了维护这种“词法上的”继承关系,匿名函数实例必须保存定义处(而不是执行处)环境的自由变量绑定。这就是 closure,闭包。
至于`var`,它具有所谓的提升特性。可以理解成是定义了在作用域内部任何位置都有效的准全局变量,这与 Scheme 的`letrec`很类似。
p.s.
① 关于词法作用域和闭包,推荐阅读王垠的文章: http://www.yinwang.org/blog-cn/2012/08/01/interpreter
② 安利 Racket 或者 Scheme 语言。JS 就是受到 Scheme 的启发而设计出的语言。JS 很多看似奇怪的特性,实际上都来源于 Scheme 这门函数式语言。

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服