xxleyi / learning_list

聚集自己的学习笔记
10 stars 3 forks source link

JS 变量提升的存在是为了满足什么目的呢? #197

Open xxleyi opened 4 years ago

xxleyi commented 4 years ago

JS 中的「变量提升」可谓众所周知。

但它到底意味什么?尤其是在 ES6 之后,var let const 三者与变量提升的关系又是为何?

归根结底一句话:变量提升的存在是为了满足什么目的呢?

xxleyi commented 4 years ago

我自己的背景是对 Python 更熟,熟到对 Python 中一个知名的错误有过一点点小研究。这个致命错误是 UnboundLocalError: local variable 'xxx' referenced before assignment

{console.log(a); let a;}

这样一段 JS 代码报的错误是 Uncaught ReferenceError: Cannot access 'a' before initialization.

作为对比:

{console.log(a);}

的报错是 ReferenceError: a is not defined.

单看这两个对比,结论很可能是 JS 中 let 不存在变量提升。

别急,再来看一个对比:

let a = 66;
{console.log(a); let a = 88}

的报错也是 Uncaught ReferenceError: Cannot access 'a' before initialization. 而

let a = 66;
{console.log(a);}

却不报错。

如果 JS 中 let 不存在变量提升,那上述错误从哪里来?该怎么解释?用所谓暂时性死区能解释得通吗?


根据新版的错误,用暂时性死区确实很形象,但总感觉差点意思。

xxleyi commented 4 years ago

关键点剖析:

结论:

这里的预计算,指的是「环境」。

xxleyi commented 4 years ago

变量提升存在的意义:

hoisting - Why does JavaScript hoist variables? - Stack Overflow

As Stoyan Stefanov explains in "JavaScript Patterns" book, the hoisting is result of JavaScript interpreter implementation.

The JS code interpretation performed in two passes. During the first pass, the interpreter processes variable and function declarations.

The second pass is the actual code execution step. The interpreter processes function expressions and undeclared variables.

Thus, we can use the "hoisting" concept to describe such behavior.

所以,这是 JS 解释器某个实现造成的。

那,为什么 JS 解释器选择这种实现方案呢?

以我目前的知识水平,先存疑不再深究。

但到这里,可以发现 JS 变量提升和 Python 中的 precompute local variable 的微妙差异:JS 同样会 precompute local variable,但除此之外,还会对某些变量进行提升

再具体点,真正想要提升的是「函数声明」。var 的提升是个历史错误,后来的 let const 有意规避掉了这一点。

xxleyi commented 4 years ago

let in MDN

let allows you to declare variables that are limited to a scope of a block statement, or expression on which it is used, unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope. The other difference between var and let is that the latter is initialized to a value only when a parser evaluates it.

xxleyi commented 4 years ago

JS 中的变量提升是在预计算的基础之上,针对 function 进行提升。

Python 只有预计算,提前使用报错: local variable 'xxx' referenced before assignment

JS 除了 function 的正经提升之外,还有一个 var 的历史性错误,其余的提前使用时,也是报错:Uncaught ReferenceError: Cannot access 'a' before initialization,这和 Python 中的错误一致。

xxleyi commented 4 years ago

在 JS creator 眼里,变量提升主要是为了「函数式编程的方便」,而 var 声明的变量被初始化为 undefined 是个错误。

BrendanEich on Twitter: "@aravind030792 function hoisting allows top-down program decomposition, 'let rec' for free, call before declare; var hoisting tagged along." / Twitter

xxleyi commented 4 years ago

Python 中一个经典的报错:

def something_maybe_true():
    return False

x = 10
def foo():
    if something_maybe_true():
        x = 1
    print(x)

UnboundLocalError: local variable 'x' referenced before assignment

xxleyi commented 4 years ago

JS 中 var 和 let 作用域的不同:

function something_maybe_true() {
  return false;
}

var x = 10;

function foo() {
  console.log(x)
  if (something_maybe_true()) {
    var x = 1;
  }
  console.log(x)
}

output:

undefined
undefined
function something_maybe_true() {
  return true;
}

let x = 10;

function foo() {
  console.log(x)
  if (something_maybe_true()) {
    let x = 1;
  }
  console.log(x)
}

output:

10
10
xxleyi commented 4 years ago

Understanding UnboundLocalError in Python - Eli Bendersky's website

xxleyi commented 4 years ago

Function Hoisting & Hoisting Interview Questions