Day3: javascript 变量提升

97 阅读4分钟

javascript变量提升

在同一作用域定义函数与变量

先声明函数在声明变量


 function b() {
    console.log(b)
 }
 var b = 34
 b(); // Uncaught TypeError: b is not a function

在这种情况我们很容易想到,此时 b = 34 ,这是按照我们正常的思路也说 先function 声明函数 b 在被var b 替换 , 这是按照我们正常的思维是如此, 那我们换个位置呢,答案还是我们所想的那样么?

接下来我们看看下面的例子:

var b = 34
function b() {
    console.log(b)
}
console.log(b) // b = 34
b()  // TypeError: b is not a function

从上面的例子我们可以看到此时的函数已经变成了 b = 34 , 按照上面的思维我们可能会觉得,此时应该是 function b 会取代前面声明的var b 然而结果并不是, 那我们来调试一下看一下什么情况

函数声明变量提升

从上面的调试我们可以看出在var b = 34 定义之前 函数 b 已经被声明 ,说明在同一作用域下函数声明 会 提到var变量声明前

函数声明变量提升

const / let的变量提升

const / let 到底存不存在 变量提升呢? 我们来调试看一看:

console.log("--- const 变量提升 ---")
console.log(a)  
const a = 12

const / let的变量提升

const / let的变量提升

从上面的调试中我们可以看出const 的确会造成类似于变量提升的现象 ,并且我们可以看出我们在const 定义之前使用 变量 是会导致报错 ReferenceError: Cannot access 'a' before initialization 出现这种情况是因为 出现了暂时性死区 下面我们来看看暂存性死区。

暂时性死区

Temporal Dead Zone (TDZ) 翻译为中文即为 暂时性死区

先看一段 MDN 上关于暂时性死区的定义

let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as “hoisting”. Unlike variables declared with var, which will start with the value undefined, let variables are not initialized until their definition is evaluated. Accessing the variable before the initialization results in a ReferenceError. The variable is in a “temporal dead zone” from the start of the block until the initialization is processed.

文档第一句话就明确指出 let 存在变量提升,但是与 var 不同的是,var 的变量提升的同时会默认赋值为 undefined. 而let仅仅发生了提升而已,并不会赋任何值给变量,在显式赋值之前,任何对变量的读写都会导致ReferenceError的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。

image.png

暂时性死区

暂时性死区深入探讨

到目前为止,我们对暂时性死区的理解已经够用了,下面是从 ECMA262 let/const 标准 层面来再稍微深入的探讨一下这个问题。

image.png

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

先看一下文档中关于 let const 的一段描述:

Lexical Environment:词法环境 Lexical Binding: 词法绑定

试着把文档中的关键句翻译成人话:

The variables are created when their containing Lexical Environment is instantiated…

当程序控制流程运行到特定作用域(scope ≈ Lexical Environment) 时:即模块,函数,或块级作用域。在该作用域中代码真正执行之前,该作用域中定义的letconst 变量会首先被创建出来。正是所谓的变量提升!

…but may not be accessed in any way until the variable’s LexicalBinding is evaluated…

这其实揭示了暂时性死区的原理: 在 let/const 变量被赋值(LexicalBinding)以前是不可以读写的。

If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

如果 let/const 变量未被显式赋值(Initializer),默认值即为 undefined

image.png

出现暂时性死区的例子

变量的自我赋值

image.png

image.png

image.png

const m 时将变量提升至 fun函数的最顶端, 导致 m + 12 中的m 出现暂时性死区

函数默认值产生的暂时性死区

function fun1(a, b = a) {
    console.log(b)  // 2
}

fun1(2, undefined)

function fun2(a = b, b) {
    console.log(a) // ReferenceError: Cannot access 'b' before initialization
}

fun2(undefined, 2)

image.png

fun1: b 在赋值时需要获取 a 的值,在此之前 a 已经被显式赋值为2,不存在 TDZ 的问题。

fun2函数的参数列表可以看作一个 scope,且参数是从左向右解析的。当 a 在赋值时试图获取 b 的值,而此时 b 出于TDZ状态,因此程序报错。