function test() {
console.log(a); // undefined
console.log(foo()); // 2
var a = 1;
function foo() {
return 2;
};
};
test();
不是很准确的草图,大概了解就好。
首先JS解释器(引擎)开始解释代码,构建执行环境栈(Execution Context Stack),并根据执行环境的不同生成不同的执行上下文(Execution Context)
栈底永远是全局上下文(后面说),当遇到test(),确认调用函数,就创建生成test函数自己的上下文,然后将函数执行上下文入栈(push on)到执行环境栈中。
-
testEC(test Execution Context)将会开始创建变量对象,我们知道,当调用一个函数(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。
创建阶段:
在这个阶段中,执行上下文分别会创建变量对象、建立作用域链,以及确定this的指向。代码执行阶段:
创建完成后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。
-
所以testEC将会生成一个变量对象,与之还有作用域链、this(这里先不讨论),注意,这里是变量对象的创建阶段,用VO简示。
变量对象的创建,经历以下过程。
建立arguments对象。检查上下文中的参数,建立该对象下的属性与属性值(key-value)
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
所以,于是就有了图中的VO引用的对象的图示,这就是变量提升的真正原因,对于函数声明,它的优先级是大于变量声明的,所以在创建阶段,函数声明的函数名就持有了函数的引用。而变量赋值需要在后面的执行阶段才被赋值。
未进入执行阶段前,变量对象中的属性都不能被访问!但是进入执行阶段之后,变量对象转变为了活动对象(Active Object),里面的属性都能被访问了,然后开始进行执行阶段的操作。
进入执行阶段,这时可称之为活动对象了(Active Object),将进行赋值操作。于是这时的变量就能被自由访问了。
关于arguments对象,是个类数组结构
目前我的测试:
函数内设有形参,但是不传实参,遍历arguments对象时没有任何值
函数内不设形参,但是传实参,遍历arguments对象有值对应的索引
function test(a,b,c,d) {
for (var i in arguments) {
console.log(i);
}
console.log(arguments.length+"个");
};
test(1,2,3,4);
// 0 1 2 3
// "4个"
function test2(a,b,c,d) {
for (var i in arguments) {
document.write(i+"<br />")
}
document.write(arguments.length+"个");
};
test2();
// 什么也没有
// 0个
脑子里有这副图后,我们在来看另一个栗子:
function test() {
console.log(foo());
console.log(bar);
var foo = "Hello";
console.log(foo);
var bar = function () {
return "world";
};
function foo() {
return "hello";
};
};
test();
// "hello"
// "undefined"
// "Hello"
主要讨论同变量声明与同变量声明与函数声明的情况,这也是变量对象在创建时所做的工作;
检查当前上下文的函数声明,也就是使用
function
关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为
undefined
。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined
,则会直接跳过,原属性值不会被修改。
如代码中的
foo
,在test
函数的执行上下文创建变量对象后,创建阶段,foo
就是变量对象中的的键名(Key),而键值就是函数的地址指针,因为函数声明的优先级大于变量声明,所以foo
此时就持有了函数的引用,而var foo
的变量声明就被跳过。执行阶段,进行赋值操作,
foo
被重新赋值"Hello"
,同时变量bar
从未赋值(undefined
)到持有一个函数的引用,这就是变量对象=>活动对象,执行栈,执行上下文所发生的操作。
关于全局上下文
var globla = 10;
function test() {
return globla++;
};
test();
// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
VO: window,
scopeChain: {},
this: window
}
以浏览器中为例,全局对象为window
。
全局上下文有一个特殊的地方,它的变量对象,就是window
对象。而这个特殊,在this
指向上也同样适用,this
也是指向window
。
除此之外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。其他所有的上下文环境,都能直接访问全局上下文的属性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。