探究{ a = 1; function a(){} }和{ function b(){}; b = 1 }

近來在多個羣裏面看見有人發了一個題,{ 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(){}
}
複製代碼

var、let、const發生了什麼

生命週期:聲明(做用域註冊一個變量)、初始化(分配內存,初始化爲undefined)、賦值調試

  • var:遇到有var的做用域,在任何語句執行前都已經完成了聲明和初始化,也就是變量提高並且拿到undefined的緣由由來
  • function: 聲明、初始化、賦值一開始就所有完成,因此函數的變量提高優先級更高
  • let、const:解析器進入一個塊級做用域,發現let關鍵字,變量只是先完成聲明,並無到初始化那一步。此時若是在此做用域提早訪問,則報錯Cannot access 'a' before initialization,這就是存在暫時性死區的表現。等到解析到有let那一行的時候,纔會進入初始化階段。若是let的那一行是賦值操做,則初始化和賦值同時進行

注意:變量提高僅提高聲明,而不提高初始化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展開也是沒有什麼變化的

第2、三個點,對a賦值1和2,也很符合預期,其餘沒有變化
第四個點,就一直是2了,和第三個點的信息是如出一轍的

想必這是一個很無聊的事情,但咱們仍是再把var換成let看看

第一個點,由於是let,因此global裏面沒有看見a。道理和let a = 1window.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(){} }

{ 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 }

咱們執行一下{ 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的表現方式有這些特色:

  • 代碼塊裏面a變量提高、a賦值、函數聲明a是和常規的同樣
  • 代碼塊裏面全部的a函數的函數聲明,也是和常規同樣提高,取最後一個
  • 代碼塊裏面a函數聲明語句,除了提高,還有一個神奇的表現:它會把代碼塊裏面上一句a相關的賦值語句(a = xxx)的值「傳遞」出去,function a(){}更像一句「賦值語句」。具體爲何呢,大概是瀏覽器的內部對代碼塊的實現方式了
  • 只有第一次a函數聲明會「傳遞」,後面的a函數聲明只會把上一句賦值語句(a = xxx)的值「傳遞」到全局

咱們能夠試一下,利用這些規律猜一下輸出結果:

{
  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 😊

相關文章
相關標籤/搜索