背景
因爲ES5的時候沒有塊級作用域,所以ES5規定不能再if這樣的塊中聲明函數,但是爲了兼容各大瀏覽器並沒有嚴格遵守這條規定。
ES6的時候引入了塊級作用域,規定在塊級作用域中聲明函數就相當於使用let來聲明變量一樣。但是又因爲瀏覽器端的兼容問題,標準中說明瀏覽器端的實現可以不完全遵守,有自己的行爲,如下:
- 允許在塊級作用域內聲明函數。
- 函數聲明類似於var,即會提升到全局作用域或函數作用域的頭部。
- 同時,函數聲明還會提升到所在的塊級作用域的頭部。
if (false) {
function a() {}
}
console.log(a);
上面輸出 undefined
這表明了上面兩條,一個是允許在塊級作用域內聲明函數,一個是塊級作用域內聲明類似於var。
if (true) {
console.log(a);
function a() {}
}
輸出函數a,這表明了第三條:函數聲明會提升到所在塊級作用域的頭部。
問題和信息
上面現有的理論並不夠解釋下面的現象:
var a;
{
a = 5;
function a() {}
a = 0;
console.log(a);
}
console.log(a);
chrome最新版輸出的是:第一個console輸出的是0
,第二個console輸出的是5
。
先陳述下依據已知的三條理論得出上面代碼幾個不合理的現象:
- 首先稍微變通就知道如下代碼:
{
function a() {}
}
console.log(a);
這裏打印了a是一個函數,穿透了塊級作用域。上面並沒有解釋這種現象。
a = 0;
的賦值並沒有影響到塊外部的a。
解釋上面兩個問題,還需要額外的信息(信息來自stackoverflow高贊回答,原文在參考鏈接中):
function enclosing() {
{
function compat() {}
}
}
// works the same as
function enclosing() {
var compat₀ = undefined; // function-scoped
{
let compat₁ = function compat() {}; // block-scoped
compat₀ = compat₁;
}
}
上面對塊中的函數聲明引入的額外的概念,更加清晰明瞭的解釋了底層做了什麼。
首先額外引入的信息本身也存在一些問題。少了函數會提升到當前塊作用域頂部,我認爲應該如下修改:
function enclosing() {
var compat₀ = undefined; // function-scoped
{
function compat() {}
let compat₁ = compat; // block-scoped
compat₀ = compat₁;
}
}
這樣就修復了沒有函數提升的問題。
猜想
根據額外補充的知識加上自己的想象力得到如下結果:
var a₀;
{
// 這部分被提升
function a() {}
let a₁ = a;
a₀ = a;
// 這部分被提升END
a₀ = 5;
a₁ = 0;
console.log(a₁);
}
console.log(a₀);
a₀ 和 a₁ 都是a在不同位置的不同分身。