深入之闭包,运行上下文和作用域链

理解JavaScript的作用域链

2015/10/31 · JavaScript
·
作用域链

原文出处:
田小计划   

上一篇文章中介绍了Execution Context中的三个重要部分:VO/AO,scope
chain和this,并详细的介绍了VO/AO在JavaScript代码执行中的表现。

本文就看看Execution Context中的scope chain。

JavaScript 深入之闭包

2017/05/21 · JavaScript
· 闭包

原文出处: 冴羽   

一、作用域Scope和上下文Context

    在javascript中,作用域scope和上下文context是两个不同的概念。每个函数调用都会伴随着scope和context,从本质上来说,scope是和函数绑定的,而context是基于对象的。即scope用于在函数调用时提供变量访问,且每次函数调用时,都不同;而context始终是关键词this的值,它指向当前执行代码所属的对象。
scope 作用域
    在前一篇的“javascript变量”部分讨论了javascript的作用域,分为全局和局部,且javascript中不存在块作用域。

** ‘this’ context 上下文**
    context
经常被函数所调用的方式所决定。(1)当函数被作为一个对象的方法调用时,this
被设置为该函数所属的对象。如

var obj = {
    foo: function() {
        return this;   
    }
};
obj.foo() === obj; // true。 this指向obj对象

(2)当使用new关键字去创建一个新的函数对象时,this的值也被设置为新创建的函数对象。比如

function foo() {
    alert(this);
}
foo() // window
new foo() // foo

(3)当函数被普通调用时,this被为全局contex或者浏览器的window对象。比如

function foo() {
    alert(this);
}
foo() // window

作用域

开始介绍作用域链之前,先看看JavaScript中的作用域(scope)。在很多语言中(C++,C#,Java),作用域都是通过代码块(由{}包起来的代码)来决定的,但是,在JavaScript作用域是跟函数相关的,也可以说成是function-based。

例如,当for循环这个代码块结束后,依然可以访问变量”i”。

JavaScript

for(var i = 0; i < 3; i++){ console.log(i); } console.log(i); //3

1
2
3
4
5
for(var i = 0; i < 3; i++){
    console.log(i);
}
 
console.log(i); //3

对于作用域,又可以分为全局作用域(Global scope)和局部作用域(Local
scpoe)。

全局作用域中的对象可以在代码的任何地方访问,一般来说,下面情况的对象会在全局作用域中:

  • 最外层函数和在最外层函数外面定义的变量
  • 没有通过关键字”var”声明的变量
  • 浏览器中,window对象的属性

局部作用域又被称为函数作用域(Function
scope),所有的变量和函数只能在作用域内部使用。

JavaScript

var foo = 1; window.bar = 2; function baz(){ a = 3; var b = 4; } //
Global scope: foo, bar, baz, a // Local scope: b

1
2
3
4
5
6
7
8
9
var foo = 1;
window.bar = 2;
 
function baz(){
    a = 3;
    var b = 4;
}
// Global scope: foo, bar, baz, a
// Local scope: b

定义

MDN 对闭包的定义为:

闭包是指那些能够访问自由变量的函数。

那什么是自由变量呢?

自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。

由此,我们可以看出闭包共有两部分组成:

闭包 = 函数 + 函数能够访问的自由变量

举个例子:

var a = 1; function foo() { console.log(a); } foo();

1
2
3
4
5
6
7
var a = 1;
 
function foo() {
    console.log(a);
}
 
foo();

foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo
函数的参数,所以 a 就是自由变量。

那么,函数 foo + foo 函数访问的自由变量 a 不就是构成了一个闭包嘛……

还真是这样的!

所以在《JavaScript权威指南》中就讲到:从技术的角度讲,所有的JavaScript函数都是闭包。

咦,这怎么跟我们平时看到的讲到的闭包不一样呢!?

别着急,这是理论上的闭包,其实还有一个实践角度上的闭包,让我们看看汤姆大叔翻译的关于闭包的文章中的定义:

ECMAScript中,闭包指的是:

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
    1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2. 在代码中引用了自由变量

接下来就来讲讲实践上的闭包。

二、函数生命周期

    函数生命周期可以分为创建和执行两个阶段。
    在函数创建阶段,JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
    在函数执行阶段,JS解析引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。

作用域链

通过前面一篇文章了解到,每一个Execution
Context中都有一个VO,用来存放变量,函数和参数等信息。

在JavaScript代码运行中,所有用到的变量都需要去当前AO/VO中查找,当找不到的时候,就会继续查找上层Execution
Context中的AO/VO。这样一级级向上查找的过程,就是所有Execution
Context中的AO/VO组成了一个作用域链。

所以说,作用域链与一个执行上下文相关,是内部上下文所有变量对象(包括父变量对象)的列表,用于变量查询。

JavaScript

Scope = VO/AO + All Parent VO/AOs

1
Scope = VO/AO + All Parent VO/AOs

看一个例子:

JavaScript

var x = 10; function foo() { var y = 20; function bar() { var z = 30;
console.log(x + y + z); }; bar() }; foo();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var x = 10;
 
function foo() {
    var y = 20;
 
    function bar() {
        var z = 30;
 
        console.log(x + y + z);
    };
 
    bar()
};
 
foo();

上面代码的输出结果为”60″,函数bar可以直接访问”z”,然后通过作用域链访问上层的”x”和”y”。

图片 1

  • 绿色箭头指向VO/AO
  • 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)

