浅谈JS的作用域链(二)
上一篇文章中介绍了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]();// 3data[1]();// 3data[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的作用域链(二)的更多相关文章
- 浅谈JS的作用域链(一)
JS的执行环境 执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念. 在JavaScript中有三种代码运行环境: Global Code JavaScrip ...
- 浅谈JS的作用域链(三)
前面两篇文章介绍了JavaScript执行上下文中两个重要属性:VO/AO和scope chain.本文就来看看执行上下文中的this. 首先看看下面两个对this的概括: this是执行上下文(Ex ...
- 浅谈 js eval作用域
原文:浅谈 js eval作用域 就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console. ...
- 浅谈js变量作用域
变量的作用域也是前端面试题常考的一个问题,掌握下面几个规律可以帮你更好的理解js的作用域. 1.作用域优先级遵循就近原则,函数内部的作用域优先级大于外部 var a=456; var b=111; f ...
- 浅谈JS中的闭包
浅谈JS中的闭包 在介绍闭包之前,我先介绍点JS的基础知识,下面的基础知识会充分的帮助你理解闭包.那么接下来先看下变量的作用域. 变量的作用域 变量共有两种,一种为全局变量,一种为局部变量.那么全局变 ...
- 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂
浅谈JS中的!=.== .!==.===的用法和区别 var num = 1; var str = '1'; var test = 1; test == num //tr ...
- JS 之作用域链和闭包
1.JS无块级作用域 <script> function Main(){ if (1==1){ var name = "alex"; } console.log(nam ...
- 浅谈 js 语句块与标签
原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象 ...
- 浅谈JS面向对象
浅谈JS面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...
随机推荐
- 离群点检测与序列数据异常检测以及异常检测大杀器-iForest
1. 异常检测简介 异常检测,它的任务是发现与大部分其他对象不同的对象,我们称为异常对象.异常检测算法已经广泛应用于电信.互联网和信用卡的诈骗检测.贷款审批.电子商务.网络入侵和天气预报等领域.这些异 ...
- Ceph基础知识和基础架构认识
1 Ceph基础介绍 Ceph是一个可靠地.自动重均衡.自动恢复的分布式存储系统,根据场景划分可以将Ceph分为三大块,分别是对象存储.块设备存储和文件系统服务.在虚拟化领域里,比较常用到的是Cep ...
- dns与wins的区别
将主机名字解析称为ip地址有四种办法: dns.winds.hosts文件.lmhosts文件 dns和hosts是很多系统cout采用的一个名称解析的方法,wins和lmhosts是微软的操作系统此 ...
- data函数参数
date(format,timestamp); 参数 描述 format 必需.规定输出日期字符串的格式.可使用下列字符: d - 一个月中的第几天(从 01 到 31) D - 星期几的文本表示(用 ...
- mha高可用以及读写分离
一.MHA简介 二.工作流程 三.MHA架构图 四.MHA工具介绍 五.基于GTID的主从复制 六.部署MHA 七.配置VIP漂移 八.配置binlog-server 九.MySQL中间件Atlas ...
- <数据结构与算法分析>读书笔记--运行时间计算
有几种方法估计一个程序的运行时间.前面的表是凭经验得到的(可以参考:<数据结构与算法分析>读书笔记--要分析的问题) 如果认为两个程序花费大致相同的时间,要确定哪个程序更快的最好方法很可能 ...
- PAT A1021 Deepest Root (25 分)——图的BFS,DFS
A graph which is connected and acyclic can be considered a tree. The hight of the tree depends on th ...
- GZIP压缩提高网络传输效率
[spring]通过GZIP压缩提高网络传输效率(可以实现任何资源的gzip压缩.包括AJAX) gzip是http协议中使用的一种加密算法,客户端向web服务器端发出了请求后,通常情况下服务器端会将 ...
- Android 让图片等比例缩放的三种方法
方法一:客户端等比例 前提条件:服务器端需要返回原始图片的“宽和高”或者“宽高缩放比例”,客户端要显示的图片的宽或者高只要其一是固定的(例如:高度为200,宽度未知,或者高度为400宽度未知) 在这种 ...
- tiled卷积神经网络(tiled CNN)
这个结构是10年Quoc V.Le等人提出的,这里的tiled,按照 Lecun的解释是Locally-connect non shared.即是局部连接,而且不是共享的,这是针对于权重来说的.本文翻 ...