深入理解javascript的作用域--函数声明为什么会前置
- 函数声明为什么前置
- 函数声明前置和变量前置优先级问题
- 为什么js文件开头就可以使用Math,String等库,而不需要导入头文件
1.变量对象VO
变量对象(Variable Object, 缩写为VO)是一个抽象
概念中的“对象”,它用于存储执行上下文中的:
1. 变量
2. 函数声明
3. 函数参数
js解释器就是通过变量对象(VO)来找到我们定义的变量和函数的。
举个例子:
var a = 10;
function test(x) {
var b = 20;
}
test(30);
针对例子的浏览器js引擎的VO记录:
//全局作用域
VO(globalContext) = {
a : 10,//变量
test : <ref to function>//函数声明
};
//test函数的函数作用域
VO(test functionContext) = {
x : 30,//函数参数
b: 20//函数声明
};
2.js开头不需要加入头文件谜解
在js代码执行前,js引擎为我们初始化了如下全局作用域的VO。
在全局上下文中
//在全局上下文中 ,vo===this===global
VO(globalContext) === [[global]];
[[global]] = {
Math : <...>,
String : <...>,
isNaN : function() {[Native Code]}
...
...
window : global // applied by browser(host)
};
那么String(10)就相当于[[global]].String(10);了。这就是js代码执行前就可以调用Math等库的原因。
String(10); //[[global]].String(10);
window.a = 10; // [[global]].window.a = 10
this.b = 20; // [[global]].b = 20;
GlobalContextVO (VO === this === global)
其中有趣的是,我们发现global的window指向global自身,所以我们可以在浏览器中尝试window.window.window…一直循环调用下去,可以证明window是一个无限循环调用。
3.函数中的VO–AO
在函数上下文中,我们在进入函数上下文的时候创建vo,这时候称呼他为ao(activation object)。
AO可以看作是VO的激活对象。
VO(functionContext) === AO;
AO = {
arguments : <Arg0>
};
arguments = {
callee,
length,
properties-indexes
};
函数的AO经历两个阶段
- 变量的初始化阶段
- 代码执行阶段
3.1变量初始化阶段
VO按照如下顺序填充:
函数参数(若未传⼊入,初始化该参数值为undefined)
函数声明(若发⽣生命名冲突,会覆盖)
变量声明(初始化变量值为undefined,若发⽣生命名冲突,会忽略。)
举个例子:
function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
b = 20;
}
test(10);
VO扫描创建过程:
添加所有传入参数:a:undefined,b:undefined
添加所有函数声明,名字重复就覆盖:d:func
添加所有变量声明,名字重复就忽略:c:undefined,e:undefined
因此它的VO:
AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <ref to func "d">
e: undefined
};
再来个例子:
function foo(x,y,z){
function x(){};
alert(x);
}
foo(100);
结果alert:function x(){}
分析:扫描传入参数:x:undefined,y:undefined,z:undefined
扫描函数声明:x:func覆盖undefined
扫描变量声明:无
最终x是func。
function foo(x,y,z){
function func(){};
var func;
consoel.log(func);
}
foo(100);
结果console.log:func(){}
分析:扫描传入参数:x:undefined,y:undefined,z:undefined
扫描函数声明:x:func覆盖undefined
扫描变量声明:x名字冲突不更改,直接忽略。
最终x还是func。
3.2代码执行阶段
还是这个例子
function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
b = 20;
}
test(10);
之前分析之后的VO:
AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <ref to func "d">
e: undefined
};
十分好理解的为每个VO变量添加上数值
VO['c'] = 10;
VO['e'] = function _e() {};
VO['b'] = 20;
结果是:
AO(test) = {
a: 10,
b: 20,
c: 10,
d: <reference to FunctionDeclaration "d">
e: function _e() {};
};
3.3练习
题目:
alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {}
alert(x); // 20
if (true) {
var a = 1;
} else {
var b = true;
}
alert(a); // 1
alert(b); // undefined
解析:
变量初始化阶段:
寻找函数声明:x:func
寻找变量声明:x不覆盖(还是func) a:undefined,b:undefined
所以第一行alert(x)返回function
代码执行阶段:
x:10
alert结果就是10
x:20
alert结果就是20
if语句没有产生新作用域,true永远为真,赋值a为1
最后两句打印a为1,b永远得不到执行,打印为undefined