@TryLoveCatch
2016-11-08T06:44:08.000000Z
字数 4188
阅读 1924
javascript核心概念
一般指全局,是全局执行上下文的一个属性
activeExecutionContext = {VO: {// 上下文数据(var, FD, function arguments)}};
全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象;
这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。
只有全局上下文的变量对象允许通过VO的属性名称来间接访问
而在全局对象里
VO === this === global
在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色,AO比VO多了一个arguments变量。
活动对象是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化。
EC分两种,全局执行上下文和函数执行上下文。全局执行上下文只有一份,函数执行上下文可以有多个。对于每个Execution Context都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this。
当一段程序开始时,会先进入全局执行上下文环境[global execution context],用来存储当前上下文中所有已定义或可获取的变量、函数等,这个也是堆栈中最底部的元素,全局上下文取决于执行环境,如Node中的global和Browser中的window
在生成新的上下文时,首先会绑定该上下文的变量对象,其中包括arguments和该函数中定义的变量;之后会创建属于该上下文的作用域链(scope chain),最后将this赋予这一function所属的Object,这一过程可以通过下图表示

var a = "global var";function foo(){console.log(a);}function outerFunc(){var b = "var in outerFunc";console.log(b);function innerFunc(){var c = "var in innerFunc";console.log(c);foo();}innerFunc();}outerFunc();
代码首先进入Global Execution Context,然后依次进入outerFunc,innerFunc和foo的执行上下文,ECS就可以表示为:

可以看出来,Js本身是单线程的,每当有function被执行时,就会产生一个新的上下文,这一上下文会被压入Js的ECS中,function执行结束后则被弹出,因此Js解释器总是在栈顶上下文中执行。
执行上下文的代码被分成两个基本的阶段来处理:
function test(a, b) {var c = 10;function d() {}var e = function _e() {};(function x() {});}test(10); // call
这个阶段,VO将会包含如下属性(顺序也是如此):
1. 函数的所有形参
没有传递对应参数的话,那么由名称和undefined值组成的一种变量对象的属性也将被创建。
2. 所有函数声明
如果变量对象已经存在相同名称的属性,则完全替换这个属性。
3. 所有变量声明
如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
这些就是上篇声明提升Hoisting的原因,加强记忆,我们在看一个例子:
(function(){console.log(foo);console.log(bar);console.log(baz);var foo = function(){};function bar(){console.log("bar");}var bar = 20;console.log(bar);function baz(){console.log("baz");}})()
结果如下:
undefined[Function: bar][Function: baz]20
声明提升,代码改造一下:
(function(){function bar(){console.log("bar");}function baz(){console.log("baz");}var foo;var bar;console.log(foo);console.log(bar);console.log(baz);foo = function(){};bar = 20;console.log(bar);})()
开头那个例子,当进入带有参数10的test函数上下文时,AO表现为如下:
AO(test) = {a: 10,b: undefined,d: <reference to FunctionDeclaration "d">c: undefined,e: undefined};
注意,AO里并不包含函数“x”。这是因为“x”是一个函数表达式(FunctionExpression, 缩写为 FE) 而不是函数声明,函数表达式不会影响VO。
这个阶段,其实可以解释上一篇关于声明提升的原理,变量声明在顺序上跟在函数声明和形式参数声明之后,变量声明不会干扰同名的形参和函数。
这个周期内,AO已经拥有了属性,不过,并不是所有的属性都有值,大部分属性的值还是系统默认的初始值undefined 。
还是前面那个例子, AO在代码执行期间被修改如下:
AO['c'] = 10;AO['e'] = <reference to FunctionExpression "_e">;
在看一个例子:
if (true) {var a = 1;} else {var b = 2;}console.log(a); // 1console.log(b); // undefined,不是b没有声明,而是b的值是undefined
虽然else部分代码永远不会执行,但是不管怎样,变量“b”仍然存在于VO中。javascript没有块状作用域。
作用域链,是在函数调用时创建的,包含该函数的AO和[[scope]],如下:
Scope = AO + [[Scope]]
这个涉及到函数的的生命周期,它分为创建和激活阶段(调用时)。
还是最开始的例子
var x = 10;function foo() {var y = 20;function bar() {var z = 30;alert(x + y + z);}bar();}foo(); // 60
[[scope]]是函数内部的一个属性,与作用域链对比,[[scope]]是函数的一个属性而不是上下文。
[[scope]]在函数创建时已经存在,静态不变的,永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
[[scope]]保存所有父VO,举个例子
var x = 10;function foo() {var y = 20;function bar() {var z = 30;alert(x + y + z);}bar();}foo(); // 60
程序开始执行时,创建了全局执行上下文,并压入ECS底部:
globalContext.VO === Global = {x: 10foo: function>};
在foo创建时,foo的[[scope]]属性是:
foo.[[Scope]] = [globalContext.VO // === Global];
内部函数bar创建时,其[[scope]]为:
bar.[[Scope]] = [fooContext.AO,globalContext.VO];
当执行foo()时,进入foo的EC,可参考上面关于EC的两个阶段,总的来说,这个时候很foo的AO对象创建了,如下
fooContext.AO = {y: 20,bar: function>};
这个时候,foo的SC(作用域链)就是
fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:fooContext.Scope = [fooContext.AO,globalContext.VO];
执行foo的时候,bar也就执行了创建操作
bar.[[Scope]] = [fooContext.AO,globalContext.VO];
接下来调用了bar(),所以bar的AO对象创建了:
barContext.AO = {z: 30};
这个时候,bar的SC(作用域链)就是
barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:barContext.Scope = [barContext.AO,fooContext.AO,globalContext.VO];
然后,程序执行到x + y + z,开始在作用域链中查找变量
xbarContext.AO (没有找到)fooContext.AO (没有找到)globalContext.VO (找到,返回10)ybarContext.AO (没有找到)fooContext.AO (找到,返回20)globalContext.VO (没有查找)zbarContext.AO (找到,返回30)fooContext.AO (没有查找)globalContext.VO (没有查找)
Scope = AO + [[Scope]]
1. 全局EC只有一个,永远位于ECS栈底。
2. 函数创建时,作用域[[scope]]就创建了,不会更改
3. 函数执行时,当前函数的EC生成,通过arguments初始化AO并绑定到EC,然后与[[scope]]创建函数的作用域链(SC),之后this赋值。
参考:https://www.kancloud.cn/kancloud/deep-understand-javascript/43689
https://www.kancloud.cn/kancloud/deep-understand-javascript/43691
