blog icon indicating copy to clipboard operation
blog copied to clipboard

揭开Hoisting面纱

Open nanyang24 opened this issue 6 years ago • 0 comments

揭开Hoisting面纱

简单来说,就是在JS的作用域里,变量不一定要先定义后使用,JS对变量的处理规则和我们在其它编程语言中的经验有些差别。

JS是一门编译型语言

对于var a= 2这样的语句来说,JS引擎是分成两部分处理的:

  • 第一部分是声明变量:var a;,这部分可以理解成是在编译期完成的,它确定了变量的作用域;
  • 第二部分是赋值:a = 2;,编译器生成表示赋值的指令,由JS引擎在运行时完成;

在一个作用域里,被编译器提前的,都是变量的声明,而之后要执行的代码(对我们的例子来说,也就是赋值),则会保留在原地。这就是hoisting的含义。 这也很好理解,如果hoisting可以改变代码的执行顺序,我们就很难写出工作正常的程序了。

foo();

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

执行这段代码,同样会得到undefined结果。这里,有两个原因:

  • 第一、函数声明同样得到了提升,表明foo是一个函数,因此,在定义foo之前,我们可以直接调用它;
  • 第二、Hoisting是按作用域发生的,声明只会提升到每个作用域的顶端

于是,上面这段代码会被编译器处理成这样:

// Hoisting in global scope
function foo() {
    // Hoisting in function scope
    var a;

    console.log(a);
    a = 2;
}

foo();

可以看到,hoisting一共发生了两次,分别在全局以及函数作用域内。因此,执行foo的时候,会看到undefined。

最后,我们再来看一个无法提升的例子:

bar();

var bar = (function foo() {
    console.log(a);
    var a = 2;
});

所以就函数来说,函数声明是可以提升的,但是函数表达式不行。

变量提升的优先级

既然,函数和变量声明都有可能提升,当存在同名的变量和函数时:

  • 函数声明优先提升,多个同名函数的声明后者覆盖前者
foo(); // 1

var foo;

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

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

第一个调用的结果是什么呢?当JS引擎编译的时候,变量foo和函数foo都可以提升,但是函数foo会优先得到提升,于是我们就可以在控制台看到结果1了。

nanyang24 avatar Apr 06 '18 07:04 nanyang24