近來在多個羣裏面看見有人發了一個題,
{ a = 1; function a(){} };console.log(a)
和{ function b(){}; b = 1 };console.log(b)
輸出的是什麼。結果兩個狀況的輸出結果都是代碼塊裏面的第一個,咦,好像和以前所學的變量提高有點不同。咱們下面開始探究一下chrome
本文基於chrome展開研究。前方告警:這是一次無聊的探索記錄瀏覽器
相信大部分人都瞭解了,這裏再重複囉嗦一下。js是解析執行的,變量提高是js中執行上下文的工做方式。變量聲明和函數聲明在編譯階段會被提早。函數
console.log(a); // undefined
a = 1;
console.log(a); // 1
var a;
// 至關於
var a;
console.log(a); // undefined
a = 1;
console.log(a); // 1
複製代碼
意外的全局變量,可是若是不去掉第一句console就會報錯ui
a = 1;
console.log(a); // 1
複製代碼
函數聲明的提高spa
console.log(a); // 打印函數a
function a() {}
// 至關於
function a() {}
console.log(a);
複製代碼
函數聲明的提高大於變量聲明的提高debug
console.log(a);
var a;
function a() {}
// 都是打印函數a
console.log(a);
function a() {}
var a;
複製代碼
注意了,if裏面的聲明也是會提高的,即便沒執行3d
console.log('a' in window); // true
if (false) {
function a(){}
}
複製代碼
生命週期:聲明(做用域註冊一個變量)、初始化(分配內存,初始化爲undefined)、賦值調試
注意:變量提高僅提高聲明,而不提高初始化code
能夠看見,這個題目和通常的變量提高有點套路不同,加了一個花括號。這裏花括號的意思是代碼塊。函數實際上就能夠理解爲「可複用代碼塊」。for循環後面也是跟一個代碼塊、while的後面也是一個代碼塊,表示重複執行該代碼塊裏面的代碼:cdn
while(1) {
// 代碼塊
}
複製代碼
甚至你能夠平白無故地寫一個代碼塊:
{
console.log(123);
};
// 這種寫法,chrome下能夠不加分號,一些其餘的瀏覽器(safari)須要加分號不然報錯
// 爲了穩妥,因此仍是加分號吧
複製代碼
對於var是沒有塊級做用域的,因此下面代碼輸出了2
var a = 1;
{
var a = 2;
};
console.log(a); // 2
複製代碼
而let、const是有塊級做用域的,以下輸出了1
// const是同樣的
let a = 1;
{
let a = 2;
};
console.log(a); // 1
// 注意:若是沒有{}代碼塊,兩個let(const)在同一個做用域裏面會報錯的
// Identifier 'a' has already been declared
複製代碼
debugger;
var a = 1;
{
debugger;
var a = 2;
debugger;
};
debugger;
複製代碼
第一個點,變量提高,var a;
而後由於是直接使用控制檯執行,a也順便變成了window下的a了。script展開也是沒有什麼變化的
想必這是一個很無聊的事情,但咱們仍是再把var換成let看看
第一個點,由於是let,因此global裏面沒有看見a。道理和let a = 1
,window.a
是undefined同樣的,'a' in window === false
第二個點,咱們能夠看見多了一個block,這個表示的是塊級做用域。也能夠看見a是存在變量提高的!!,只是你訪問它會報錯,此時代碼塊裏面,let a = 2
以上的代碼都是暫時性死區。還有一個事情,如今用了let,script展開能夠看見有a了,這個a是代碼塊外面的a
第三個點不用想,block裏面的a確定是2了,而後script的a仍是外面那個
最後又回到了外面,a仍是script的a,沒有block了
有了前面的鋪墊以及一些前面介紹的斷點調試的技巧,咱們開始步入正題。先看{ a = 1; function a(){} }
這個的結果是1,符合常規思惟,函數被提早,而後a賦值1。可是打點看一下,有點不同——第一個點Global裏面的a爲何不是函數而是undefined
// 開始打點
debugger; // Global => a: undefined
{
debugger; // Block => a: function, Global => a: undefined
a = 1;
debugger; // Block => a: 1, Global => a: undefined
function a(){}
debugger; // Block => a: 1, Global => a: 1
};
debugger;// Global => a: 1
複製代碼
先看代碼塊裏面,Block裏面的a仍是和常規的變量提高是同樣的表現:首先函數聲明大於變量聲明,因此第二個點的Block的a是一個函數。接着a被賦值,第三個點a是1,此時Global的a是undefined而不是1。第四個點,Global的a纔是1。
第一個點Global裏面的a爲何不是函數而是undefined,第三個點Global的a爲何是undefined而不是1,並且要在
function a(){}
後面纔開始賦值1?
實際上chrome對於這種狀況的函數聲明提高,最開始也是先undefined的。safari就不同了,不會先undefined,直接function。並且{ a = 1; function a(){} }和{ function a(){}; a = 1 }都是輸出1。在safari下,這種狀況加了代碼塊和沒加是同樣的,至關於直接執行了a = 1; function a(){}
咱們執行一下{ function a(){}; a = 1 }
,發現最後的a竟然是一個function了!!!這個題目答案的表現就是,代碼塊裏面先聲明什麼,最終a的結果就是什麼。問題轉化成爲:爲何外層的a是代碼塊的第一個聲明的a?
// 開始打點
debugger; // Global => a: undefined
{
debugger; // Block => a: function, Global => a: undefined
function a(){};
debugger; // Block => a: function, Global => a: function
a = 1;
debugger; // Block => a: 1, Global => a: function
};
debugger;// Global => a: function
複製代碼
a = 1
對於Global的a無濟於事?{
a = 1;
// 問題1: 此處Global的a爲何是undefined而不是1
function a() {};
};
{
function a() {};
// 問題2: 此處Global的a直接是function了並且a = 1對Global的a無濟於事
a = 1;
};
複製代碼
多個函數聲明,取最後一個
{
function a(){}
function a(b){return 1}
a = 1;
};
// >> function a(b){return 1}
複製代碼
多個賦值,取最後一個
{
a = 1;
a = 2;
function a(){}
};
// >> 2
複製代碼
在代碼塊裏面,function更像一個「賦值語句」
debugger;
{
debugger; // global的a: undefined
function a() { }
debugger; // ac
a = 1;
debugger; // ac
function a(b) { }
debugger; // 1
a = 2;
debugger; // 1
a = 3;
debugger; // 1
function a(c) { }
debugger; // 3
};
複製代碼
能夠看出,在代碼塊裏面,chrome的表現方式有這些特色:
function a(){}
更像一句「賦值語句」。具體爲何呢,大概是瀏覽器的內部對代碼塊的實現方式了咱們能夠試一下,利用這些規律猜一下輸出結果:
{
console.log(a, window.a);
function a() { }
console.log(a, window.a);
a = 1;
console.log(a, window.a);
function a(b) { }
console.log(a, window.a);
a = 2;
console.log(a, window.a);
a = 3;
console.log(a, window.a);
function a(c) { }
console.log(a, window.a);
function a(ca) { }
console.log(a, window.a);
};
複製代碼
{
console.log(a, window.a);
// function a(ca)變量提高,全局a是undefined
function a() { }
console.log(a, window.a);
// function a(ca),全局a是function a(ca),上一句是a函數聲明但又帶function a(ca)提高,「傳遞」出去並賦值
a = 1;
console.log(a, window.a);// 1,function a(){}
function a(b) { }
console.log(a, window.a);
// 1, 1由於上一次賦值是1,1被「傳遞」出去
a = 2;
console.log(a, window.a);
// 2 1
a = 3;
console.log(a, window.a);
// 3 1
function a(c) { }
console.log(a, window.a);
// 3 3,由於上一次是a=3,3被「傳遞」出去
function a(ca) { }
console.log(a, window.a);
// 3 3,由於函數聲明只會第一次把本身「傳遞」出去,如今不會了,能夠理解爲挨着的function a(){}都合併掉了
};
複製代碼
然而,對於safari來講,這一切和沒有代碼塊
{}
時的表現是同樣的。折騰了一陣,原來是瀏覽器處理方式不同。斷點調試熟練度+10,經驗+3 😊