JavaScript中的各种变量提升(Hoisting)

首先纠正下,文章标题里的 “变量提升” 名词是随大流叫法,“变量提升” 改为 “标识符提升” 更准确。因为变量一般指使用 var 声明的标识符,JS 里使用 function 声明的标识符也存在提升(Hoisting)。

JS 存在变量提升( Hoisting ),这个的设计其实是低劣的,它允许变量 不声明就可以访问 , 或声明在后使用在前 。新手对于此则很迷惑,甚至许多使用JS多年老手也比较迷惑。但在 ES6 加入 let/const 后,变量Hoisting 就不存在了。

一、 变量未声明,直接使用

function test() {
alert(notDefined);
}
test(); // ?

报错是自然的

JavaScript中的各种变量提升(Hoisting)

二. 变量声明在末尾

function test() {
alert(declaredButNotAssigned); // undefined
var declaredButNotAssigned;
}
test();

输出 undefined, 结果比上例好一些,没有报错,代码可以运行,但变量值可能不是程序员所期望的。

三、 变量声明在末尾,同时给变量赋值

function test() {
alert(declaredAndAssigned); // undefined
var declaredAndAssigned = 1;
}
test();

结果和 二 相同, 很明显, 并不会因为赋值了 就输出 1。

二、三 都发生了变量提升( Hoisting ),简单定义

变量提升(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里可以如此,见二、三。

因此,有类 C语言 经验的程序员,都很清楚变量需要 先声明后使用 ,不然会报错。而到了JS里,有 变量提升 现象,可以 先使用后声明 ,C 的经验用到 JS 里迷惑便出现了。

四、 函数表达式也存在变量提升

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,表示无可用性。

二、三、四 都是使用 var 声明的变量,JS 里函数声明也会存在提升(Hoisting),只是这个 “变量” 比较特殊,它是一个 function 类型(可以作为函数、方法或构造器)。它的名字(标识符)也会提升到当前作用域的顶部。

五、函数声明的名也会提升到当前作用域顶部

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,变量提升(Hoisting)就不复存在了。

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声明的就会报错了。

上一篇:HOWTO install Oracle 11g on Ubuntu Linux 12.04 (Precise Pangolin) 64bits


下一篇:div动态加载页面