JavaScript不一样于其余语言,存在变量提高,以下面代码例子:javascript
console.log(x)
var x = 'hello world';
复制代码
这段代码不会报错,会输出 undefined
。这就是所谓的变量提高,但具体细节JS引擎是怎么处理的,还须要理解JS的Execution Context执行上下文。java
Execution Context 是JS执行代码时候的一个上下文环境。如执行到一个调用函数,就会进入这个函数的执行上下文,执行上下文中会肯定这个函数执行期间用到的诸如this,变量,对象以及定义的方法等。express
当浏览器加载script的时候,默认直接进入Global Execution Context(全局上下文),将全局上下文入栈。若是在代码中调用了函数,则会建立Function Execution Context(函数上下文)并压入调用栈内,变成当前的执行环境上下文。当执行完该函数,该函数的执行上下文便从调用栈弹出返回到上一个执行上下文。浏览器
Global execution context。当js文件加载进浏览器运行的时候,进入的就是全局执行上下文。全局变量都是在这个执行上下文中。代码在任何位置都能访问。ecmascript
Functional execution context。定义在具体某个方法中的上下文。只有在该方法和该方法中的内部方法中访问。函数
Eval。定义在Eval方法中的上下文。该方法不建议使用对此就不进一步研究。ui
Js是单线程执行,每次注定只能访问一个execution context。所以调用栈最上方的执行上下文将最早被执行,执行完后返回到上层的执行上下文继续执行。引用一篇博文的动态图示以下:this
execution context期间js引擎主要分两个阶段:spa
建立阶段(函数调用时,但在函数执行前)线程
JS解析器扫描一遍代码,建立execution context内对应的variables, functions和arguments。这三个称之为Variable Object。
建立做用域链scope chain
决定this的指向
executionContextObj = {
'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
'this': {}
}
复制代码
executionContextObj由函数调用时运行前建立,建立阶段arguments的参数会直接传入,函数内部定义的变量会初始化为undefined。
执行阶段
下面是执行上下文期间JS引擎执行伪代码
一个简单例子以下:
console.log(foo(22))
console.log(x);
var x = 'hello world';
function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
console.log(i)
}
复制代码
(a):代码首先进入到全局上下文的建立阶段。
ExecutionContextGlobal = {
scopeChain: {...},
variableObject: {
x: undefined,
foo: pointer to function foo() }, this: {...}
}
复制代码
而后进入全局执行上下文的执行阶段。这一阶段从上至下逐条执行代码,运行到console.log(foo(22))
该行时,建立阶段已经为variableObject中的foo赋值了,所以执行时会执行foo(22)
函数。
当执行foo(22)
函数时,又将进入foo()
的执行上下文,详见(b)。
当执行到console.log(x)
时,此时x
在variableObject中赋值为undefined
,所以打印出undefined
,这也正是变量提高产生的结果。
当执行到var x = 'hello world';
,variableObject中的x被赋值为hello world
。
继续往下是foo
函数的声明,所以什么也不作,执行阶段结束。下面是执行阶段完成后的ExecutionContextGlobal。
ExecutionContextGlobal = {
scopeChain: {...},
variableObject: {
x: 'hello world',
foo: pointer to function foo() }, this: {...}
}
复制代码
(b):当js调用foo(22)时,进入到foo()函数的执行上下文,首先进行该上下文的建立阶段。
ExecutionContextFoo = {
scopeChain: {...},
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c() a: undefined, b: undefined }, this: {...}
}
复制代码
当执行阶段运行完后,ExecutionContextFoo以下。
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c() a: 'hello', b: pointer to function privateB() }, this: { ... }
}
复制代码
理清了JS中的执行上下文,就很容易明白变量提高具体是怎么回事了。在代码执行前,执行上下文已经给对应的声明赋值,只不过变量是赋值为undefined
,函数赋值为对应的引用,然后在执行阶段再将对应值赋值给变量。
首先看下面几个代码片断,分别输出是什么?
Question 1:
function foo(){
function bar() {
return 3;
}
return bar();
function bar() {
return 8;
}
}
alert(foo());
复制代码
Question 2:
function foo(){
var bar = function() {
return 3;
};
return bar();
var bar = function() {
return 8;
};
}
alert(foo());
复制代码
Question 3:
alert(foo());
function foo(){
var bar = function() {
return 3;
};
return bar();
var bar = function() {
return 8;
};
}
复制代码
Question 4:
function foo(){
return bar();
var bar = function() {
return 3;
};
var bar = function() {
return 8;
};
}
alert(foo());
复制代码
上面4个代码片断分别输出 8
,3
,3
,[Type Error: bar is not a function]
。
function name([param,[, param,[..., param]]]) { [statements] }
函数声明以关键字function
开头定义函数,同时有肯定的函数名。如最简单的栗子:
function bar() {
return 3;
}
复制代码
经过函数执行上下文,函数声明会产生hoisted,即函数声明会提高到代码最上面。
因此在Question 1中,foo.VO中 bar:pointer to the function bar()
,由于有声明了两次bar()
函数,因此后面的定义覆盖前面的定义。
var myFunction = function [name]([param1[, param2[, ..., paramN]]]) { statements };
函数表达式中,函数名字能够省略,简单栗子以下:
//anonymous function expression
var a = function() {
return 3;
}
//named function expression
var a = function bar() {
return 3;
}
//self invoking function expression
(function sayHello() {
alert("hello!");
})();
复制代码
以上三种都是函数表达式,最后一种是当即执行函数。函数表达式不会提高到代码最上面,如Question 2中,在函数执行上下文的建立阶段中,foo.VO 中 bar : undefined
,在执行阶段才进行赋值。
在回头看看Question 4:
function foo(){
return bar(); // 执行阶段返回调用bar(),但建立阶段bar被赋值为 undefined,因此报Type Error。
var bar = function() {
return 3;
};
var bar = function() {
return 8;
};
}
alert(foo());
复制代码
参考
What is the Execution Context & Stack in JavaScript?