在 ES6 以前,JavaScript 沒有塊級做用域(一對花括號{}即爲一個塊級做用域) ,大體分爲 全局做用域 和 函數做用域 。變量提高即將變量聲明提高到它所在 做用域 的 最開始 的部分。 在 JavaScript 代碼運行以前實際上是有一個 編譯階段 的。編譯以後纔是 從上到下 ,一行一行解釋執行。變量提高就發生在 編譯階段 ,它把 變量 和 函數 的聲明提高至做用域的頂端。(編譯階段的工做之一就是將變量與其做用域進行關聯)。我先分開介紹變量提高和函數提高到後面再放到一塊兒比較。 若是想更深刻的瞭解 產生變量提高的緣由javascript
注意java
同一個變量只會 聲明一次 ,其它的會被覆蓋掉。
變量提高/函數提高 是提高到 當前做用域 的頂部,若是遇到特殊的 if(){}/try-cache 做用域,同時也會把也會提高到 特殊做用域 的外部。
函數提高 的優先級是高於 變量提高 的優先級,而且 函數聲明 和 函數定義 的部分一塊兒被提高。
變量提高
咱們直接從代碼從最基礎的開始chrome
console.log(a); // undefined
var a = 2;
複製代碼
相信這個你們知道,上面代碼其實就是瀏覽器
var a;
console.log(a); // undefined
a = 2;
複製代碼
他會提早聲明 a,可是不會給 a 賦值。 可是以下代碼會怎麼執行呢?ide
console.log(a); // Uncaught ReferenceError: a is not defined
a = 2;
複製代碼
若是沒有經過 var 聲明值類型的就不會存在變量提高,而是會報錯。函數
函數提高
聲明函數 有兩種方式: 一種是 函數表達式 ,另外一種是 函數聲明 。debug
函數表達式
console.log(aa) // undefined
var aa = function () {};3d
/** 代碼分解 ***/
var aa;
console.log(aa);
aa = function () {};
複製代碼
函數表達式 和 變量 的提高效果基本上是一致的,它會輸出 undefined 。調試
函數聲明
它和 函數表達式 是有點不同的,在沒有 {}做用域 時它們表現是一致的。表現一致的例子對象
console.log(a); // function a () {}
function a() { };
/** 代碼分解 ***/
function a() { };
console.log(a);
複製代碼
那若是 變量提高 和 函數提高 同時存在,誰先誰後呢? 咱們根據上面的注意事項 1 和 3 能夠得出結果,根據實例來分析一下。 請看下面的例子:
console.log(aa); // function aa () {}
var aa = 'aaaa';
function aa () {};
console.log(aa); // aaaa
/** 代碼分解 ***/
var aa; // 只會聲明一次的變量
function aa () {}; // 變量別覆蓋爲 aa 字面量函數
console.log(aa); // function aa () {} 輸出字面量函數
aa = 'aaaa'; // aa 從新被覆蓋爲 'aaaa'
console.log(aa); // aaaa 輸出最後的覆蓋值
複製代碼
其實咱們能夠經過 chrome 瀏覽器調試效果大體以下圖所示:
到這裏就大體知道 變量提高 、 函數提高 它們的大體過程和它們之間的 優先級 。下面咱們來講一下它們和 塊級做用域 和 函數做用域
的關係。
做用域
在 ES6 出現以後做用域變得很複雜,有太多種了,這裏只說和本篇文章相關的幾種做用域。咱們只看 全局做用域 、 詞法做用域 、 塊級做用域 、 函數做用域 這四種做用域。 全局做用域 基本上沒什麼好說的,上面的樣例基本上都是 全局做用域 ,這裏就不作多的贅述。
詞法做用域/函數做用域
詞法做用域: 函數在定義它們的做用域裏運行,而不是在執行它們的做用域裏運行。 咱們直接經過一個例子來分析一下:
在有 做用域 時,咱們來看一下 函數聲明 的表現,仍是經過一個實例來分析一下,代碼以下:
console.log(aa); // 若是直接輸入 會報錯 VM1778:1 Uncaught ReferenceError: a is not defined
複製代碼
下面修改代碼來分析在 函數做用域 中 函數聲明 的特殊表現。
console.log(aa); // undefined
var aa = 'aaaa';
console.log(aa); // aaaa
function test () {
console.log(aa); // undefined
var aa = 'bbbb';
console.log(aa); // bbbb
}
test();
/** 代碼分解 ***/
var aa;
console.log(aa); // undefined
aa = 'aaaa';
console.log(aa); // aaaa
function test () {
var aa;
console.log(aa); // undefined
aa = 'bbbb';
console.log(aa); // bbbb
}
test();
複製代碼
全局聲明瞭一個名字叫作 aa 的變量,它被提高全局域的頂部聲明,而在 test 函數中咱們又聲明瞭一個變量 aa ,這個變量在當前 函數做用 的頂部聲明。在函數的執行的階段,變量的讀取都是就近原則,先從當先的 活動對象 或 做用域 查找,若是沒有才會從 全局對象 或 全局做用域 查找。
稍微加大一點難度,修改代碼以下:
console.log(aa); // undefined
var aa = 'aaaa';
console.log(aa); // aaaa
function test () {
console.log(aa); // aaaa
aa = 'bbbb';
console.log(aa); // bbbb
}
test();
/** 代碼分解 ***/
var aa;
console.log(aa); // undefined
aa = 'aaaa';
console.log(aa); // aaaa
function test () {
console.log(aa); // aaaa
aa = 'bbbb';
console.log(aa); // bbbb
}
test();
複製代碼
咱們把 test函數 內部的 var aa = 'bbbb' 修改成 aa = bbbb ,這樣就 不存在變量提高 只是一個簡單 變量覆蓋賦值 。
塊級做用域
在 ES6 中新增了 塊級做用域 ,咱們能夠經過 let/const 來建立 塊級做用域 ,只能在當前 塊中訪問 經過 let/const 聲明的變量。 咱們簡單的瞭解一下 let 和 塊級做用域 ,請看下方的代碼:
if (true) {
// console.log(aa); // VM439541:1 Uncaught SyntaxError: Identifier 'aa' has already been declared
let aa = 'aaa';
}
console.log(aa); // VM439096:4 Uncaught ReferenceError: aa is not defined
複製代碼
在 if條件語句 內部經過 let aa = 'aaa' 中的 let 關鍵字建立了一個 塊級做用域 ,因此咱們在外面不能訪問 aa 變量。
console.log(aa); // VM440010:1 Uncaught ReferenceError: aa is not defined
let aa = 'aaa';
複製代碼
let 聲明的變量同時存在 DTZ(暫時性死區) ,在 let 聲明變量以前使用這個變量,會觸發 DTZ(暫時性死區) 報錯。
let aa = 'aaa';
let aa = 'aaa';
// Uncaught SyntaxError: Identifier 'aa' has already been declared
複製代碼
let 不能屢次聲明同一個變量,否則會報錯。
if判斷/try-cache
if(){}/try-cache(){} 它們算一個做用域嗎?咱們經過下面的例子一步一步的分析它們,咱們以 if 爲分析樣例請看代碼:
console.log(aa) // undefined
if (true) {
var aa = 10;
}
console.log(aa); // 10
/代碼分析/
var a;
console.log(aa); // undefined
if (true) {
aa = 10;
}
console.log(aa); // 10
複製代碼
在 變量提高 時 if 是 不存在做用域 的,它的做用域就是全局做用域。那若是是 函數提高 呢? if會存在做用域 嗎? 經過下面這個實例咱們大概會了解 函數提高 和 if 的關係:
console.log(aa); // undefined
if (true) {
console.log(aa); // function aa () {}
function aa () {};
console.log(aa); //function aa () {}
}
/代碼分析/
var aa;
console.log(aa); // undefined
if (true) {
function aa () {};
console.log(aa); // function aa () {}
console.log(aa); //function aa () {}
}
複製代碼
咱們經過這個能夠看到當前執行的結果和上面所描述的 函數提高 表現並不一致,它只是提高了 aa 的聲明,賦值只是發生在 if 內部的,這也是 函數提高 在 if 中特異的表現。再來一個更特異的 if 和 函數提高 。
var aa = 'aaaa';
if (true) { // 執行序號 5
console.log(aa); // 執行序號 6
aa = 1; // 執行序號 7
function aa () {} // 執行序號 8
console.log(aa);
}
console.log(aa);
/代碼分析 執行順序/
var aa;
aa = 'aaaa';
if (true) {
function aa () {}
console.log(aa); // function aa () {}
aa = 1;
// function aa () {} 再執行一遍
console.log(aa); // 1
}
console.log(aa); // 1 ?這個肯定對?
複製代碼
咱們主要觀察 if 內部的 aa = 1; function aa () {} 的順序,在當前代碼中 第二個console.log(aa) 會輸出一個 1 ,若是咱們把 aa = 1; function aa () {} 改成 function aa () {}; aa = 1; 它外部的 console.log(aa) 就會變化,看代碼:
var aa = 'aaaa';
if (true) { // 執行序號 1
console.log(aa); // function aa () {} 執行序號 2
function aa () {} // 執行序號 3
aa = 1; // 執行序號 4
console.log(aa); // 1
}
console.log(aa); // function aa () {}
/代碼分析 執行順序/
var aa;
aa = 'aaaa';
if (true) {
function aa () {}
console.log(aa); // function aa () {}
// function aa () {} 再執行一遍
aa = 1;
console.log(aa); // 1
}
console.log(aa); // function aa () {} ?這個肯定對?
複製代碼
若是是按上面分析的代碼執行順序是相同的,可是爲何結果不太相同,這種資料不太好找,咱們直接上代碼去 chrome 中調試一下代碼就一清二楚了,大體調試過程以下:
function aa () {}; aa = 1; 執行過程
執行序號1時: 進入 if 內部執行,在 scope 中會多出來一個 block ,也就是在 做用域鏈 中會多出來一個 block ,這個做用域中有 aa = function aa () {} 。以下圖所示:
這個時候 block 是 function aa() {} 而全局的 window.aa 如今仍是 aaaa
執行序號2時: 執行 console.log(aa) ,這個只是一個輸出語法並不會改變變量的值,執行效果沒有變。
執行序號3時: 執行 function aa() {} , 咱們能夠看到 block 和 全局做用域 的 aa 變量都改變爲 function aa () {} ,以下圖所示:
執行序號4時: 它會執行的代碼 aa = 1 ,這個時候根據做用域鏈的規則,就近獲取和修改變量。因此 block 內的 aa = 1 ,而全局變量 window.aa = function aa () {} 以下圖所示:
aa = 1; function aa () {}; 執行過程
執行序號5時: 進入 if 內部執行,在 scope 中會多出來一個 block ,也就是在 做用域鏈 中會多出來一個 block ,這個做用域中有 aa = function aa () {} 。以下圖所示:
這個時候 block 是 function aa() {} 而全局的 window.aa 如今仍是 aaaa
執行序號6時: 執行 console.log(aa) ,這個只是一個輸出語法並不會改變變量的值,執行效果沒有變。
執行序號7時: 執行 aa = 1 , 咱們能夠看到 block 做用域的變量 aa 被賦值爲了 1 ,而 全局做用域 中的變量 aa 仍是 aaaa 。以下圖所示:
執行序號8時: 它會執行的代碼 function aa() {} ,當前代碼執行完成時,咱們會發現 全局做用域 中的變量 aa 也被賦值爲 1 . 以下圖所示:
aa = 1; 執行過程 當沒有 function aa () {}; 函數聲明時,咱們會發現不會產生一個臨時的 block 做用域,也不會存在奇特的現象。
綜合上面三個實例中咱們能夠得出如下的結論:
在 if 內部包含了 函數聲明 會在內部產生一個 block做用域 ,在不包含時不會產生 block做用域 。
在當前 if 外部存在和 函數聲明 相同的 變量名稱 時,當執行到 函數聲明 時同時會更新外部 函數做用域or全局做用域 中變量的值,只更新當前執行的這一次。
咱們再來一個例子來證實咱們獲得的結論,例子以下:
function test () {
// debugger
var aa = 'aaaa';
if (true) {
console.log(aa); // 第一個 ƒ aa () {}
aa = 1;
function aa () {}
console.log(aa); // 第二個 1
}
console.log(aa); // 第三個 1
}
test()
console.log(aa) // 第四個 VM5607:13 Uncaught ReferenceError: aa is not defined
複製代碼
第一個 console.log(aa) 會輸出 ƒ aa () {} ,由於 函數聲明 的提高和賦值都會放到 if 的內部。同時會產生一個 block做用域 。
第二個 console.log(aa) 會輸出 if 內部中的 aa = 1 ,由於 a = 1 會把 if 產生的 block做用域 中的變量 aa 修改成了 1 。
第三個 console.log(aa) 會輸出 test函數做用域 中的 aa = 1 ,由於在執行 function aa () {} 是都會更新外部變量 aa 的值爲 1 ,也就是 test函數做用域 中的 aa = 1 ;
第四個 console.log(aa) 會輸出 全局做用域 中的 aa ,由於歷來沒有聲明過全局變量 aa 因此會報錯, is not defined 。
來兩道題
來兩道題加深一下印象。
第一道題
var a = function() {
console.log(1);
};
var a = function() {
console.log(2);
};
var a;
console.log(a);
a = 1;
console.log(a);
a = 2;
console.log(a);
console.log(typeof a);
複製代碼
若是隻能答出來就沒有必要看了。
若是變量提高遇到函數提高,那個優先級更高呢,看下面的代碼。
console.log(a); // function a () {console.log(1);}
var a = 1;
function a() {
console.log(1);
}
console.log(a); // 1
複製代碼
看上面的代碼知道 函數提高 是 高於變量提高 的,由於在 javascript 中函數是一等公民, 而且不會被變量聲明覆蓋 ,可是會被 變量賦值覆蓋 。其實代碼以下
var a = function() {
console.log(1);
};
var a;
console.log(a); // function a () {console.log(1);}
a = 1;
console.log(a); // 1
複製代碼
咱們再來一個稍微複雜一點的,代碼以下:
console.log(a); // function a () {console.log(2);}
var a = 1;
function a() {
console.log(1);
}
console.log(a); // 1
var a = 2;
function a() {
console.log(2);
}
console.log(a); // 2
console.log(typeof a); // number
複製代碼
在屢次函數提高的會後一個覆蓋前一個,而後纔是變量提高,其實代碼以下:
var a = function() {
console.log(1);
};
var a = function() {
console.log(2);
};
var a;
console.log(a); // function a () {console.log(2);}
a = 1;
console.log(a); // 1
a = 2;
console.log(a); // 2
console.log(typeof a); // number
複製代碼
第二道題
第二道題會比第一道題難一點點,代碼以下:
console.log(aa);
var aa = 'aaa';
if (true) {
console.log(aa);
aa = 1;
function aa () {}
aa = 2;
console.log(aa);
}
console.log(aa);
複製代碼
若是上面的內容看懂了,大概這個題就會感受很簡單,大體過程以下:
第一個 console.log(aa) 會輸出 全局做用域 中的 aa 值爲 undefined ,由於 var aa = 'aaa' 會產生變量提高,會把 var aa; 放到全局做用域中的頂端,因此會輸出 undefined 。第二個 console.log(aa) 會輸出 if 內部中的 aa = ƒ aa () {} , if 內部執行產生 block做用域 ,而且 block做用域 內部的 ƒ aa () {} 被提高到頂部,因此會輸出 ƒ aa () {} 。第三個 console.log(aa) 會輸出 block做用域 中的 aa = 2 ,由於在執行 function aa () {} 是都會更新外部變量 aa 的值爲 1 ,也就是 全局做用域 中的 aa = 1 ;第四個 console.log(aa) 會輸出 全局做用域 中的 aa ,由於在上一步中咱們知道了 全局做用域 中的 aa = 1 ,因此會輸出 1 。undefinedƒ aa () {}21複製代碼到此結束JavaScript中的變量提高,若是發現本篇文章沒有涉及的變量提高的知識點和錯誤的地方,請你們多多指正、探討。