ES5.1下的做用域链

做用域链(ES5.1规格视角)

javascript做用域类型

javascript采用的是词法做用域(静态做用域) 与之相对的是动态做用域,如下的例子能够很好地说明javascript

let a = 3
function foo() {
  console.log(a); 
}
function foowrapper() {
  let a = 4
  foo()
}
foowrapper() // 3而不是4

静态做用域是与咱们惯性思惟冲突的,从上述例子能够看出,咱们第一次看会习惯性往函数调用位置去找变量(动态做用域思惟),可是javascript使用的是静态做用域,也就是代码写在哪里,做用域就从那里连接,而不是运行的位置进行做用域连接,这是整个做用域的核心概念html

做用域链定义

函数调用上下文做用域链

规格

伪代码
执行环境由词法环境、变量环境 this组成 ,今天咱们主要关注点在变量环境(VO|AO),和词法环境[[scope]]前端

activeExecutionContext = {
    VO: {...}, // or AO
    this: thisValue,
    Scope: [ // Scope chain
      // 全部变量对象的列表
      // 用于标识符查找
    ]
};
Scope定义
// [[Scope]]表示内部使用的抽象操做,用于描述语言规范自己
Scope = AO + [[Scope]]

函数激活做用域连接过程

在函数进入上下文(函数建立时)AO/VO,Scope定义以下:java

Scope = AO|VO + [[Scope]]

解析
活动对象是做用域数组的第一个对象,被添加到做用域的前端数组

Scope = [AO].concat([[Scope]])

函数激活连接实例app

var x = 10;
 
function foo() {
  var y = 20;
 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  }
 
  bar();
}
 
foo(); // 60

对此,咱们有以下的变量/活动对象,函数的的[[scope]]属性以及上下文的做用域链:ecmascript

全局上下文的变量对象是:函数

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};

在“foo”建立时,“foo”的[[scope]]属性是:this

foo.[[Scope]] = [
  globalContext.VO
];

在“foo”激活时(进入上下文),“foo”上下文的活动对象是:es5

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. 深刻到原型链环节

    可能的状况
    二维做用域链是指在做用域查找以后,再去原型链上查找,分为一下两种状况
  3. 基本属性查找,查找到了做用域链末端,全局变量节点,此时会在全局变量的原型链上展开搜索。
  4. 对象属性查找,先要在做用域链上找到对象自己,而后再去对象原型链上找到指定属性。

状况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》

相关文章
相关标签/搜索