JavaScript的“变量提升”(看了MDN中英文两版后...)

299 阅读5分钟

在抖音刷到了几个变量提升的题后,闲来无事翻了MDN的变量提升的解释,基于国人属性,熟练地切到了中文版,不知道为啥切到英文版,结果发现多了好多东西,有点开到“盲盒”的感觉,记录下。

中文版变量提升

从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。

MDN的这块解释还是挺简短的,主要包含一下内容(简单回顾下):

  1. 强调了var的声明被提升(默认初始化为undefined)
  2. 译者注函数和变量相比,会被优先提升。这意味着函数会被提升到更靠前的位置。

中文版结束。

对的,没了……译者很良心,提了下函数声明的提升优先于变量声明,来个例子当回顾了:

console.log(a)  // f a(){}
var a = 1
console.log(a)  // 1
var a = function () {}
console.log(a)  // f () {}
function a() {}
console.log(a)  // f () {}

var b = 2;
//非严格模式
(function(a, b){
    console.log(a, b) //(f(){}, 2)
    var a = 3, b = 4;
    console.log(a, b) // (3, 4)
    //function a() {}  //注释打开 IFFE第一行打印(f a(){}, 2)
})(a, b)

英文版 Hositing

函数提升

强调了在函数声明前可以使用函数(都知道,略过)的优点,否则你得先声明函数,再执行。

变量提升(有干货)

  1. 强调了js只提升了变量声明,而不是初始化!阐述了即使你在变量声明前初始化,或者在同一行声明并赋值,并不会改变变量提升(中文网言简意赅...)

    从概念上讲,变量提升通常是指解释器将代码声明和初始化分离,并将声明移到代码顶部

var提升

  1. 在使用后声明默认值undefined;在使用后同时声明和初始化依旧如此;
  2. 如果未使用var声明,没有提升作用,在初始化前使用该变量则抛异常;初始化后可正常使用

let/const提升

这里可能和红宝书以及其他地方看到的不一样

let声明的变量不会在作用域中被提升...在解析代码时,JavaScript引擎会注意出现在块后面的let声明,只不过在此之前不能以任何方式引用未声明的变量... ——《JavaScript高级程序设计(第4版)》

mdn英文版翻译归来意思是

letconst声明的变量也会被提升,但与var不同的是,它们不会用默认值(undefined)初始化。如果在初始化之前读取用letconst声明的变量,则会引发异常。

Hoisting works with variables too, so you can use a variable in code before it is declared and/or initialized.

并且强调,重要的是代码的执行顺序,而不是在源文件中写入代码的顺序。只要初始化变量的代码行在读取变量的代码行之前执行,代码就会成功。比如以下代码正常执行:

{
  // TDZ starts at beginning of scope
  const func = () => console.log(letVar); // OK

  // Within the TDZ letVar access throws `ReferenceError`

  let letVar = 3; // End of TDZ (for letVar)
  func(); // Called outside TDZ!
}

并且给到了let的暂时性死区的相关链接(中文版加点油) 趁热打铁,来看下暂时性死区

暂时性死区——Temporal dead zone (TDZ)

针对letconst变量 letconst变量从代码块开始到声明前的这段时间一直处于“暂时性死区”(TDZ),这段时间内任何形式的访问或引用该变量将导致ReferenceError;直到代码执行到声明(和初始化变量,只声明会默认赋值undefined)的那一行后可用。

var不同,var在声明之前访问会返回undefined

temporal ——“暂时性”,是指该区域取决于执行的顺序(时间),而不是代码写入的顺序(位置)。复用上例:

//尽管letvar写入位置在前,但是执行位置或顺序在声明初始化之后,因此代码正常运行
{
  // TDZ starts at beginning of scope
  const func = () => console.log(letVar); // OK

  // Within the TDZ letVar access throws `ReferenceError`

  let letVar = 3; // End of TDZ (for letVar)
  func(); // Called outside TDZ!
}

暂时性死区和词法作用域相结合的时候:

function test() {
  var foo = 33;
  //foo 为33 布尔值为true进入if
  if (foo) {
    //foo被let重新声明为块状作用域变量,但foo+55执行会报错
    //因为此时块状作用域的foo还未完成声明和初始化
    let foo = foo + 55; // ReferenceError
  }
}
test();

同理let n of n.a也不能正常运行。

varlet/const

  • 对于let/const,我们无法使用条件先判断后声明:
    1. 处于暂时性死区的变量使用typeof会报错
    2. 不能使用单独的let/const声明作为块状作用域的主体,因为无法访问到变量
    3. var是函数作用域,可以提升到块状作用域外
  • var可以重复声明变量,let/const不可以,两者也不可以混合重复声明

类的提升

使用类声明定义的类,意味着JavaScript具有对类的引用。但是,默认情况下不初始化类,因此在执行其初始化的行之前使用它的任何代码都会抛出异常。例如:

console.log(Rectangle)  //  ReferenceError
const p = new Rectangle(); // ReferenceError 

class Rectangle {}

函数声明和类声明之间的一个重要区别在于,虽然可以在定义之前出现的代码中调用函数,但必须在构造这些函数之前定义类(所以是不是认为没有提升...文档下就直接给了解释)

类声明:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

函数表达式和类表达式提升

函数表达式和类表达式没有提升。

表达式分别求值为函数或类。它们通常被赋值给一个变量或传递给其他函数,通常是变量声明被提升表达式是它的初始化。因此,直到执行相关的行,表达式才会被求值。

函数表达式:

const getRectArea = function(width, height) {
  return width * height;
};

类表达式:

Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

中对英,缩水好多啊,不过看Cache-Control各个字段以及状态码解析还算是全乎的。

文章基本翻译总结自:developer.mozilla.org/en-US/docs/… 有兴趣可以直接去官网看