深度理解作用域链和闭包

当函数被调用的时候,一个叫做actvation object的特别对象被创建。这个对象被传入实参和特别的arguments对象。然后,这个actvation object被用作函数的变量对象,供函数引用对象的值

actvation object存储实参,arguments对象,函数声明,但不存储函数表达式。

function foo(x, y) {
  var z = 30;
  function bar() {} // FD
  (function baz() {}); // FE
}
 
foo(10, 20);

在这段代码foo函数拥有如下的actvation object对象。

再一次提醒函数表达式(FE)不会被存储。

作用域链

作用域链是为了查找变量,规则很简单:如果在当前作用域(activation object)中找不到一个变量,就向上在父变量对象中查找。如此反复。

当函数定义的时候,会创建一个[[Scope]]属性。和activation object不同,它存储的是其父函数的作用域链。

而先前也提到,当函数调用的时候会创建一个activetion object 变量对象。

当最终函数执行时,可访问的变量实际在一个叫作用域链东西中寻找,这个作用域链实际就是 activetion object( 自身变量对象)和 [[Scope]](之前的作用域链)。

分析下面代码

var x = 10;
 
(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    // "x" and "y" are "free variables"
    // and are found in the next (after
    // bar's activation object) object
    // of the bar's scope chain
    console.log(x + y + z);
  })();
})();

在作用域链中,每个AO通过一个隐藏属性__parent__引用下一个对象。

上述代码的结构如下

闭包

在javascript中,函数运行完毕就会释放局部变量。在这样一个情况下,当一个函数返回值也是函数时,外部函数执行完毕,而返回的函数还未执行时,未了让返回的函数可以访问外部函数的变量,就引入了闭包概念。

在外部函数运行时,定义返回函数,此时,将外部函数的作用域链存储到[[Scope]]属性当中,在返回函数调用时,创建activation object,再将activation object和[[Scope]]属性作为它的作用于链。

也因此,[[Scope]]属性存在对父函数变量的引用,即使父函数运行完毕,也不会释放变量。

Scope chain = Activation object + [[Scope]]

再一次提醒,重点是,当函数定义的时候,存储了父函数的作用域链,这样它就可以在自己执行的时候,寻找父函数中的变量。

下面巩固一下闭包的工作流程和一个特别的情况。

function baz() {
  var x = 1;
  return {
    foo: function foo() { return ++x; },
    bar: function bar() { return --x; }
  };
}
 
var closures = baz();
 
console.log(
  closures.foo(), // 2
  closures.bar()  // 1
);

当baz运行时,定义了两个函数foo和bar,此时复制了baz的作用域链到两个函数的[[scope]]属性。然后到foo和bar调用时创建自己的activition object,并将activition object与[[scope]]的合并结果作为它们自己的作用域。

而他们各自的 [[scope]]里的x实际都是指baz AO里的x。当一个函数对x修改时,另一个函数再引用实际是修改后的结果。

原文地址:https://www.cnblogs.com/winderby/p/4074632.html