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 外部变量的呢? 我理解的哪里有问题吗?
https://astronautweb.co/javascript-lexical-scope/
问题的关键是这个lexical scope.
比如这样一段代码:
var a = 1
console.log(a)
console.log(b)
var b = 2
console.log(c)
运行结果是:
1
undefinded
ReferenceError: c is not defined
从这里可以看出, js语言在运行之前,也就是compile阶段, 就已经将变量定义好了, 只不过赋值是在运行时, 这也就是人们常说的js变量提升的特性.
所以本帖中的问题 js闭包如何捕获外部变量 可以这么解释: 在line7执行前已经被两个scope捕获: foo 和 闭包, 也就是大家讨论的 LexicalEnvironment.
1
s0f 2019-08-30 15:21:37 +08:00 1
函数在创建的时候已经引用了外部的上下文,和这个函数的执不执行没关系。
|
2
maichael 2019-08-30 15:22:09 +08:00 1
"capture"是发生在函数定义的时候,而不是执行的时候,不然每一次执行“ capture ”的变量都不一样了,闭包的意义在那。
|
3
FaiChou OP @s0f
也就是说执行到第 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, 对吗? |
4
FaiChou OP @maichael
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 ? |
5
mcfog 2019-08-30 15:55:13 +08:00 1
执行( enter ) function 的时候的过程在这里,确实会新建 execution context
https://www.ecma-international.org/ecma-262/5.1/#sec-10.4.3 注意 > 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/#sec-13 关注里面关于 Scope 的描述 在执行 foo,return 前,计算 function() { console.log(a); } 表达式的值的时候,当前的 lexical environment 也就是包含 var a=1 的东西被记录在这个函数的[[Scope]]内部属性中了,于是后面有 f.[[Scope]] => Lexical Environment{ a=1 } 阻止了 a 被 GC |
6
maichael 2019-08-30 15:55:33 +08:00
@FaiChou #4 在 foo 里面的匿名函数定义时,匿名函数处于 foo 的 execution context 中,它自然可以 capture 这个外部变量。
https://www.cnblogs.com/starof/p/6400261.html |
7
mcfog 2019-08-30 15:58:44 +08:00
@mcfog 更正一下后面,精确地说,应该是
f.[[Scope]] => DeclarativeEnvironment { ..., outer => Lexical Environment{ a=1 } } 如果你的 console.log(a)旁边有 var x=42,x=42 就是在...位置的 |
8
s0f 2019-08-30 16:04:40 +08:00
@FaiChou 就是这样。函数执行会创建上下文没错,但是函数创建的时候,按照 ES3 的解释,会创建好包含外部作用域,保存在函数的 [[Scope]] 中,等到调用时再复制到上下文中。ES5
|
10
v2qwsdcv 2019-08-30 17:56:55 +08:00
按引用
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++的 [&](){...} |
11
rus4db 2019-09-05 23:37:33 +08:00 1
一个函数,如果其作用域内不引用任何“外面的”变量,称为“封闭的”。
但如果一个函数不是封闭的,那么其内部引用的“外面的”变量,称为它的“自由变量”。 闭包就是保存了自由变量绑定的函数实例。 为什么要保存定义时所在上下文的自由变量绑定?因为 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 这门函数式语言。 |