转自:https://www.cnblogs.com/Gary-Guoweihan/p/6251870.html
Js 变量声明提升和函数声明提升
Js代码分为两个阶段:编译阶段和执行阶段
Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容
包括变量声明(var a)和函数声明(function a(){})在内的所有声明都会在代码被执行前的编译阶段首先被处理
过程就好像变量声明和函数声明从他们代码中出现的位置被移动到执行环境的顶部,这个过程就叫做提升
只有声明操作会被提升,赋值和逻辑操作会被留在原地等待执行
变量声明
Js编译器会把变量声明看成两个部分分别是声明操作(var a)和赋值操作(a=2)
声明操作在编译阶段进行,声明操作会被提升到执行环境的顶部,值是undefined(表示未初始化)
赋值操作会被留在原地等待执行阶段
1 var a = 2; 2 3 function foo() { 4 console.log(a); //undefined 5 var a = 10; 6 console.log(a); //10 7 } 8 9 foo(); 10 11 // 相当于 12 13 var a = 2; 14 15 function foo() { 16 var a; 17 console.log(a); //undefined 18 a = 10; 19 console.log(a); //10 20 } 21 22 foo();
函数声明
定义函数有两种方式:函数声明和函数表达式
函数声明提升会在编译阶段把声明和函数体整体都提前到执行环境顶部,所以我们可以在函数声明之前调用这个函数
函数表达式,其实就是变量声明的一种,声明操作会被提升到执行环境顶部,并赋值undefined。赋值操作被留在原地等到执行。
1 // 函数声明 2 3 foo(); //100 4 5 function foo(){ 6 console.log(100); 7 }
1 // 函数表达式 2 baz(); // TypeError: baz is not a function 3 4 var baz = function(){ 5 console.log(200); 6 } 7 8 //相当于 9 10 var baz; 11 12 baz(); 13 14 baz = function() { 15 console.log(200); 16 };
控制语句
Js中使用函数级作用域,不存在块级作用域。所有普通块中的声明都会被提升到顶部,所以控制语句对声明的控制就显得完全没有效果
1 if (false) { 2 var a = 10; 3 } 4 5 console.log(a); //undefined 6 7 // 相当于 8 9 var a; 10 if (false) { 11 a = 10; 12 } 13 14 console.log(a) //undefined
奇怪的函数声明
1 console.log(a); //undefined 2 3 if (false) { 4 function a() { 5 console.log(100); 6 } 7 } 8 9 a(); //TypeError: a is not a function 理论上应该是100
奇怪吧??函数提升发生在所有代码执行之前,所以尽管a函数的定义过程写在了if分支中,但是理论上,它是不会影响函数声明提升的
在新版本的浏览器中会出现此问题,旧版本的浏览器中会在控制台中打印出100
这也提醒了我们尽量不要在控制语句中进行声明,会造成很多无法预知的bug
函数优先
提升操作会优先进行函数的声明
函数会首先被提升然后才是变量,重复的变量声明会被忽略,只剩下赋值操作,多个函数声明可以进行覆盖
声明的顺序是这样的:
1. 找到所有的函数声明,初始化函数体,如有同名的函数则会进行覆盖
2. 查找变量声明,初始化为undefined,如果已经存在同名的变量,就什么也不做直接略过
1 // 1 2 foo(); //200 3 4 function foo() { 5 console.log(100); 6 } 7 8 function foo() { 9 console.log(200); 10 } 11 12 // 2 13 console.log(foo); //function foo(){...} 14 15 function foo(){ 16 console.log(200); 17 } 18 var foo = 100;
一个小例子
先来看个例子:
console.log(a); // undefined
var a = 2;
console.log(a); // 2
为什么是这样的结果呢?这是因为 JavaScript 代码在执行之前会有一个 预解析
阶段,在这个阶段,解释器会将所有 变量声明
和 函数声明
提升到他们各自的作用域顶部。
注:变量声明提升只是预解析阶段的一部分行为!
如果变量在函数体内声明,它的作用域是函数作用域(function-level scope)。否则,它就是全局作用域。
继续上面的例子,因为这个预解析阶段,上面的代码会被解释器预解析成下面的代码:
var a;
console.log(a); // undefined
a = 2;
console.log(a); // 2
变量的声明
在 ES6 之前,通常通过
var
来声明一个变量,但是 ES6 发布后,又新添了2个关键字来声明一个变量:let
和const
。
-
var
声明了一个变量,这个变量的作用域是当前执行位置的上下文:一个函数的内部(声明在函数内)或者全局(声明在函数外) -
let
声明了一个块级域的局部变量,并且它声明的变量只在所在的代码块内有效 -
const
声明了一个只读的块级域的常量,并且它声明的常量也只在所在的代码块内有效
{ // 代码块
var a = 1;
let b = 2;
const c = 3;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
}
console.log(a); // 1
console.log(b); // 报错,ReferenceError: b is not defined(…)
console.log(c); // 未执行, 因为上面语句出错,所以这条语句不再执行,如果上面的语句不报错,那么这里就会报错
(function (){
var a = 1;
let b = 2;
const c = 3;
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
})(); // 为了方便,这里使用了自执行函数
console.log(a); // 报错,ReferenceError: a is not defined(…)
console.log(b); // 未执行
console.log(c); // 未执行
-
let
const
不像var
那样会发生“变量提升”现象。
console.log(a); // 报错,ReferenceError: a is not defined
let a = 2;
console.log(a); // 待执行
console.log(a); // 报错,ReferenceError: a is not defined
const a = 2;
console.log(a); // 待执行
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
变量声明提升(Variable hoisting)
提升(hoisting)影响了变量的生命周期,一个变量的生命周期包含3个阶段:
声明 - 创建一个新变量,例如 var myValue
初始化 - 用一个值初始化变量 例如 myValue = 150
使用 - 使用变量的值 例如 alert(myValue)
当代码按照这三个步骤的顺序执行的时候,一切看起来都很简单,自然。
-
变量提升的部分只是变量的声明,赋值语句和可执行的代码逻辑还保持在原地不动
console.log(a); // undefined
var a = 111;
function fun(){
console.log(a); // undefined
var a = 222;
console.log(a); // 222
}
fun();
console.log(a); // 111
// --------------
//变量提升后
function fun(){
var a;
console.log(a); // undefined
a = 222;
console.log(a); // 222
}
var a;
console.log(a); // undefined
a = 111;
fun();
console.log(a); // 111
-
在基本的语句(或者说代码块)中(比如:
if语句
、for语句
、while语句
、switch语句
、for...in语句
等),不存在变量声明提升
var a = "aaa";
{
console.log(a); // aaa
var a = "bbb";
}
console.log(a); // bbb
//---------------
var a = "aaa";
if (true) {
console.log(a); // aaa
var a = "bbb";
}
console.log(a); // bbb
//---------------
var a = "aaa";
for(let x in window){
console.log(a); // aaa
var a = "bbb";
break;
}
console.log(a); // bbb
函数声明提升(Function Hoisting)
函数声明(function declarations) 和 函数表达式(function expressions)在语法上其实是等价的,但是有一点不同,就是 JavaScript 引擎 加载他们的方式不一样。简单讲,就是函数声明会被提升到其作用域顶部,而函数表达式不会。
-
函数声明会提升,但是函数表达式的函数体就不会提升了
fun(); // hello
function fun(){
console.log("hello");
}
// --------------
// 提升后
function fun(){
console.log("hello");
}
fun(); // hello
fun(); // 报错,TypeError: fun is not a function
var fun = function(){
console.log("hello");
};
// --------------
// 提升后
var fun;
fun(); // 报错,TypeError: fun is not a function
fun = function(){
console.log("hello");
};
当函数表达式的函数不再是匿名函数,而是一个有函数名的函数时,会发生什么?
foo(); // 报错,TypeError "foo is not a function"
bar(); // 有效的
baz(); // 报错,TypeError "baz is not a function"
spam(); // 报错,ReferenceError "spam is not defined"
// anonymous function expression ('foo' gets hoisted)
var foo = function () {};
// function declaration ('bar' and the function body get hoisted)
function bar() {};
// named function expression (only 'baz' gets hoisted)
var baz = function spam() {};
foo(); // 有效的
bar(); // 有效的
baz(); // 有效的
spam(); // 报错,ReferenceError "spam is not defined"
-
如果一个变量和函数同名,函数声明优先于变量声明(毕竟函数是 JavaScript 的第一等公民),并且与函数名同名的变量声明将会被忽略。
fun(); // 输出的结果为111
function fun(){
console.log(111);
}
var fun = function(){
console.log(222);
}
fun(); // 输出的结果为222
// --------------
// 提升后
function fun(){
console.log(111);
}
fun(); // 输出的结果为111
fun = function(){ // 重新定义了变量 fun
console.log(222);
}
fun(); // 输出的结果为222
-
如果定义了相同的函数变量声明,后定义的声明会覆盖掉先前的声明,看如下代码:
foo(); //输出3
function foo(){
console.log(1);
}
var foo = function(){
console.log(2);
}
function foo(){
console.log(3);
}
练习
-
alert(foo) 的值是多少?
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
alert(foo); // ?
}
bar();
-
alert(a) 的值是多少?
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
alert(a); // ?
第二题的解析请看 这里
参考资料
-
【文章】JavaScript Scoping and Hoisting(推荐)
-
【文章】JavaScript变量提升