原文连接javascript
先来看个例子:html
console.log(a); // undefined var a = 2; console.log(a); // 2
为何是这样的结果呢?这是由于 JavaScript 代码在执行以前会有一个 预解析
阶段,在这个阶段,解释器会将全部 变量声明
和 函数声明
提高到他们各自的做用域顶部。java
注:变量声明提高只是预解析阶段的一部分行为!express
若是变量在函数体内声明,它的做用域是函数做用域(function-level scope)。不然,它就是全局做用域。segmentfault
继续上面的例子,由于这个预解析阶段,上面的代码会被解释器预解析成下面的代码:函数
var a; console.log(a); // undefined a = 2; console.log(a); // 2
在 ES6 以前,一般经过
var
来声明一个变量,可是 ES6 发布后,又新添了2个关键字来声明一个变量:let
和const
。spa
var
声明了一个变量,这个变量的做用域是当前执行位置的上下文:一个函数的内部(声明在函数内)或者全局(声明在函数外)code
let
声明了一个块级域的局部变量,而且它声明的变量只在所在的代码块内有效htm
const
声明了一个只读的块级域的常量,而且它声明的常量也只在所在的代码块内有效blog
{ // 代码块 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命令,这个区块对这些命令声明的变量,从一开始就造成了封闭做用域。凡是在声明以前就使用这些变量,就会报错。
提高(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 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); // ?
第二题的解析请看 这里