美文网首页前端Web前端之路
变量声明中的变量提升(var hoisting)

变量声明中的变量提升(var hoisting)

作者: 唯泥Bernie | 来源:发表于2017-05-10 01:20 被阅读302次

今天讲讲变量声明和变量提升(var hoisting)。有一类题目会问你在变量声明前去获取这个变量值,会获取到什么值或者产生什么情况。我以下面的示例代码来说明下,当我们运行一个函数时发生了什么?(为了言简意赅,我简化了规范中的步骤,只做一个大概的说明,详情请参考 ES6 规范

var foo1 = 'foo1';
var foo2 = 'foo2Outer';

function exampleFunc() {
  console.log( foo1 ); // foo1
  console.log( foo2 ); // undefined
  console.log( bar ); // undefined
  console.log( varFunc ); // undefined
  console.log( expFunc ); // function expFunc() {alert( 'second' )}
  console.log( localVarUndefined );
  // Firefox -> ReferenceError: can't access lexical declaration `localVarUndefined' before initialization
  // Chrome -> Uncaught ReferenceError: localVarUndefined is not defined
  console.log( localVar ); 
  // Firefox -> ReferenceError: can't access lexical declaration `localVar' before initialization
  // Chrome -> Uncaught ReferenceError: localVar is not defined

  var foo2 = 'foo2Inner'
  var bar = 'bar';
  var varFunc = function() {
  }
  function expFunc() {
    alert( 'first' );
  }
  function expFunc() {
    alert( 'second' );
  };
  var expFunc;
  /* ----- let declared variable ---- */
  let localVarUndefined;
  let localVar = 'localVar';

  console.log( localVarUndefined ); // undefined
  console.log( localVar ); // localVar
}

exampleFunc();

exampleFunc 函数被调用后发生的步骤:

  1. 生成执行上下文和作用域并做一定的初始化。(如果对执行上下文是什么不了解的话,详见《什么是作用域和执行上下文》。初始化过程还涉及 this 的初始化,详见《function作为构造函数和非构造函数调用的区别》
  2. 对形式参数进行初始化(这篇我们就先不具体解释这步)。
  3. 遍历函数体,寻找声明的变量,并按一定的规则生成这些变量放在作用域中(注意这里仅仅是生成变量,忽略等号后面赋值语句,即使声明和赋值写在一起)。
  4. 按程序逻辑从上到下运行函数体。

接下来我们逐一分析示例代码中的各种情况:

  • foo1
    变量 foo1 在函数中并没有声明,所以这里只涉及变量寻值的问题,所以取值就是全局变量 foo1。

  • foo2,bar
    这两个变量按上面函数被调用的步骤所示,在运行函数体之前两个变量已经生成,去操作并不会产生运行时错误。又由于运行 console 的时候还没有运行到下方 var xxx = yyy 的赋值语句,所以此时打印出来的是 undefined。(对!你没有想错,当运行函数体时,var xxx = yyy 其实已经变成单纯的赋值语句了。)

  • varFunc,expFunc
    varFunc 虽然是一个函数,但是是用 var 关键字声明的,其实可以和 foo2,bar 归为一类,所以打印出来也是 undefined。expFunc 既有作为函数声明,又有用 var 声明,这里就涉及到上述步骤3中的子步骤了:

    1. 先搜寻函数声明,并以函数名创建变量,如果有多个同名函数声明,取最后一个为准。
    2. 再搜寻 var 声明,如果查询到 var 声明的变量已经在上一步函数证明过程中占用了,则不做任何操作。如果没有占用再创建一个变量。
    3. 对第一步中的函数(名)变量进行函数初始化。

所以 expFunc 在函数体运行前,变量创建过程中变成了 function expFunc() {alert( 'second' )}。当然如果函数体运行后再对 expFunc 进行赋值后,其又可以变成其他:

function exampleFunc() {
  console.log( expFunc ); // function expFunc() {alert( 'second' )}
  
  function expFunc() {
    alert( 'first' );
  }
  function expFunc() {
    alert( 'second' );
  };
  console.log( expFunc ); // function expFunc() {alert( 'second' )}
  var expFunc = 'str';
  console.log( expFunc ); // str
}

exampleFunc();

<br />
到这里为止,我们看到了变量声明在 ES6 之前的大致行为,这就是所谓的变量提升(var hoisting):函数运行的过程像是把函数体内所有变量的声明(创建)都提到了函数的最前面。

  • localVarUndefined,localVar
    但是 ES6 中加入了 let 局部变量的声明,它的行为就和 var 声明不同了,那我们来看下区别在哪里?其实 exampleFunc 函数被调用后发生的步骤一点都没有变化,依然会在函数体运行前进行变量的搜寻和创建。但是当遇到 let 声明的变量,规范规定在这个变量创建后是无法获取到的,直到 LexicalBinding 阶段。那什么是 LexicalBinding 阶段呢?就是函数体运行到 let xxx [= yyy]; 这条语句时。换句话说就是当函数开始执行,然后逐行运行到 let 声明(或者外加赋值)语句前,let 声明的变量虽然被创建了,但是程序获取不到,所以就会抛出 ReferenceError。这段无法获取变量的阶段称之为 TDZ(Temporal Dead Zone)。如此就能解释 localVarUndefined,localVar 的打印结果了。

相关文章

  • [深入理解ES6]块级绑定

    var声明与变量提升 变量提升(hoisting):使用var关键字声明的变量,无论声明位置在何处,都会被视为声明...

  • 变量声明中的变量提升(var hoisting)

    今天讲讲变量声明和变量提升(var hoisting)。有一类题目会问你在变量声明前去获取这个变量值,会获取到什么...

  • ES6学习-块级作用域绑定

    var声明及变量提升(Hoisting)机制 在函数作用域通过var声明的变量,无论在哪里声明都会被当成作用域顶部...

  • 深入理解ES6-块级作用域绑定

    var声明及变量提升(Hoisting)机制 在函数作用域或全局作用域钟通过关键字var声明的变量,都会被当成在当...

  • 块级绑定

    使用 var 声明的变量,会提升到当前作用域的最顶部或者是全局作用域,叫做变量提升 Hoisting 。 变量没声...

  • 块级作用域绑定

    var声明及变量提升(Hoisting)机制 在函数作用域或全局作用域中通过关键字var声明的变量,无论实际上是在...

  • 变量提升(hoisting)

    变量提升(hoisting) 变量提升,提升的是声明而不是赋值所有的变量的声明语句,都会被提升到代码的头部,这就叫...

  • 理解 JS 作用域链与执行上下文

    贫道,感觉,JS的坑,不是一般地大。 变量提升: 变量提升( hoisting )。 我可恨的 var 关键字: ...

  • 变量声明 var、let、const

    1. var变量声明提升 var声明的变量,发生变量声明提升;即:变量的声明被提升到该作用域的顶部 let 和 c...

  • ES6之 let,const

    var 声明与变量提升 var是js的变量声明语句,使用var声明的变量,无论其声明的实际语句在何处,都会被提升到...

网友评论

    本文标题:变量声明中的变量提升(var hoisting)

    本文链接:https://www.haomeiwen.com/subject/ishwtxtx.html