浅谈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 ](); // 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的作用域链(二)的更多相关文章
- 浅谈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中remove的一些坑
前几天,使用python时遇到这么一个需求,删除一个列表中值为1的元素.我寻思着使用remove方法,但是remove方法只会删除第一个,于是我使用for循环去删除.代码和运行结果如下: 当时这个结果 ...
- IDEA 如何查看一个类里面的所有方法
快捷键:Alt+7
- java调用Linux执行Python爬虫,并将数据存储到elasticsearch中--(java后台代码)
该篇博客主要是java代码,如需相应脚本及java连接elasticsearch工具类代码,请移步到上一篇博客(https://www.cnblogs.com/chenyuanbo/p/9973685 ...
- Appium 实战练习一
# -*- coding:utf-8 -*- ''' Created on Sep 30, 2018 @author: SaShuangYiBing Comment: ''' import time ...
- <20190303>大厂的风度,firmware每年更新!
哪怕是最普通的型号, 思科Cisco 每隔一个周期都会推送一个新的firmware, 来提高 路由器的稳定性,和整体兼容性, 2015年买的路由器, 今年年初又发布一个更新包. Release Not ...
- React脚手架创建一个React应用以及项目目录结构详解
react脚手架 用来帮助程序员快速创建一个基于xxx库的模板项目,包含了所有需要的配置,指定好了所有的依赖,可以直接安装/编译/运行一个简单效果 react提供了一个专门用于创建react项目的脚手 ...
- php如何控制用户对图片的访问 PHP禁止图片盗链
本文摘自网络仅供学习只用 本人根据教程总结了一下https://www.imooc.com/video/13412 主要是利用apache的htacess进行控制,,拿什么判断是不是通过本站点访问的呢 ...
- ucml 查询组件
查询视图组件通常用于通过某些字段来查询数据,在页面设计处选好需要查询的字段,并设置操作符等信息最后设计页面添加执行查询的按钮即可,页面效果同编辑视图组件. 具体操作示例: 1.添加一个新的查询组件VC ...
- npm和node的版本过低时的解决办法
npm版本过低时的解决办法npm全名Node Package Manager 1.配置源的三种方法:1).npmrc文件的作用,就是配置npm源:使用淘宝源的方法就是在文件.npmrc中加入下面的语句 ...
- (转)Docker磁盘垃圾清理
文章转自https://mp.weixin.qq.com/s/S8ZjGZF8oLC8c1JRnkE5yw?tdsourcetag=s_pctim_aiomsg 1.整体分析 对于Docker来说,存 ...