再看一个比较典型的例子:

JavaScript

var data = []; for(var i = 0 ; i < 3; i++){ data[i]=function() {
console.log(i); } } data[0]();// 3 data[1]();// 3 data[2]();// 3

1
2
3
4
5
6
7
8
9
10
var data = [];
for(var i = 0 ; i < 3; i++){
    data[i]=function() {
        console.log(i);
    }
}
 
data[0]();// 3
data[1]();// 3
data[2]();// 3

第一感觉(错觉)这段代码会输出”0,1,2″。但是根据前面的介绍,变量”i”是存放在”Global
VO”中的变量,循环结束后”i”的值就被设置为3,所以代码最后的三次函数调用访问的是相同的”Global
VO”中已经被更新的”i”。

分析

让我们先写个例子,例子依然是来自《JavaScript权威指南》,稍微做点改动:

var scope = “global scope”; function checkscope(){ var scope = “local
scope”; function f(){ return scope; } return f; } var foo =
checkscope(); foo();

1
2
3
4
5
6
7
8
9
10
11
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
 
var foo = checkscope();
foo();

首先我们要分析一下这段代码中执行上下文栈和执行上下文的变化情况。

另一个与这段代码相似的例子,在《JavaScript深入之执行上下文》中有着非常详细的分析。如果看不懂以下的执行过程,建议先阅读这篇文章。

这里直接给出简要的执行过程:

  1. 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope
    执行上下文被压入执行上下文栈
  4. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  5. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

了解到这个过程,我们应该思考一个问题,那就是:

当 f 函数执行的时候,checkscope
函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到
checkscope 作用域下的 scope 值呢?

以上的代码,要是转换成 PHP,就会报错,因为在 PHP 中,f
函数只能读取到自己作用域和全局作用域里的值,所以读不到 checkscope 下的
scope 值。(这段我问的PHP同事……)

然而 JavaScript 却是可以的!

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }

1
2
3
fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO
的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使
checkscopeContext 被销毁了,但是 JavaScript 依然会让
checkscopeContext.AO 活在内存中,f 函数依然可以通过 f
函数的作用域链找到它,正是因为 JavaScript
做到了这一点,从而实现了闭包这个概念。

所以,让我们再看一遍实践角度上闭包的定义:

  1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  2. 在代码中引用了自由变量

在这里再补充一个《JavaScript权威指南》英文原版对闭包的定义:

This combination of a function object and a scope (a set of variable
bindings) in which the function’s variables are resolved is called a
closure in the computer science literature.

