提升

135 阅读3分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

提升

任何声明在某个作用域内的变量,都将附属于这个作用域。

编译器来袭

编译阶段中的一部分工作就是找到所有的声明,并用合适的作用域将它们关联起来。

因此,正确的思路是,包括变量和函数在内的所有的声明都会在任何代码被执行前首先被处理。

**1、**只有声明本身会被提升,而赋值或其他运行的逻辑会留在原地。如果提升改变了代码执行的顺序,会造成非常严重的破坏。

foo();

function foo() {
    console.log(a); //undefined
    var a = 3;
}

foo函数的声明被提升了,因此第一行中的调用可以正常执行。值得注意的是,每个作用域都会进行提升操作。

因此,这段代码实际上可以理解成这样子:

function foo() {
    var a;
    console.log(a); //undefined
    a = 3;
}
foo();

**2、**函数声明会被提升,但是函数表达式不会被提升

foo(); //不是ReferenceError,而是TypeError
var foo = function bar() {
    //...
}

这段程序中的变量标识符foo()被提升并分配给所在作用域(在这里是全局作用域),因此foo()不会导致ReferenceError。但是foo此时没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。foo()由于对undefined值进行函数调用而导致非法操作,因此抛出TypeError异常。

即使是具名的函数表达式,名称标识符在赋值之前也无法在所在作用域中使用:

foo();
bar();
var foo = function bar() {

    //...
}

提升之后:

var foo;
foo(); //TypeError
bar(); //ReferenceError
var foo = function bar() {
    //...
}

函数优先

函数声明和变量声明都会被提升,但是一个值得注意的细节是函数会首先被提升,然后才是变量。

foo(); //1
var foo;

function foo() {
    console.log(1);
};
foo = function () {
    console.log(2);
}

这个代码片段会被引擎理解为以下形式:

function foo() {
    console.log(1);
};
foo(); //1
foo = function () {
    console.log(2);
}

因为函数声明会被提升到普通变量之前,所以var foo 重复声明被忽略

小结

我们习惯于将var a = 2;看作一个声明,而实际上javascript引擎并不这么认为。它将var a 和a = 2当做两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务

这意味着无论作用域中的声明出现在什么地方,都将代码本身被执行前首先进行处理。可以将这个过程形象的想象成所有的声明(变量和函数)都会被“移动”到各自作用域的最顶端,这个过程被称为提升

声明本省会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

要注意避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引发很多危险的问题。