浅谈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面向对象 一 .什么是面向过程 就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了.注重代码的过程部分. 二.什么是面向对象 最先出现在管理学 ...
随机推荐
- Python实例---模拟微信网页登录(day5)
第六步: 实现发送/接受消息---day5代码 settings.py """ Django settings for weixin project. Generated ...
- MySql数据库基础笔记(一)
一.表与库的概念 数据库管理数据,它是以什么样的形式组成的? 生活中的表---->表 table多行多列,传统的数据库都是这样的;声明了表头,一个表创建好了,剩下的就是往表中添加数据 多张表放在 ...
- 【PAT】B1042 字符统计(20 分)
/* 15分的题很简单,但是自己写的时候在输入数据时没有考虑好下标 另外有忘记了输入字符时考虑是否有\n */ #include<stdio.h> #include<algorith ...
- 【PAT】B1060 爱丁顿数(25 分)
逻辑问题,对我来说还是挺有难度的,一开始想不通 我输入数据并以数据为下标,数据出现次数为内容存储 然后从后遍历计算所有大于当前下标的元素出现的次数 最后遍历一遍确定是否为爱丁顿数,如果大于当前已经找到 ...
- nginx1.14的安装
编译安装nginx1.14.2 #拷贝指定文件到当前目录下[root@localhost ~]# find /usr/share -iname "*.jpg" -exec cp { ...
- 删除排序数组中的重复项的golang实现
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. 给定数组 ...
- [经验总结] 在 windows 命令窗口中运行 python 脚本时提示 ModuleNotFoundError: No module named 'xxx'
先给出的代码和目录结构 获取CPU代码如下: # -*- coding:utf-8 -*- ''' Created on Sep 10, 2018 @author: ''' import sys im ...
- 【转】玩玩你的Windows防火墙——穿透与防御
前言:在防火墙专区,我经常看见朋友们讨论,“某某防火墙的性能如何”,亦或是,“某某防火墙的防御能力如何”.实际上,一个防火墙所履行的基本职责便是“网络访问控制”,即放行我们允许的通信,阻止我们未允许的 ...
- ROS教程5 使用串口
http://blog.csdn.net/u011853479/article/details/51261704 http://blog.csdn.net/u011853479/article/det ...
- jQuery添加自定义函数的三种方法
原文链接:http://caibaojian.com/284.html 方法一: jQuery.fn.setApDiv=function () { //apDiv浮动层显示位置居中控制 var whe ...