別高估本身,這道題,有點難!

學無止境,不會的爲啥老是那麼多?chrome

今天在一個技術羣裏面,有朋友丟了一道題目,問這個輸出是啥?瀏覽器

粗略一看,輸出都是 21 啊,你覺得加個 if(true),大夥就不知道了?函數

而後,就沒有而後了…………ui

「光榮」的答錯了!spa

正確答案:內部是 21,外部是 1;3d

這個玄妙之處確實就在這個塊級做用域 if 裏面。指針

假如咱們去掉 if 看題。code

var a = 0;
// if(true){
 a = 1;
function a(){}
a = 21;
console.log("裏面",a);
// }
console.log("外部",a);
複製代碼

這道題估計沒人好意思去問了,毫無疑問,輸出的 a 都是 21 啊。cdn

然而在 JS 裏面都說沒有塊級做用域,那爲啥 if 這個塊會影響最終結果呢?blog

如下爲chrome瀏覽器下的實現

本文主要點:

  • 神奇的 let
  • 函數塊級做用域

什麼是提高(Hosting)?

主要分爲變量提高和函數提高。

變量提高

變量的提高是以變量做用域來決定的,即全局做用域中聲明的變量會提高至全局最頂層,函數內聲明的變量只會提高至該函數做用域最頂層。

入門級

看題:

console.log(a);
var a = 0;
複製代碼

這個並不會報: Uncaught ReferenceError: a is not defined。

而是會輸出 undefined。

由於變量提高以後的結果是:

var a;
console.log(a);
a = 0;
複製代碼

進階級

例子1:

var x = 0;
function a(){
    console.log(x);
    let x = 1;
}
a();
複製代碼

若是 let x 不會變量提高的話,那麼應該 x 輸出 0,其實是:

VM296:3 Uncaught ReferenceError: Cannot access 'x' before initialization at a (:3:14) at :1:1

它也不是報錯 x not defined,而是 Cannot access。

那這個報錯是啥緣由呢?

例子2:

let a = a;
let a = a;
複製代碼

你以爲會報什麼錯誤呢?

「 a not defined 」 或者 「Cannot access 'a' before initialization」 ?

實際上都不是,而是報錯: a has already been declared。

這裏看起來是:let 也會 「變量提高」,若是不會提高的話,例子1 的 x 應該輸出 0 ,例子2 應該報錯 a not defined。

可是若是會變量提高,那也說不過去呀, 那上面的例子1 應該輸出 undefined 啊。

因而咱們管這叫 「暫時性死區」。

實際上這個既不是咱們理解的變量提高,也不是沒有變量提高。那什麼是暫時性死區呢?

let 定義變量是有一個「特殊聲明」的過程,JS 預解析的時候,先將定義的 let ,const 「特殊聲明」提早,相似「舉手」,JS 引擎規定了同一個做用域,同一個變量只能被一次「舉手」。

這裏不一樣於 var 的定義和賦值,var 的聲明是若是已經聲明瞭,後者直接忽略聲明。

咱們繼續回到本題目來看。

let a = a; // 定義變量 a,我暫標識爲 a1
let a = a; // 定義變量 a,我暫標識爲 a2
複製代碼

預解析,將 a1 聲明,而後準備將 a2 聲明,這個時候,JS 引擎發現,聲明 a2 的時候 ,已經有 a1 聲明瞭。

因而違反了 「同一個做用域,同一個變量只能被聲明一次」 的規定,直接報錯。實際上代碼中賦值的 a 變量還沒讀取(在讀取變量的時候纔可能拋變量未定義的錯誤)。

因此,報錯了,錯誤內容:a2 已經被聲明瞭(被 a1 聲明瞭 a)。

因此回到上述例 1,代碼在讀取 x 的時候,發現已有 let 聲明的 x ,可是並未初始化,才直接報錯 x 沒法訪問。

那麼 let 變量「特殊聲明」是一個什麼神奇的東西呢?

其實是 JS 引擎爲了解決這個 let 變量提高時引入的 declareData, 在預解析的時候,裏面存儲了做用域裏面全部的 let 和 const 聲明數據。

