首页 > 代码库 > 深入理解javascript的作用域--函数声明为什么会前置

深入理解javascript的作用域--函数声明为什么会前置

 标签: 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经历两个阶段

  1. 变量的初始化阶段
  2. 代码执行阶段

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

深入理解javascript的作用域--函数声明为什么会前置