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 = 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上下文与当前调用上下文的做用域链一致
示例
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建立了本身的做用域,不会影响到当前调用的上下文做用域
它们将做用变量添加到做用域链最前端,做用域链修改以下:
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
过程解析:
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
过程解析:
状况2示例
function foo() { console.log(o.x) } let o = { } Object.prototype.x = 200 foo();
过程解析:
参考了颜海镜大大翻译的ES5.1规格,感谢他和汤姆大叔的无私奉献,而后我才能发现一些东西别人几年前就看透了,本身好菜呀(划掉),这里是汤姆大叔的原文,我推荐大看一下他的深刻js系列,虽然在2012写的,可是放在如今仍然是教科书级别的干货,例子出于他的文章和《你不知道的javascript》