如下来自[1]:javascript
在JavaScript中,变量有4种基本方式进入做用域:html
1 语言内置:全部的做用域里都有this和arguments;java
2 形式参数:函数的形参会做为函数体做用域的一部分;程序员
3 函数声明:像这种形式:function foo(){};面试
4 变量声明:像这样:var foo;安全
ECMAScript参考文档中关于做用域和变量提高的描述:
函数
若是变量在函数体类声明,则它是函数做用域。不然,它是全局做用域(做为global的属性)。变量将会在执行进入做用域的时候被建立。块不会定义新的做用域,只有函数声明和程序(译者觉得,就是全局性质的代码执行)才会创造新的做用域。变量在建立的时候会被初始化为undefined。若是变量声明语句里面带有赋值操做,则赋值操做只有被执行到的时候才会发生,而不是建立的时候。this
如下来自[2]:
spa
首先纠正下,文章标题里的 “变量提高” 名词是随大流叫法,“变量提高” 改成 “标识符提高” 更准确。由于变量通常指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提高(Hoisting)。.net
JS 存在变量提高,这个的设计实际上是低劣的,或者是语言实现时的一个反作用。它容许变量不声明就能够访问,或声明在后使用在前。新手对于此则很迷惑,甚至许多使用JS多年老手也比较迷惑。但在 ES6 加入 let/const 后,变量Hoisting 就不存在了。
1、 变量未声明,直接使用
function test() { alert(notDefined); } test(); // ?
报错是天然的
二. 变量声明在末尾
function test() { alert(declaredButNotAssigned); // undefined var declaredButNotAssigned; } test();
输出 undefined, 结果比上例有所改善,没有报错,代码能够运行,但变量值可能不是程序员所指望的。
3、 变量声明在末尾,同时给变量赋值
function test() { alert(declaredAndAssigned); // undefined var declaredAndAssigned = 1; } test();
结果和 二 相同, 很明显,并不会由于赋值了就输出 1。
2、三 都发生了变量提高(Hoisting),简单定义
变量提高: 在指定做用域里,从代码顺序上看是变量先使用后声明,但运行时变量的 “可访问性” 提高到当前做用域的顶部,其值为 undefined ,没有 “可用性”。
这里强调 “代码顺序” 和 “运行顺序”,是由于多数时候咱们写的代码都是顺序执行的,即 “代码顺序” 和 “运行顺序” 是一致的。这也符合人的大脑的思惟过程。好比有过 C语言 经验的程序员
#include <stdio.h> int main() { int x = 1; printf("%d, ", x); // 1 }
两句代码,先声明整数型 x, 再输出。代码顺序和运行顺序是一致的,即正常运行。
若是顺序反过来
#include <stdio.h> int main() { printf("%d, ", x); // error int x = 1; }
此时,编译都不能经过了。但JS里能够反过来写,见2、三。
所以,有类 C语言 经验的程序员,都很清楚变量须要 先声明后使用,否则会报错。而到了JS里,有 变量提高 现象,能够 先使用后声明,C 的经验用到 JS 里迷惑便出现了。
4、 函数表达式也存在变量提高
function test() { alert(func); // undefined var func = function() {}; } test();
但若是想使用这个 func,则无可能
function test() { alert(func); // undefined func(); // 报异常 var func = function() {}; } test();
结果func 是 undefined,调用 func 则会报异常。 在上面的定义中提到了 可访问性 和 可用性 对应以下语句。
可访问性:alert(func),输出 undefined,能够运行,能够访问 func。
可用性: func(), 报异常,不能正常调用 func,表示无可用性。
2、3、四 都是使用 var 声明的变量,JS 里函数声明也会存在提高,只是这个 “变量” 比较特殊,它是一个 function 类型(能够做为函数、方法或构造器)。它的名字(标识符)也会提高到当前做用域的顶部。
5、函数声明的名也会提高到当前做用域顶部
function test() { alert(f1); // function f1(); // "called" function f1() { alert('called'); } } test();
咱们看到,声明 f1 在代码最末,f1 使用在前,alert(f1) 和 f1() 都正常执行,表示 可访问性 和 可用性 都有了。
前面说了,变量提高(Hoisting)没什么用,属于语言的低劣设计,好的习惯仍是 “先声明后使用”。这个特性也会出如今很多大公司面试题里
题1:
// 写出如下代码的运行结果 var a = 1; function fn() { if (!a) { var a = 2; } alert(a); // ? } fn();
题2:
// 写出如下代码的运行结果 var a = 1; function fn() { a = 2; return; function a() {} } fn(); alert(a); // ?
但这一切随着 ES6 的 let/const 到来结束了,ES里除全局变量外,其它都使用 let/const,var 替换成 let 后变量提高就不复存在了。
function test() { alert(declaredButNotAssigned1); // 报异常 alert(declaredButNotAssigned2); // 报异常 alert(func); // 报异常 let declaredButNotAssigned1; let declaredButNotAssigned2 = true; let func = function() {}; } test();
这强制程序员养成好的习惯,变量须要“先声明再使用”,不然报错。
如下摘自MDN的关于let不在发生变量提高的描述
In ECMAScript 6, let does not hoist the variable to the top of the block. If you reference a variable in a block before the let declaration for that variable is encountered, this results in a
ReferenceError, because the variable is in a “temporal dead zone” from the start of the block until the declaration is processed.
用 let 声明变量后,typeof 也再也不安全
if (condition) { alert(typeof num); // Error! let num = 100; }
之前能够用 typeof == ‘undefined’,来判断是否引入了某lib,好比jQuery
// 判断jQuery是否引入了 if (typeof $ !== 'undefined') { // do something }...
jQuery没有引入,$ 没有声明,这句也不会报错而影响到下面的代码执行,但若是是 let 声明的就会报错了。
相关:
Temporal_dead_zone_and_errors_with_let
JavaScript判断变量是否为undefined两种方式差别
参考文档:
[1] Javascript做用域和变量提高, http://blog.csdn.net/sunxing007/article/details/9034253
[2] JavaScript中变量提高是语言设计缺陷, http://www.cnblogs.com/snandy/p/4552078.html