因为javascript没有块级作用域(即if, for包裹的区域)的概念,在函数和变量的定义和使用中,可能会出现令人迷惑的结果。本文就js作用域和变量提升这两个话题,做一番讲解。
PS: ES6标准引入了let关键字,用它声明的变量,具有块级作用域。并且在ES6中,花括号{}内部就是一个块级作用域。
在Javascript中,名称(变量或函数)进入作用域的基本方式有以下4种:
1. 所有作用域内,都存在 this 和 arguments 这两个变量
2. 给函数指定的参数,参数名会自动包含在函数的作用域内
3. 函数的声明。如 function X() {}
4. 变量的声明。如 var Y;
重点来了,这4种类型的名称,会按照上述的优先级,被javascript引擎移动到它们所在的作用域(如函数内)的顶端。这就是所谓的“变量提升”。
下面让我们用实例讲解上面这段话的含义。
首先是变量声明。下面的函数A()其实会被js引擎解释为函数B()的形式:
function A() {
bar();
var x = 1;
}
function B() {
var x;
bar();
x = 1;
}
由这个特性我们可以得出一个推论:给变量声明+赋值的代码即使在逻辑上执行不到,也不会妨碍变量被声明,只不过无法赋值而已。
比如下面这俩函数是一样的:
function A() {
if (false) {
var x = 1;
}
return;
var y = 1;
}
function B() {
var x, y;
if (false) {
x = 1;
}
return;
y = 1;
}
也就是说,即使在函数A()里,x和y这两个变量也是存在的,只不过因为if和return的缘故,无法被赋值为1罢了。
接着是函数声明。和变量声明不同,函数声明的提升,是连同函数体一起提升了。当然,仅限于function X() {}这种写法的函数。而var X = function() {}这种,本质上是声明了一个变量,并把一个匿名函数给这个变量赋值,所以只会把变量名提升。如下示例:
function test() {
A(); // 报错 TypeError "A is not a function"
B(); // 没问题,打印出"this will run!"
var A = function () { // 本质是变量声明 + 赋值
alert("this won't run!");
}
function B() { // 函数声明
alert("this will run!");
}
}
test();
上述的4个优先级还会造成一个现象:因为一旦一个名称被声明,后面再重复声明是无效的,所以同一个作用域内的同名变量和函数,函数的声明永远会覆盖掉变量的声明。结合变量提升的原理,你应该知道是怎么回事了吧?当然,只是声明部分被覆盖,赋值部分还是会被执行。下面的例子可以清楚地看到这一点:
alert(typeof X); // "function"
var X = 1;
function X() {}
alert(typeof X); // "number"
不过也有特殊情况。其一,就比如作用域里默认生成的那个arguments变量。虽然把它放在优先级1里,但是实际上它真正的声明位置是在函数的参数之后,即优先级2和3之间的位置。因此如果在定义函数时,把函数的某个参数命名为arguments,那么这个参数就会覆盖掉默认的arguments,例如:
function X(param) {
alert(arguments);
}
function Y(arguments) {
alert(arguments);
}
X(1); // "[Object Arguments]"
Y(1); // "1"
其二,this也不能作为标识符使用,如作为函数的参数及函数名,否则会报SyntaxError,因此可以说this是这些名称里面优先级最高的。其三,如果一个函数拥有多个同名参数,那么最后那个参数会进入函数的内部作用域。这与在函数内反复声明变量时的结果正好相反。例如:
function X(a, a, a, b, a) {
alert(a);
}
X(1); // "undefined"
X(1, 2, 3, 4, 5) // "5"
最后一个要点,是关于用命名函数来给变量赋值,那个命名函数是否会被提升的问题。答案是NO。
如下面的例子:
A(); // TypeError "A is not a function"
B(); // 没问题
C(); // TypeError "C is not a function"
X(); // ReferenceError "X is not defined"
var A = function () {}; // 变量声明+赋值,仅仅A这个名称被提升
function B() {}; // 函数声明。整个函数被提升
var C = function X() {}; // 变量声明+赋值,仅仅C这个名称被提升
A(); // 没问题
B(); // 没问题
C(); // 没问题
X(); // ReferenceError "X is not defined"
可以看到,X()不仅没有被提升,它根本没有进入作用域!
Reference:
[1] http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html
本文为悠然居(https://wordpress.youran.me/)的原创文章,转载请注明出处!