Js代码分为两个阶段:编译阶段和执行阶段
我们习惯将var a = 2;看作一个声明,而实际上JavaScript引擎并不这么认为。它将var a和a = 2当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务。
这意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为 提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。
1. 变量声明提升
Js编译器会把变量声明看成两个部分: 声明操作(var a) 和 赋值操作(a=2)
声明操作在编译阶段进行,声明操作会被提升到执行环境的顶部,值是undefined(表示未初始化),赋值操作会被留在原地等待执行阶段
- 来一个例子:
console.log(name); //undefined
var name = 'aman';
//相当于
var name;
console.log(name); //undefined
name = 'aman';
- 再来一个例子:
var name = 'aman1';
function getName(){
console.log(name ); //undefined
var name = 'aman2';
console.log(name ); //aman2
}
getName();
//相当于
var name = 'aman1';
function getName(){
var name;
console.log(name ); //undefined
name = 'aman2';
console.log(name ); //aman2
}
getName();
2. 函数声明提升
定义函数有两种方式:
- 函数声明
- 函数表达式
1. 函数声明: 函数声明提升会在编译阶段把声明和函数体整体都提前到执行环境顶部,所以我们可以在函数声明之前调用这个函数
foo(); //foo
function foo(){ console.log('foo') }
2. 函数表达式:其实就是变量声明的一种,声明操作会被提升到执行环境顶部,并赋值undefined。赋值操作被留在原地等到执行。
foo(); //报错:TypeError: foo is not a function
var foo = function(){ console.log('foo') }
//相当于
var foo;
foo();
foo = function(){ console.log('foo') }
3. 控制语句
Js中使用函数级作用域,不存在块级作用域。所有普通块中的声明都会被提升到顶部,所以控制语句对声明的控制就显得完全没有效果
if(false){
var name = 'aman';
}
console.log(name); //undefined
//相当于
var name;
if(false){
name = 'aman';
}
console.log(name); //undefined
4. 函数优先
提升操作会优先进行函数的声明
函数会首先被提升然后才是变量,重复的变量声明会被忽略,只剩下赋值操作,多个函数声明可以进行
声明的顺序是这样的:
- 找到所有的函数声明,初始化函数体,如有同名的函数则会进行覆盖
- 查找变量声明,初始化为undefined,如果已经存在同名的变量,就什么也不做直接略过
foo(); //b
function foo() { console.log("a"); }
function foo() { console.log("b"); }
var foo = 100;
console.log(foo); //100
//相当于
//编译阶段:
function foo() { console.log("a"); }
function foo() { console.log("b"); } //覆盖上边的声明
// var foo; //忽略
//执行阶段:
foo(); //b
foo = 100;
console.log(foo); //100
注意: 尽量避免在控制语句内使用函数表达式声明函数,如下
一个特别的例子
console.log(foo); // (早期的浏览器会输出 b,现在是 undefined)
var a = true;
if (a) {
function foo() { console.log("a"); }
} else {
function foo() { console.log("b"); }
}
网友评论