前言
hoist
vt.升起,提起;
vi.被举起或抬高;
n.起重机,升降机; 升起; <俚>推,托,举;
这篇文章不讲英语,但是对于某些英语单词找不到很好的翻译,一上来就列出“hoist”这个单词的释义是为了让大家有个准备,我在这里将此单词翻译为“提前”,是为了解释 JavaScript 语言中很“古怪”的一个特性。
变量声明“被提前”
JavaScript 的语法和 C 、Java、C# 类似,统称为 C 类语法。有过 C 或 Java 编程经验的同学应该对“先声明、后使用”的规则很熟悉,如果使用未经声明的变量或函数,在编译阶段就会报错。然而,JavaScript 却能够在变量和函数被声明之前使用它们。下面我们就深入了解一下其中的玄机。
运行下边代码立马就报错,不过,这也正是我们期望的,因为 x 变量根本就没有定义过嘛!
console.log(x);
[Web浏览器] "Uncaught ReferenceError: x is not defined"
再来看看下面的代码,x 变量是在调用语句后面定义,为什么居然输出的是 undefined呢?!
这其实是 JavaScript 解析器搞的鬼,解析器将当前作用域内声明的所有变量和函数都会放到作用域的开始处,但是,只有变量的声明被提前到作用域的开始处了,而赋值操作被保留在原处。
上述代码对于解析器来说其实是如下这个样子滴,备注里是重点。
这就是为什么上述代码不报异常的原因!变量和函数经过“被提前”之后,x变量其实就被放在了调用函数的前面,根据 JavaScript 语法的定义,已声明而未被赋值的变量会被自动赋值为 undefined ,所以,打印 x变量的值就是 undefined
console.log(x); var x=123; /* JavaScript 解析器将上面这个变量相当于下边的代码: var x; //声明被提前到作用域开始处了! console.log(x); x=123; //赋值操作还在原地! */
[Web浏览器] "undefined"
其实这种影响还蛮烦人的,比如:
我们先声明了一个变量 x ,我们的本意是希望在第一次打印 name 变量时能够输出全局范围内定义的 x 变量,然后再在函数中定义一个局部 x 变量覆盖全局变量,最后输出局部变量的值。可是第一次输出的结果和我们的预期完全不一致,原因就是我们定义的局部变量在其作用域内被“提前”了,也就是变成了如下形式:
var x=123; window.onload=function(){ console.log(x); var x=456; console.log(x); /* var x; console.log(x); x=456; console.log(x); */ }
[Web浏览器] "undefined"
[Web浏览器] "456"
后语
由于 JavaScript 具有这样的“怪癖”,所以你会看到很多编码指南建议大家将变量声明放在作用域的最上方,这样就能时刻提醒自己注意了。