闭包在计算机科学中也只是一个普通的概念,大家不要去想得太复杂。

三、变量对象

VO 和 AO
    VO (Variable
Object)变量对象,对应的是函数创建阶段,JS解析引擎进行预解析时,所有变量和函数的声明(即在JS引擎的预解析阶段,就确定了VO的内容,只不过此时大部分属性的值都是undefined)。VO与执行上下文相关,知道自己的数据存储在哪里,并且知道如何访问。VO是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:
(1)变量 (var, 变量声明);
(2)函数声明 (FunctionDeclaration, 缩写为FD);
(3)函数的形参

function add(a,b){
    var sum = a + b;
    function say(){
        alert(sum);
    }
    return sum;
}
// sum,say,a,b 组合的对象就是VO,不过该对象的值基本上都是undefined

    AO(Activation
Object)对应的是函数执行阶段,当函数被调用执行时,会创建一个执行上下文,该执行上下文包含了函数所需的所有变量,该变量共同组成了一个新的对象就是Activation
Object。该对象包括了:
(1)函数的所有局部变量
(2)函数的所有命名参数声明(Function Declaration)
(3)函数的参数集合

function add(a,b){
    var sum = a + b;
         var x = 10;
    function say(){
        alert(sum);
    }
    return sum;
}
add(4,5);
//  AO = {
//      arguments : [4,5],
//      a : 4,
//      b : 5,
//          x: undefined
//      say : <reference to function>,
//      sum : undefined
//  }

更详细的关于变量对象VO的知识,请访问:http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html

结合作用域链看闭包

在JavaScript中,闭包跟作用域链有紧密的关系。相信大家对下面的闭包例子一定非常熟悉,代码中通过闭包实现了一个简单的计数器。

JavaScript

function counter() { var x = 0; return { increase: function increase() {
return ++x; }, decrease: function decrease() { return –x; } }; } var
ctor = counter(); console.log(ctor.increase());
console.log(ctor.decrease());

1
2
3
4
5
6
7
8
9
10
11
12
13
function counter() {
    var x = 0;
 
    return {
        increase: function increase() { return ++x; },
        decrease: function decrease() { return –x; }
    };
}
 
var ctor = counter();
 
console.log(ctor.increase());
console.log(ctor.decrease());

下面我们就通过Execution Context和scope
chain来看看在上面闭包代码执行中到底做了哪些事情。

  1. 当代码进入Global Context后,会创建Global VO

图片 2.

  • 绿色箭头指向VO/AO
  • 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)

 

  1. 当代码执行到”var cter = counter();”语句的时候,进入counter Execution
    Context;根据上一篇文章的介绍,这里会创建counter AO,并设置counter
    Execution Context的scope chain

图片 3

  1. 当counter函数执行的最后,并退出的时候,Global
    VO中的ctor就会被设置;这里需要注意的是,虽然counter Execution
    Context退出了执行上下文栈,但是因为ctor中的成员仍然引用counter
    AO(因为counter AO是increase和decrease函数的parent scope),所以counter
    AO依然在Scope中。

图片 4

  1. 当执行”ctor.increase()”代码的时候,代码将进入ctor.increase Execution
    Context,并为该执行上下文创建VO/AO,scope
    chain和设置this;这时,ctor.increase AO将指向counter AO。

图片 5

  • 绿色箭头指向VO/AO
  • 蓝色箭头指向scope chain(VO/AO + All Parent VO/AOs)
  • 红色箭头指向this
  • 黑色箭头指向parent VO/AO

 

相信看到这些,一定会对JavaScript闭包有了比较清晰的认识,也了解为什么counter
Execution Context退出了执行上下文栈,但是counter
AO没有销毁,可以继续访问。

必刷题

接下来,看这道刷题必刷,面试必考的闭包题:

var data = []; for (var i = 0; i 3; i++) { data[i] = function () {
console.log(i); }; } data[0](); data[1](); data[2]();

1
2
3
4
5
6
7
8
9
10
11
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = function () {
    console.log(i);
  };
}
 
