@TryLoveCatch
2016-11-08T14:44:08.000000Z
字数 4188
阅读 1634
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); // 1
console.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: 10
foo: 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,开始在作用域链中查找变量
x
barContext.AO (没有找到)
fooContext.AO (没有找到)
globalContext.VO (找到,返回10)
y
barContext.AO (没有找到)
fooContext.AO (找到,返回20)
globalContext.VO (没有查找)
z
barContext.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