【问题标题】:Execution context and closures执行上下文和闭包
【发布时间】:2015-01-23 22:41:20
【问题描述】:

我知道如何实现闭包,但我无法理解为什么会发生闭包。例如:

function foo(){
    var a = 4;
    return function innerFoo(){
        alert(a);
    };
};
var bar = foo();
bar();                   // alerts 4

当我看到上面的代码时,我知道会发生什么,但是当我尝试从逻辑上遵循它时,它没有任何意义。我认为我对执行上下文及其作用域链的理解是错误的。

这就是我期望程序运行的方式:

function foo(){
    var a = 4;
    return function innerFoo(){
        alert(a);
    };
};

var bar = foo(); // function foo() is executed, creating a foo execution context(EC)
                 // fooEC = {scopeChain: [fooEV.VO, globalEC.VO],
                 //                  VO: {innerFoo: pointer to innerFoo(),
                 //                              a: 4}};

                 // So, the pointer to innerFoo() is returned to bar
                 // fooEC is exited
bar(); // bar points to code in innerFoo(), creates a innerFoo execution context
       // innerFooEC = {scopeChain: [innerFoo.VO, globalEC.VO],
       //                       VO: { }};

       // interpreter searches innerFooEC.VO for variable "a", none found, looks
       // down the scope chain at the global variable object,
       // globalEC = {scopeChain: [globalEC.VO],
       //                     VO: {foo: pointer to foo(),
       //                          bar: pointer to innerFoo()}};  
       // a is undefined

我哪里做错了?

【问题讨论】:

  • 我认为这里的关键是理解词法范围和闭包。
  • innerFooECscopeChain 将是[innerFooEC.VO].concat(fooEC.scopeChain);[innerFooEC.VO, fooEC.VO, globalEC.VO]
  • bar 指向innerFoo 函数对象(不仅仅是它的代码),并且该函数对象有一个指向其父环境的指针:fooEC。当函数被调用时,它将用于作用域链——它不是跟随innerFoo.VO的全局对象

标签: javascript closures


【解决方案1】:

错误在

bar points to code in innerFoo()

闭包不仅仅是一个指向代码的指针,它是一个指向代码的指针加上一个环境;那就是定义a的环境。

当您执行闭包时,作用域链是定义闭包时激活的作用域链(即在foo 内部),而不是进行调用的作用域链。

请注意,闭包捕获变量,而不是。这可以通过以下方式观察到:

function getset(x) {
    var a = x;
    function getter() { return a; }
    function setter(x) { a = x; }
    return [getter, setter];
}

var gs1 = getset(12);
var gs2 = getset(34);

console.log(gs1[0]()); // --> 12
console.log(gs2[0]()); // --> 34
gs1[1](1122);
gs2[1](3344);
console.log(gs1[0]()); // --> 1122
console.log(gs2[0]()); // --> 3344

即每对 getter/setter 都有自己的变量在两个闭包之间共享,但与另一对分开。

根据我的经验,这是在循环中创建闭包时最常见的错误的根源:

for (var i=0; i<10; i++) {
    var node = document.createElement("div");
    node.textContent = "click me (" + i + ")";
    node.onclick = function() { alert(i); };
    document.body.appendChild(node);
}

在这段代码中,所有 10 个元素在单击时都会显示相同的数字 (10),因为所有闭包都共享循环中使用的相同 i 变量,而 10 是变量将具有的最终值。

【讨论】:

  • 谢谢,我想我现在明白了。因此,如果我理解正确,当在调用 foo() 时定义闭包时,作用域链的一个实例被“保存”并作为父作用域链链接到 innerFoo()?因此,如果我声明 var bar = foo(); var bar2 = foo(); bar(); bar2(); bar 和 bar2 正在查看两个独特的作用域链?
  • @Genos:完全正确。每次调用 foo 时,都会创建一个带有自己独立的 a 变量的新闭包。请注意,捕获的是变量,而不是值;这意味着如果您在同一范围内创建 两个 闭包,捕获它们共享的相同变量,即如果一个更改变量,另一个将看到更改。见编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-18
  • 2013-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-07
  • 2011-02-07
相关资源
最近更新 更多