data[0]();
data[1]();
data[2]();

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

data[0]Context = { Scope: [AO, globalContext.VO] }

1
2
3
data[0]Context = {
    Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i
为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

var data = []; for (var i = 0; i 3; i++) { data[i] = (function (i) {
return function(){ console.log(i); } })(i); } data[0](); data[1]();
data[2]();

1
2
3
4
5
6
7
8
9
10
11
12
13
var data = [];
 
for (var i = 0; i  3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}
 
data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = { VO: { data: […], i: 3 } }

1
2
3
4
5
6
globalContext = {
    VO: {
        data: […],
        i: 3
    }
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = { Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

1
2
3
data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = { AO: { arguments: { 0: 1, length: 1 }, i: 0 } }

1
2
3
4
5
6
7
8
9
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数
Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO
中查找了,即使 globalContext.VO 也有 i
的值(值为3),所以打印的结果就是0。

data[1] 和 data[2] 是一样的道理。

四、执行上下文

    执行上下文(execution context)是ECMAScript规范中用来描述 JavaScript
代码执行的抽象概念。所有的 JavaScript
代码都是在某个执行上下文中运行的。在当前执行上下文中调用
function会进入一个新的执行上下文。该function调用结束后会返回到原来的执行上下文中。如果function在调用过程中抛出异常,并且没有将其捕获,有可能从多个执行上下文中退出。在function调用过程中,也可能调用其他的function,从而进入新的执行上下文,由此形成一个执行上下文栈。

    每个执行上下文都与一个作用域链(scope
chain)关联起来。该作用域链用来在function执行时求出标识符(identifier)的值。该链中包含多个对象,在对标识符进行求值的过程中,会从链首的对象开始,然后依次查找后面的对象,直到在某个对象中找到与标识符名称相同的属性。在每个对象中进行属性查找时,会使用该对象的prototype链。在一个执行上下文中,与其关联的作用域链只会被with语句和catch
子句影响。

执行上下文属性
    每个执行上下文都有三个重要的属性,变量对象(Variable Object),
作用域链(Scope Chain)和this,当然还有一些其他属性。
![][3]

    当一段javascript代码被执行的时候,javascript解释器会创建并使用Execution
Context,这里有两个阶段:
(1)创建阶段(当函数被调用,但开始执行内部代码之前)
(a) 创建 Scope Chain
(b) 创建VO/AO (函数内部变量声明、函数声明、函数参数)
(c) 设置this值
(2)激活阶段/代码执行阶段
(a) 设置变量的值、函数的引用,然后解释/执行代码。

在阶段(1)(b)创建VO/AO这一步,解释器主要做了以下事情:
(1)根据函数的参数,创建并初始化参数列表
(2)扫描函数内部代码,查找函数声明。对于所有找到的内部函数声明,将函数名和函数引用存储
VO/AO中;如果 VO/AO中已经有同名的函数,那么就进行覆盖
(3)扫描函数内部代码,查找变量声明。对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为
undefined;如果变量名称和已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性(就是说变量无效)
比如以下代码:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}
foo(22);

在“创建阶段”,可以得到下面的 Execution Context object:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}

在“激活/代码执行阶段”,Execution Context object 被更新为:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

    函数在定义时就会确定它的作用域和作用域链(静态),只有在调用的时候才会创建一个执行上下文,(1)其中包含了调用时的形参,函数内的函数声明与变量,同时创建活动对象AO;(2)并将AO压入执行上下文的作用域链的最前端,执行上下文的作用域链是通过它正在调用的函数的[[scope]]属性得到的(动态);(3)执行上下文对象中也包含this的属性

二维作用域链查找

通过上面了解到,作用域链(scope
chain)的主要作用就是用来进行变量查找。但是,在JavaScript中还有原型链(prototype
chain)的概念。

由于作用域链和原型链的相互作用,这样就形成了一个二维的查找。

对于这个二维查找可以总结为:当代码需要查找一个属性(property)或者描述符(identifier)的时候,首先会通过作用域链(scope
chain)来查找相关的对象;一旦对象被找到,就会根据对象的原型链(prototype
chain)来查找属性(property)

下面通过一个例子来看看这个二维查找:

JavaScript

var foo = {} function baz() { Object.prototype.a = ‘Set foo.a from
prototype’; return function inner() { console.log(foo.a); } } baz()();
// Set bar.a from prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = {}
 
function baz() {
 
    Object.prototype.a = ‘Set foo.a from prototype’;
 
    return function inner() {
        console.log(foo.a);
    }
 
}
 
baz()();
// Set bar.a from prototype

对于这个例子,可以通过下图进行解释,代码首先通过作用域链(scope
chain)查找”foo”,最终在Global
context中找到;然后因为”foo”中没有找到属性”a”,将继续沿着原型链(prototype
chain)查找属性”a”。

图片 6

  • 蓝色箭头表示作用域链查找
  • 橘色箭头表示原型链查找

深入系列

JavaScript深入系列目录地址:。

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript
    深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象
  5. JavaScript 深入之作用域链
  6. JavaScript 深入之从 ECMAScript 规范解读
    this
  7. JavaScript 深入之执行上下文

    1 赞 1 收藏
    评论

图片 7

五、作用域链 scope chain

    每个运行上下文都有自己的变量对象,对于全局上下文,它是全局对象本身;对于函数,它是活动对象。作用域链是运行上下文所有变量对象(包括父变量对象)的列表。此链表用于查询标识符。

var x = 10;
function foo() { 
  var y = 20; 
  function bar() {
    alert(x + y);
  } 
  return bar; 
}
foo()(); // 30

上面的例子中, bar 上下文的作用域链包括 AO(bar) –> AO(foo) — >
VO(global).

作用域链如何构造的
    上面提到,作用域链Scope Chain是执行上下文Execution
Context的一个属性。它是在函数被执行时,通过被执行函数的[[scope]]属性得到。
    函数创建时:在javascript中,函数也是一个对象,它有一个属性[[scope]],该属性是在函数被创建时写入,它是该函数对象的所有父变量对象的层级链,它存在于函数这个对象中,直到函数销毁。
    函数执行时:创建执行上下文Execution context, 执行上下文Execution
context 把 AO 放在 函数[[scope]]最前面作为该执行上下文的Scope
chain。
即 Scope chain(运行上下文的属性,动态) =
AO|VO(运行上下文的属性,动态) + [[Scope]](函数的属性,静态)

一个例子

var x = 10; 
function foo() {
  var y = 20; 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  } 
  bar();
}
foo(); // 60

