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

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

作用域

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

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

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),所有的变量和函数只能在作用域内部使用。

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

作用域链

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

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

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

Scope = VO/AO + All Parent VO/AOs

看一个例子:

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"。

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

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

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

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

  • 绿色箭头指向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)

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

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"。

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

总结

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

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

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

浅谈JS的作用域链(二)的更多相关文章

  1. 浅谈JS的作用域链(一)

    JS的执行环境 执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念. 在JavaScript中有三种代码运行环境: Global Code JavaScrip ...

  2. 浅谈JS的作用域链(三)

    前面两篇文章介绍了JavaScript执行上下文中两个重要属性:VO/AO和scope chain.本文就来看看执行上下文中的this. 首先看看下面两个对this的概括: this是执行上下文(Ex ...

  3. 浅谈 js eval作用域

    原文:浅谈 js eval作用域 就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console. ...

  4. 浅谈js变量作用域

    变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...

  5. 浅谈JS中的闭包

    浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...

  6. 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂

    浅谈JS中的!=.== .!==.===的用法和区别   var num = 1;     var str = '1';     var test = 1;     test == num  //tr ...

  7. JS 之作用域链和闭包

    1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...

  8. 浅谈 js 语句块与标签

    原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...

  9. 浅谈JS面向对象

    浅谈JS面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...

随机推荐

  1. Linux Regulator Framework(2)_regulator driver

    转自蜗窝科技:http://www.wowotech.net/pm_subsystem/regulator_driver.html 说实话,这篇好难懂啊... 1. 前言 本文从regulator d ...

  2. [HDFS_4] HDFS 的 Java 应用开发

    0. 说明 在 IDEA下 进行 HDFS 的 Java 应用开发 通过编写代码实现对 HDFS 的增删改查操作 1. 流程 1.1 在项目下新建 Moudle 略 1.2 为 Moudle 添加 M ...

  3. knockoutjs关于ko.bindingHandlers的updata订阅

    ko.bindingHandlers是先执行init进行初始化数据的绑定(如果需要执行updata进行数据更新可以不用初始化); init: function(element, valueAccess ...

  4. 【PAT】B1027 打印沙漏(20 分)

    #include<cstdio> #include<stdlib.h> char C='*'; int qiushu(int hang){//输入行数,求出字符数 int su ...

  5. 17秋 软件工程 第六次作业 Beta冲刺 Scrum1

    17秋 软件工程 第六次作业 Beta冲刺 Scrum1 各个成员冲刺期间完成的任务 重新梳理项目架构与当前进展,并且对我们的Alpha版本项目进行完整测试,将测试过程中发现的问题列入Github i ...

  6. php 怎么在foreach中循环数组的时候添加元素的属性

    foreach($arr as $key => &$vo){ //注意,由于上面遍历的时候写了地址传值符&, //所以下面可以直接给$vo 赋值;如果不写&符号,下面这样 ...

  7. 很好的一篇eureka的讲解文章

    文章地址 http://nobodyiam.com/2016/06/25/dive-into-eureka/

  8. AI 数值计算

    数值计算,通过迭代来更新解的估计值. 1.上溢和下溢 实数按照一定的精度存储在计算机中,通常存在误差,进而可能导致一些错误. 1)下溢(underflow),例如接近0的数 2)上溢(overflow ...

  9. MySQL(九)插入、更新和删除

    常用的SQL语句,除了select用于查询,还有insert.update.delete等. 一.insert insert:用来插入(或添加)行到数据库中,常见方式有以下几种: ①插入完整的行: ② ...

  10. VisualStudio2008+水晶报表的使用

    1:打开VisualStudio2008,新建一个Windows窗体应用程序项目,名称可以自定义,选择项目的保存路径,然后确定 刚新建好的窗体应用程序: 2. 把准备好的水晶报表插件复制到项目中的bi ...