作用域链(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

过程解析:

  1. x = 10, y = 10
  2. {x: 20} 添加到作用域顶端
  3. var x 、var y 在执行代码前以及被提升到with外面
  4. 修改{x: 20} 中的20 为 30
  5. 没在{x: 30}中找到y,在全局作用域中找到了y,将其修改为30
  6. with结束,将{x: 30} 从作用域链中移出。
  7. 此时x为全局作用域x,没有变化,而y在with声明时,变为30

catch实例

try {
...
} catch (ex) {
console.log(ex)
}

作用域链修改为:

var catchObject = {
ex: <exception object>
}; Scope = catchObject + AO|VO + [[Scope]]

知识关联---二维作用域链(作用域链、原型链)查找

为什么会有二维作用域链呢,因为一个属性在对象中没有直接找到,查询会在原型链中继续。也就是搜索属性

过程
  1. 作用域链环节
  2. 深入到原型链环节
可能的情况

二维作用域链是指在作用域查找之后,再去原型链上查找,分为一下两种情况

  1. 基本属性查找,查找到了作用域链末端,全局变量节点,此时会在全局变量的原型链上展开搜索。
  2. 对象属性查找,先要在作用域链上找到对象本身,然后再去对象原型链上找到指定属性。

情况1示例

function foo() {
console.log(x)
} Object.prototype.x = 10; foo(); // 10

过程解析:

  1. 在foo的VO对象没有找到x
  2. 向作用域链下一个节点查找,此时为window
  3. 无法在window上查找到属性x
  4. 此时已经是作用域顶端,便向着window其原型链找到x,辛酸过程如下图



    可以看到window原型链的末端节点为Object,终于在这里找了x,输出了10

情况2示例

function foo() {
console.log(o.x)
} let o = {
} Object.prototype.x = 200
foo();

过程解析:

  1. 在foo的VO对象没有找到o
  2. 在window对象找到了o
  3. 在o自身查找x属性,没有找到
  4. 在o的原型上Object.prototype上找到了x = 200
  5. 输出200

感谢

参考了颜海镜大大翻译的ES5.1规格,感谢他和汤姆大叔的无私奉献,然后我才能发现一些东西别人几年前就看透了,自己好菜呀(划掉),这里是汤姆大叔的原文,我推荐大看一下他的深入js系列,虽然在2012写的,但是放在现在仍然是教科书级别的干货,例子出于他的文章和《你不知道的javascript》

ES5.1下的作用域链的更多相关文章

  1. (好文推荐)一篇文章看懂JavaScript作用域链

    闭包和作用域链是JavaScript中比较重要的概念,首先,看看几段简单的代码. 代码1: var name = "stephenchan"; var age = 23; func ...

  2. JavaScript 中的闭包和作用域链(读书笔记)

    要想理解闭包,应当先理解JavaScript的作用域和作用域链. JavaScript有一个特性被称之为“声明提前(hoisting)”,即JavaScript函数里声明的所有变量(但不涉及赋值)都被 ...

  3. javaScript执行环境、作用域链与闭包

    一.执行环境 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为:每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中.虽然我们编写的代码无法访问这个对象 ...

  4. JavaScript执行环境和作用域(链)的那些事

    执行环境 什么是执行环境 提起作用域,我们不得不说说什么是执行环境.执行环境定义了变量或函数有权访问的其他数据,并决定其各自的行为.每一个执行环境都有一个对应的变量对象,这个对象的作用就是保存在环境中 ...

  5. JavaScript作用域、作用域链 学习随笔

    (本文是这些知识点的自我理解.写之余从头回顾,加深理解.取得更多收获之用.) 作用域(scope) 程序设计概念,通常来说,一段程序代码中所用到的名字(JS叫标识符(如变量名.函数名.属性名.参数.. ...

  6. js原型链闭包作用域链-Tom

    1.原型相当于Java.C++里面的父类,由封装公有属性及方法而产生,子类可以继承. 原型继承实现(函数的原型属性指向原型函数一个实例对象,函数的原型的构造函数指向函数本身) 1)eg:原型链 fun ...

  7. Scope Chain(作用域链)

    本章,我们讨论一下ECMAScript中的作用域链 , 开门见山. 什么是作用域链 i.ECMAScript是允许创建内部函数的,甚至能从父函数中返回这些函数.作用域链正是内部上下文中所有变量对象(及 ...

  8. js对象系列【二】深入理解js函数,详解作用域与作用域链。

    这次说一下对象具体的一个实例:函数,以及其对应的作用域与作用域链.简单的东西大家查下API就行了,这里我更多的是分享自己的理解与技巧.对于作用域和作用域链,相信绝大多数朋友看了我的分享都能基本理解,少 ...

  9. 《前端之路》之四 JavaScript 的闭包、作用域、作用域链

    04:JavaScript 的闭包 一.定义: 常规定义: 闭包的定义: 有权利访问外部函数作用域的函数. 通俗定义: 1.函数内部包含了函数.然后内部函数可以访问外部函数的作用域. 2.内部函数可以 ...

随机推荐

  1. Unreal Engine 4 系列教程 Part 6:动画教程

    .katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > ...

  2. Erlang基础2

    1. apply apply(Mod, Func, [Arg1, Arg2, ..., ArgN]) 等价于 Mod:Func(Arg1, Arg2, ..., ArgN) 区别在于,使用apply, ...

  3. JAVAWeb入门之JSP基础知识

    也是到了考试周,很多课都结了,准备去学点新东西.随后就开始自学JAVAWeb. 要学习JAVAWeb,首先需下面的知识: a)      HTML/CSS/JS(前端页面),XML,JSON,vue ...

  4. 深入理解TCP/IP应用层

    TCP/IP四层模型分为: 应用层,传输层(只关注起点(发送者)和终点(接收者)),网络层(规划出一条或几条路线),数据链路层(关注两个相邻点之间怎么传输)   协议   应用层 DNS,URI,HT ...

  5. .net Dapper 学习系列(1) ---Dapper入门

    目录 写在前面 为什么选择Dapper 在项目中安装Dapper 在项目中使用Dapper 在项目中使用Dapper 进行单表增删改数据操作 总结 写在前面 Dapper 是一款轻量级ORM架构.为解 ...

  6. Windows Service 服务搭配FluentScheduler实现定时任务调度

    Windows Service 服务 创建Windows Service 项目 创建一个Windows Service项目,并将项目名称改为 TaskWindowService 在解决方案资源管理器内 ...

  7. 基于cephfs搭建高可用分布式存储并mount到本地

    原文:https://www.fullstackmemo.com/2018/10/11/cephfs-ha-mount-storage/ 服务器硬件配置及环境 项目 说明 CPU 1核 内存 1GB ...

  8. Android studio R文件丢失或错误解决方法

    android studio中有时引用资源会出现R文件丢失或报错,大多数情况下是由于引入资源时R文件没有及时更新造成的 (在代码没有错误或资源引用没有错误的前提下) 注意:资源文件的文件名必须小写,即 ...

  9. Java学习——枚举类

    Java学习——枚举类 摘要:本文主要介绍了Java的枚举类. 部分内容来自以下博客: https://www.cnblogs.com/sister/p/4700702.html https://bl ...

  10. html 实体编码转换成原字符

    今天遇到件很恶心的事,某国外歌词网站提供的歌词在源文件里使用“&#数字;”格式的编码表示abcd....原来小菜我实在才疏学浅不知此为何物,于是特有的搜索引擎控,搜之.片刻得解,此乃html实 ...