ES5.1下的作用域链
作用域链(ES5.1规格视角)
javascript作用域类型
javascript采用的是词法作用域(静态作用域) 与之相对的是动态作用域,以下的例子可以很好地说明
let a = 3
function foo() {
console.log(a);
}
function foowrapper() {
let a = 4
foo()
}
foowrapper() // 3而不是4
静态作用域是与我们惯性思维冲突的,从上述例子可以看出,我们第一次看会习惯性往函数调用位置去找变量(动态作用域思维),但是javascript使用的是静态作用域,也就是代码写在哪里,作用域就从那里链接,而不是运行的位置进行作用域链接,这是整个作用域的核心概念
作用域链定义
函数调用上下文作用域链
规格
伪代码
执行环境由词法环境、变量环境 this组成 ,今天我们主要关注点在变量环境(VO|AO),和词法环境[[scope]]
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 所有变量对象的列表
// 用于标识符查找
]
};
Scope定义
// [[Scope]]表示内部使用的抽象操作,用于描述语言规范本身
Scope = AO + [[Scope]]
函数激活作用域链接过程
在函数进入上下文(函数创建时)AO/VO,Scope定义如下:
Scope = AO|VO + [[Scope]]
解析
活动对象是作用域数组的第一个对象,被添加到作用域的前端
Scope = [AO].concat([[Scope]])
函数激活链接实例
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x + y + z);
}
bar();
}
foo(); // 60
对此,我们有如下的变量/活动对象,函数的的[[scope]]属性以及上下文的作用域链:
全局上下文的变量对象是:
globalContext.VO === Global = {
x: 10
foo: <reference to function>
};
在“foo”创建时,“foo”的[[scope]]属性是:
foo.[[Scope]] = [
globalContext.VO
];
在“foo”激活时(进入上下文),“foo”上下文的活动对象是:
fooContext.AO = {
y: 20,
bar: <reference to function>
};
“foo”上下文的作用域链为:
fooContext.Scope = fooContext.AO + foo.[[Scope]]
fooContext.Scope = [
fooContext.AO,
globalContext.VO
];
内部函数“bar”创建时,其[[scope]]为:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];
在“bar”激活时,“bar”上下文的活动对象为:
barContext.AO = {
z: 30
};
“bar”上下文的作用域链为:
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:
barContext.Scope = [
barContext.AO,
fooContext.AO,
globalContext.VO
];
对“x”、“y”、“z”的标识符解析如下:
- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10
- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20
- "z"
-- barContext.AO // found - 30
一个例外-使用函数构造函数生成函数
var x = 10;
function foo() {
var y = 20;
function barFD() { // 函数声明
alert(x);
alert(y);
}
var barFE = function () { // 函数表达式
alert(x);
alert(y);
};
var barFn = Function('alert(x); alert(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo();
正常来说,在函数创建时获取函数的[[scope]]属性,通过该属性访问到所有父上下文的变量,但是由函数构造函数创建的的函数,其[[scope]]总是唯一的全局对象属性。
动态改变作用域链
eval
eval能够动态生成函数,且eval上下文与当前调用上下文的作用域链一致
示例
function foo(str, a) {
eval( str ); // 4
var c = 4;
console.log( a, b );
}
var b = 2;
var c = 3;
foo( "var b = 3; console.log(c)", 1 ); // 1, 3
这里我们使用eval打印了c,可以看到结果是foo作用中的c,这里判断出eval不是像函数构造函数的[[Scope]]总是唯一的全局变量,而b变量能够在foo中访问,也说明了eval与foo是享受了同一个作用域,当然严格模式下,eval会创建自己的作用域,不会影响到当前调用上下文的作用域,如下例:
function foo(str, a) {
'use strict'
eval(str) // 4
var c = 4
console.log(`foo-a ${a} foo-b ${b}`) // 1, 2 而不是 1 3
}
var b = 2
var c = 3
foo('var b = 3; console.log("eval-b",b,"eval-c",c)', 1)
这里访问b时,输出了2而不是eval中的3,说明严格模式下,eval创建了自己的作用域,不会影响到当前调用的上下文作用域
with、catch
它们将作用变量添加到作用域链最前端,作用域链修改如下:
Scope = withObject | catchObject + AO | VO +[[Scope]]
with实例
var x = 10, y = 10;
with ({x: 20}) {
var x = 30, y = 30;
console.log(x) // 30
console.log(y) // 30
}
console.log(x) // 10
console.log(y) // 30
过程解析:
- x = 10, y = 10
- {x: 20} 添加到作用域顶端
- var x 、var y 在执行代码前以及被提升到with外面
- 修改{x: 20} 中的20 为 30
- 没在{x: 30}中找到y,在全局作用域中找到了y,将其修改为30
- with结束,将{x: 30} 从作用域链中移出。
- 此时x为全局作用域x,没有变化,而y在with声明时,变为30
catch实例
try {
...
} catch (ex) {
console.log(ex)
}
作用域链修改为:
var catchObject = {
ex: <exception object>
};
Scope = catchObject + AO|VO + [[Scope]]
知识关联---二维作用域链(作用域链、原型链)查找
为什么会有二维作用域链呢,因为一个属性在对象中没有直接找到,查询会在原型链中继续。也就是搜索属性
过程
- 作用域链环节
- 深入到原型链环节
可能的情况
二维作用域链是指在作用域查找之后,再去原型链上查找,分为一下两种情况
- 基本属性查找,查找到了作用域链末端,全局变量节点,此时会在全局变量的原型链上展开搜索。
- 对象属性查找,先要在作用域链上找到对象本身,然后再去对象原型链上找到指定属性。
情况1示例
function foo() {
console.log(x)
}
Object.prototype.x = 10;
foo(); // 10
过程解析:
- 在foo的VO对象没有找到x
- 向作用域链下一个节点查找,此时为window
- 无法在window上查找到属性x
- 此时已经是作用域顶端,便向着window其原型链找到x,辛酸过程如下图
可以看到window原型链的末端节点为Object,终于在这里找了x,输出了10
情况2示例
function foo() {
console.log(o.x)
}
let o = {
}
Object.prototype.x = 200
foo();
过程解析:
- 在foo的VO对象没有找到o
- 在window对象找到了o
- 在o自身查找x属性,没有找到
- 在o的原型上Object.prototype上找到了x = 200
- 输出200
感谢
参考了颜海镜大大翻译的ES5.1规格,感谢他和汤姆大叔的无私奉献,然后我才能发现一些东西别人几年前就看透了,自己好菜呀(划掉),这里是汤姆大叔的原文,我推荐大看一下他的深入js系列,虽然在2012写的,但是放在现在仍然是教科书级别的干货,例子出于他的文章和《你不知道的javascript》
ES5.1下的作用域链的更多相关文章
- (好文推荐)一篇文章看懂JavaScript作用域链
闭包和作用域链是JavaScript中比较重要的概念,首先,看看几段简单的代码. 代码1: var name = "stephenchan"; var age = 23; func ...
- JavaScript 中的闭包和作用域链(读书笔记)
要想理解闭包,应当先理解JavaScript的作用域和作用域链. JavaScript有一个特性被称之为“声明提前(hoisting)”,即JavaScript函数里声明的所有变量(但不涉及赋值)都被 ...
- javaScript执行环境、作用域链与闭包
一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...
- JavaScript执行环境和作用域(链)的那些事
执行环境 什么是执行环境 提起作用域,我们不得不说说什么是执行环境.执行环境定义了变量或函数有权访问的其他数据,并决定其各自的行为.每一个执行环境都有一个对应的变量对象,这个对象的作用就是保存在环境中 ...
- JavaScript作用域、作用域链 学习随笔
(本文是这些知识点的自我理解.写之余从头回顾,加深理解.取得更多收获之用.) 作用域(scope) 程序设计概念,通常来说,一段程序代码中所用到的名字(JS叫标识符(如变量名.函数名.属性名.参数.. ...
- js原型链闭包作用域链-Tom
1.原型相当于Java.C++里面的父类,由封装公有属性及方法而产生,子类可以继承. 原型继承实现(函数的原型属性指向原型函数一个实例对象,函数的原型的构造函数指向函数本身) 1)eg:原型链 fun ...
- Scope Chain(作用域链)
本章,我们讨论一下ECMAScript中的作用域链 , 开门见山. 什么是作用域链 i.ECMAScript是允许创建内部函数的,甚至能从父函数中返回这些函数.作用域链正是内部上下文中所有变量对象(及 ...
- js对象系列【二】深入理解js函数,详解作用域与作用域链。
这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链.简单的东西大家查下API就行了,这里我更多的是分享自己的理解与技巧.对于作用域和作用域链,相信绝大多数朋友看了我的分享都能基本理解,少 ...
- 《前端之路》之四 JavaScript 的闭包、作用域、作用域链
04:JavaScript 的闭包 一.定义: 常规定义: 闭包的定义: 有权利访问外部函数作用域的函数. 通俗定义: 1.函数内部包含了函数.然后内部函数可以访问外部函数的作用域. 2.内部函数可以 ...
随机推荐
- LeetCode 841:钥匙和房间 Keys and Rooms
题目: 有 N 个房间,开始时你位于 0 号房间.每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间. 在形式上,对于每个房间 i 都有一个钥匙列表 ...
- 七、Spring之深入理解AOP源码
Spring之深入理解AOP源码 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...
- Redis 设计与实现,看 SDS(Simple Dynamic String) 感悟
Redis 设计与实现,看 SDS(Simple Dynamic String) 感悟 今天在看 Redis 设计与实现这本书的时候,发现了里面系统定义的数据结构 SDS,中文名为 简单动态字符串.对 ...
- 如何判断服务器之间的服务是否可用?ping 还是 telnet?
1. 背景 机器A需要调用机器B的服务,为此要保证服务的可用性,我们有时候用ping,有时候用telent来验证机器A和B的连通性,但有时候会出现这种情况,A可以ping通B,但A调用B的服务会一直报 ...
- 图灵的文章“Computing machinery and intelligence”译文
图灵奠基AI的力作“Computing machinery and intelligence”全文译完,摘自http://blog.sciencenet.cn/blog-2322490-112266 ...
- wordpress 数据查询-全局注入-模板数据消费输出简图
我一直比较好奇,类似于wordpress这样的CMS,它可以做的很灵活,同样的软件,为什么就能做出几乎完全不具有相似性的不同站点来呢?除了功能可以有大不同以外,即便是相同的简单blog站他们的外观也可 ...
- Spring循环依赖原因及如何解决
浅谈Spring解决循环依赖的三种方式 SpringBoot构造器注入循环依赖及解决 原文:https://www.baeldung.com/circular-dependencies-in-spri ...
- 2.原生js实现图片懒加载
网上查了很多图片懒加载的内容, 但基本上都是jQuery实现的, 没有说清楚其原理, 所以研究了一下 多的不说, 上代码, 看不明白的建议看下我的上一篇文章<1. 图解浏览器和用户设备的宽高等属 ...
- filebeat + ELK 部署篇
ELK Stack Elasticsearch:分布式搜索和分析引擎,具有高可伸缩.高可靠和易管理等特点.基于 Apache Lucene 构建,能对大容量的数据进行接近实时的存储.搜索和分析操作.通 ...
- BUUCTF Hack World
有返回 ,基于布尔得盲注这里用到异或注入(个人喜欢这样用)1^0 返回 Hello, glzjin wants a girlfriend.1^1 返回 Error Occured When Fetch ...