全局上下文的变量对象是:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};

在“foo”创建时,“foo”的[[scope]]属性是:

foo.[[Scope]] = [
  globalContext.VO
];

在“foo”激活时(进入上下文),“foo”上下文的活动对象是:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};

“foo”上下文的作用域链为:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

内部函数“bar”创建时,其[[scope]]为:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];

在“bar”激活时,“bar”上下文的活动对象为:

barContext.AO = {
  z: 30
};

“bar”上下文的作用域链为:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:

barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

对“x”、“y”、“z”的标识符解析如下:

  • “x”
    — barContext.AO // not found
    — fooContext.AO // not found
    — globalContext.VO // found – 10

  • “y”
    — barContext.AO // not found
    — fooContext.AO // found – 20

  • “z”
    — barContext.AO // found – 30

基于作用域链的变量查询
    在代码执行时需要访问某个变量值时,JS引擎会在Execution context的scope
chain中从头到尾查找,直到在scope
chain的某个元素中找到或者无法找到该变量。

总结

本文介绍了JavaScript中的作用域以及作用域链,通过作用域链分析了闭包的执行过程,进一步认识了JavaScript的闭包。

同时,结合原型链,演示了JavaScript中的描述符和属性的查找。

下一篇我们就看看Execution Context中的this属性。

1 赞 5 收藏
评论

图片 7

发表评论

电子邮件地址不会被公开。 必填项已用*标注