`

理解JavaScript的作用域链

阅读更多

作者:田小计划
出处:http://www.cnblogs.com/wilber2013/


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

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

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

console.log(i); //3

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

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

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

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

复制代码
var foo = 1;
window.bar = 2;

function baz(){
    a = 3;
    var b = 4;
}
// Global scope: foo, bar, baz, a 
// Local scope: b
复制代码

作用域链

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

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

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

Scope = VO/AO + All Parent VO/AOs

看一个例子:

复制代码
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"。

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

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

复制代码
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中,闭包跟作用域链有紧密的关系。相信大家对下面的闭包例子一定非常熟悉,代码中通过闭包实现了一个简单的计数器。

复制代码
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

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

 

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

 

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

 

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

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

 

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

二维作用域链查找

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

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

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

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

复制代码
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"。

  • 蓝色箭头表示作用域链查找
  • 橘色箭头表示原型链查找
分享到:
评论

相关推荐

    理解JavaScript作用域和作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。任何程序...

    深入理解JavaScript作用域和作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 ...

    JavaScript作用域与作用域链深入解析

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 ...

    Javascript作用域和作用域链原理解析

    作用域和作用域链在Javascript和很多其它的编程语言中都是一种基础概念。但很多Javascript开发者并不真正理解它们,但这些概念对掌握Javascript至关重要。 正确的去理解这个概念有利于你去写更好,更高效和更简洁的...

    深入理解变量作用域

    每个javascript执行环境都有一个和它关联在一起的作用域链(scope chain).这个作用域链是一个对象列表或对象链。当javascript需要查询变量x的值时,它就开始查看该链的第一个对象。如果那个对象有一个叫x的属性,那么...

    详解JavaScript作用域和作用域链

    虽然对于许多新手开发者来说,作用域的概念并不是很容易理解,本文我会尽我所能用最简单的方式来解释作用域和作用域链,希望大家有所收获! 作用域(Scope) 1. 什么是作用域 作用域是在运行时代码中的某些特定部分中...

    JavaScript作用域链实例详解

    本文实例讲述了JavaScript作用域链。分享给大家供大家参考,具体如下: 跟其他语言一样,变量和函数的作用域揭示了这些变量和函数的搜索路径。对于JavaScript而言,理解作用域更加重要,因为在JavaScript中,作用域...

    跟我学习javascript的作用域与作用域链

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。 一、...

    JavaScript作用域示例详解

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域示例详解的介绍,希望能帮助大家更好的学习JavaScript。 任何程序设计...

    JavaScript作用域链使用介绍

    觉得写得很清晰,可以简单理解闭包产生原因,但看评论都在说了解了作用域链和活动对象才能真正理解闭包,起初不以为然,后来在跟公司同事交流的时候发现作用域和执行环境确实很重要,又很基础,对理解JavaScript闭包...

    JavaScript作用域链示例分享

    作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理。今天这篇文章对JavaScript作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript。

    深入理解JavaScript系列

    深入理解JavaScript系列(14):作用域链(Scope Chain) 深入理解JavaScript系列(15):函数(Functions) 深入理解JavaScript系列(16):闭包(Closures) 深入理解JavaScript系列(17):面向对象编程之一般...

    深入理解javascript原型和闭包

    深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系

    javascript作用域、作用域链(菜鸟必看)

    javascript的作用域和作用域链是我学习最痛苦的一部分,因为我花了好多时间看了好多技术文档都没有理解.大体知道什么意思了,然后还说不出之所以然来. 通过我大量的测试和看技术文档总结了以下理解,虽然不是很有技术...

    javascript作用域链与执行环境详解

    作用域、作用域链、执行环境、执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下; 局部作用域函数内部的区域,全局作用域就是window; 作用域链取决于函数被声明时的位置,解析...

    JavaScript 作用域链解析

    JavaScript中有Scope(作用域),Scope chain(作用域链),Execute context(执行上下文),Active Object (活动对象),Dynamic Scope(动态作用域),Closure(闭包)这些概念,要理解这些概念,我们从静态和动态两个方面去...

    javascript作用域链(Scope Chain)用法实例解析

    本文实例分析了javascript作用域链(Scope Chain)用法。分享给大家供大家参考,具体如下: 关于js的作用域链,早有耳闻,也曾看过几篇介绍性的博文,但一直都理解的模棱两可。近日又精心翻看了一下《悟透Javascript》...

    详解JavaScript作用域、作用域链和闭包的用法

    主要介绍了JavaScript作用域、作用域链和闭包的用法,帮助大家更好的理解和使用JavaScript,感兴趣的朋友可以了解下

Global site tag (gtag.js) - Google Analytics