事實上,做用域內全部的函數和變量的建立都須要校驗是否與 declareData 的值衝突。

例子 3:

var a = 1;//定義變量 a,我暫標識爲 a1
let a = 2;//定義變量 a,我暫標識爲 a2
複製代碼

declareData 聲明變量 a2,而後準備定義變量 a1,發現 declareData 已經有聲明 a2 了,直接報錯: a1 已經被聲明瞭,由於已經由 a2 聲明瞭變量 a 。

函數提高

函數提高,相似變量提高,可是確有些許不一樣。

函數表達式

console.log(a);// undfined
var a = function (){}

console.log(a); // function a
function a(){}
複製代碼

函數表達式不會聲明提高,第一個例子輸出的是 undefined 而不是 not defined,是由於中了變量 var a 的變量提高。

塊級做用域

console.log(a);// undefined
if(true){
    console.log(a); // function a
    function a(){}
}
複製代碼

若是是變量提高,是不存在塊級做用域的,可是函數提高是存在的,這個預解析以下:

var a; // 函數 a 的聲明
console.log(a);// undefined
if(true){
    function a(){} // 函數 a 的定義
    console.log(a); // function a
}
複製代碼

其實函數 function a(){} 在通過預解析以後,將函數聲明提到函數級做用域最前面,而後將函數定義提高到塊級做用域最前面。

注意:這裏的函數定義是提高到塊級做用域最前面。

再看一題:

try{
    console.log(a);// undefined
    aa.c;
}catch(e){
    var a = 1;
}
console.log(a);// 1
console.log(e);// Uncaught ReferenceError: e is not defined
複製代碼

在 catch 裏面定義的 a,被聲明提早了。可是 catch 裏面的 e 在外部沒法訪問。 若是你認爲 catch 是一個「函數做用域」,那麼裏面的 a 不該該被提高到最外層。實際上 catch 裏面遵循的是塊做用域。

在 JS 領域內,自己是存在塊級做用域的(let const 以外)。

再看原題

爲了方便閱讀,我再貼一下題目:

var a = 0;
if(true){
    a = 1;
    function a(){}
    a = 21;
    console.log("裏面",a);
}
console.log("外部",a);
複製代碼

結合上面咱們瞭解到的知識。首先,if 裏面的 function a(){} 會聲明提高,將聲明" var a" 移到函數級做用域最前面,將函數定義移到塊級做用域最前面,預解析以下:

var a;// 函數 a 的聲明提早
var a = 0;  // 已經聲明瞭 a,這裏會忽略聲明 ,直接賦值爲 0
if(true){
    function a(){} // 函數定義 a 聲明提高到塊級最前面
    a = 1; // 這裏將 塊級做用域最前面的函數 a 重置爲 1了。
    // function a(){}; how do ?
    a = 21;
    console.log("裏面",a);
}
console.log("外部",a);
複製代碼

函數自己是【 定義函數名變量 指針指向 函數內存塊】。

函數提高是在塊級做用域,可是函數名變量是函數級別的做用域。因此在塊級的函數定義(原始代碼函數的聲明位置)的時候,會將函數名變量同步到函數級做用域,實際上,只有這個時候,在塊級做用域外才能訪問到函數定義。

預解析以下:

var a = 0;
console.log(a,window.a); // 輸出 0 和 0
if(true){
    console.log(a,window.a);// 函數提高,是塊級做用域,輸出 function a 和 0
    a = 1;  // 取做用域最近的塊級做用域的 function a ,且被重置爲 1了,本質又是一個 變量的賦值。
    console.log(a,window.a);// a 是指向塊級做用域的 a, 輸出 1 和 0 
    function a(){} // 函數的聲明,將執行函數的變量的定義同步到函數級的做用域。
    console.log(a,window.a);// 輸出 1 和 1
    a = 21; // 仍然是函數定義塊級做用域的 a ,重置爲 21
    console.log(a,window.a); // 輸出爲函數提高的塊級做用域的 a, 輸出 21,1
    console.log("裏面",a);
}
console.log("外部",a);
複製代碼

到此爲止,應該都理解了吧,不再會答錯了吧!若是不理解,再多看幾遍試試。

相關文章
相關標籤/搜索