本章,咱们讨论一下ECMAScript中的做用域链 , 开门见山。前端
什么是做用域链数组
i.ECMAScript是容许建立内部函数的,甚至能从父函数中返回这些函数。做用域链正是内部上下文中全部变量对象(及自由对象)的列表。此链用来变量解析查询。浏览器
做用域链的特性闭包
i.是执行上下文的一个属性函数
activeExecutionContext = { vo : {}, this : thisValue, scope : [] }
ii.逻辑上是一个数组,每个元素是一个变量对象this
iii.定义为:Scope = ActiveContext.VO + Function.[[Scope]] ([[Scope]] 是函数的属性)spa
理解做用域链prototype
i.[[Scope]] 是函数的私有属性,在函数被解析时建立,不会改变。code
为了让你们更好的理解,先让你们看一段代码:对象
var x = 'test'; function foo() { console.log(x); } (function() { var x = 'what'; foo(); })();
上面的代码输出会是什么呢?为何?
控制台将会输出 'test',而非 'what' 。这个例子也说明,一个函数的[[Scope]] 持续存在,即便是在函数建立的做用域已经完成以后。
ii.[[Scope]] “一般”(存在乎外) 包含了父级函数的[[Scope]]属性,ECMAScript依靠这个特性来实现闭包。
iii.[[Scope]]是函数的属性,这也意味着ECMAScript中没有Java那样的块级做用域。(ES6中对这一块获得了增强)只有函数级做用域。
观察如下3种函数构造方式的差别:
var x = 10; function foo() { var y = 20; // 函数声明方式建立 function innerFoo() { console.log(x ,y); } //函数表达式方式建立 var innerFoo2 = function() { console.log(x ,y); } //构造函数方式建立 var innerFoo3 = Function('console.log(x);console.log(y);'); innerFoo(); //10 20 innerFoo2(); //10 20 innerFoo3(); //10 ,y not defined }
经过以上代码,咱们能够看出,经过Function构造函数建立的方法只拥有全局做用域
iiii.变量的二维链式查找
变量的解析是经过做用域链来实现的。
变量本质是上以变量对象的属性方式存在,当变量对象与JavaScript中对象重叠时,它就会自然的受到原型链的影响。
一段有趣的代码:
function foo() { console.log(x); } Object.prototype.x = 10; foo(); //10
缘由: 此时,全局对象为 window(假设在浏览器中运行该段代码),而window对象是Object所派生的。根据原型链查找规则,实例中访问不到的属性和方法,将会在原型中查找。
如下面的代码为例,其查找顺序是这样的:
iiiii.全局代码和 eval 的做用域链
全局代码中的做用域链仅包含全局对象
eval的上下文与当前 calling context(调用上下文) 拥有相同的做用域链
ES5中规定,若是对eval创建别名(非直接调用),这时做用域链仅包含全局对象
iiiiii.做用域链是能够在运行时动态改变的
在with 和 catch 语句中:Scope = withObject || catchObject + VO + [[Scope]]
大多数状况下是不变的,但在with语句和catch语句块中,能够改变做用域链。这种技巧在有些时候很是有用,但大多数状况下,咱们要尽量避免
ES5中,经过词法环境、词法环境记录的方式来扫描这种变化
根据咱们上面讲到的,你们看看以下代码:
var x = 10, y = 10; with({x: 20}) { var x = 30 , y = 30; console.log(x ,y); // 30 30 } console.log(x ,y); // 10 30
输出的结果是: 30 30 10 30 ,为何呢?实际上上面讲到的做用域链的动态改变时,已经对该问题作出了解答, 下面咱们分析一下:
在with和catch语块中的做用域链:
1.x = 10 ,y = 10;
2.对象{x:20}被添加到了做用域的前端
3.在with内部,遇到了var声明。可是什么也没建立,由于在进入上下文时,全部变量已被解析添加
4.在步骤2中,仅修改变量'x' ,实际上对象中的'x'如今被解析,并添加到做用域链的前端,'x'由 20 变为 30
5.一样也有变量对象的属性'y'的修改,被解析后其值也由10变为30
6.此外,在with声明完后,它的特定对象从做用域链中移除,(已改变的变量“x”--30也从那个对象中移除),即做用域链的结构恢复到with获得增强之前的状态。
7.最后console中,当前变量对象 'x'保持相同,'y'的值 = 30(在with声明运行中已发生改变)
iiiiiii.结合this一块儿
直接调用函数,做用域为 withObject
var x = 10; with({ foo : function() { console.log(this.x); } , x : 20 }) { foo(); // 20 }
总结
理解执行上下文、VO(变量对象)、this、做用域链是理解JavaScript执行的基础,尤为是this和做用域链