摘要:聲明的變量不得改變值,這意味著,一旦聲明變量,就必須立即初始化,不能留到以后賦值。這在語法上,稱為暫時性死區,簡稱。這表明函數內部的變量與循環變量不在同一個作用域,有各自多帶帶的作用域。系列文章系列文章地址
為什么需要塊級作用域
ES5 只有全局作用域和函數作用域,沒有塊級作用域,這帶來很多不合理的場景。
通過var聲明變量存在變量提升:
if (condition) { var value = 1 } console.log(value)
初學者可能會認為當變量condition為true時,才會創建value。當condition為false時,不會創建value,結果應該是報錯。然而因為JavaScript存在變量提升的概念,代碼等同于:
var value if (condition) { value = 1 } console.log(value) // undefined
所有當condition為false,輸入結果為undefined。
ES5 只有全局作用域和函數作用域,其中變量提升也分成兩種情況:一種全局聲明的變量,提升會在全局最上面,上面就屬于全局變量聲明;一種是函數中聲明的變量,提升在函數的最上方:
function fn() { var value if (condition) { value = 1 } console.log(value) // undefined } console.log(value) // Uncaught ReferenceError: value is not defined
所有當condition為false,函數內輸入結果為undefined,函數輸入就會報錯Uncaught ReferenceError: value is not defined。函數的變量提升是根據最近的外層函數提升,沒有函數就為全局下提升。
為規范變量使用控制,ECMAScript 6 引入了塊級作用域。
塊級作用域就是 {} 之間的區域
我們來整理一下 let 和 const 的特點:
不存在變量提升
if(condition) { let value = 1 } console.log(value) // Uncaught ReferenceError: value is not defined
不管 conditon 為 true 或者 false ,都無法輸出value,結果為 Uncaught ReferenceError: value is not defined
重復聲明報錯
let value = 1 let value = 2
重復聲明同一個變量,會直接報錯 Uncaught SyntaxError: Identifier "value" has already been declared
不綁定在全局作用域上
var value = 1 console.log(window.value) // 1
在來看一下let聲明:
let value = 1 console.log(window.value) // undefinedlet 和 const 的區別:
const聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
const value = 1 value = 2 // Uncaught TypeError: Assignment to constant variable.
上面代碼表明改變常量的值會報錯。
const聲明的變量不得改變值,這意味著,const一旦聲明變量,就必須立即初始化,不能留到以后賦值。
const foo; // SyntaxError: Missing initializer in const declaration
上面代碼表示,對于const來說,只聲明不賦值,就會報錯。
對于對象的變量,變量指向是數據指向的內存地址。const只能保證數據指向內存地址不能改變,并不能保證該地址下數據不變。
const data = { value: 1 } // 更改數據 data.value = 2 console.log(data.value) // 2 // 更改地址 data = {} // Uncaught TypeError: Assignment to constant variable.
上述代碼中,常量 data 存儲的是一個地址,這里地址指向一個對象。不可變的只是這個地址,即不能將 data 指向另一個地址,但是對象本身是可以變的,所以依然為其更改或添加新屬性。
暫時性死區在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的。這在語法上,稱為“暫時性死區”(temporal dead zone,簡稱 TDZ)。
let 和 const 聲明的變量不會被提升到作用域頂部,如果在聲明之前訪問這些變量,會導致報錯:
console.log(typeof value); // Uncaught ReferenceError: value is not defined let value = 1;
這是因為 JavaScript 引擎在掃描代碼發現變量聲明時,要么將它們提升到作用域頂部(遇到 var 聲明),要么將聲明放在 TDZ 中(遇到 let 和 const 聲明)。訪問 TDZ 中的變量會觸發運行時錯誤。只有執行過變量聲明語句后,變量才會從 TDZ 中移出,然后方可訪問。
看似很好理解,不保證你不犯錯:
var value = "global"; // 例子1 (function() { console.log(value); let value = "local"; }()); // 例子2 { console.log(value); const value = "local"; };
兩個例子中,結果并不會打印 "global",而是報錯 Uncaught ReferenceError: value is not defined,就是因為 TDZ 的緣故。
常見面試題for(var i = 0; i < 3; i++) { setTimeout(() => { console.log(i) }) } // 3 // 3 // 3
上述代碼中,我們期望輸出0,1,2三個值,但是輸出結果是 3,3,3 ,不符合我們的預期。
解決方案如下:
使用閉包解法
for(var i = 0; i < 3; i++) { (function(i) { setTimeout(() => { console.log(i) }) })(i) } // 0 // 1 // 2
ES6 的 let 解法
for(let i = 0; i < 3; i++) { setTimeout(() => { console.log(i) }) } // 0 // 1 // 2
上述代碼中,變量 i 是 let 聲明的,當前的i只在本輪循環有效,所以每一次循環的i其實都是一個新的變量,所以最后輸出的是0,1,2。你可能會問,如果每一輪循環的變量i都是重新聲明的,那它怎么知道上一輪循環的值,從而計算出本輪循環的值。這是因為 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算.
另外,for循環還有一個特別之處,就是設置循環變量的那部分是一個父作用域,而循環體內部是一個多帶帶的子作用域。
for (let i = 0; i < 3; i++) { let i = "abc"; console.log(i); } // abc // abc // abc
上面代碼正確運行,輸出了 3 次abc。這表明函數內部的變量i與循環變量i不在同一個作用域,有各自多帶帶的作用域。
如果嘗試將 let 改成 const 定義:
for (const i = 0; i < 3; i++) { console.log(i); } // 0 // Uncaught TypeError: Assignment to constant variable.
上述代碼中,會先輸出一次 0,然后代碼就會報錯。這是由于for循環的執行順序造成的,i 定義為 0,然后執行 i < 3比較,符合條件執行循環主體,輸出一次 0, 然后執行 i++,由于 i 使用const定義的只讀變量,代碼執行報錯。
說完了普通的for循環,我們還有for…in循環呢~
那下面的結果是什么呢?
const object = {a: 1, b: 1, c: 1}; for (const key in object) { console.log(key) } // a // b // c
上述代碼中,雖然使用 const 定義 key 值,但是代碼中并沒有嘗試修改 key 值,代碼正常執行,這也是普通for循環和for…in循環的區別。
Babel編譯在 Babel 中是如何編譯 let 和 const 的呢?我們來看看編譯后的代碼:
let value = 1;
編譯為:
var value = 1;
我們可以看到 Babel 直接將 let 編譯成了 var,如果是這樣的話,那么我們來寫個例子:
if (false) { let value = 1; } console.log(value); // Uncaught ReferenceError: value is not defined
如果還是直接編譯成 var,打印的結果肯定是 undefined,然而 Babel 很聰明,它編譯成了:
if (false) { var _value = 1; } console.log(value);
我們再寫個直觀的例子:
let value = 1; { let value = 2; } value = 3;
var value = 1; { var _value = 2; } value = 3;
本質是一樣的,就是改變量名,使內外層的變量名稱不一樣。
那像 const 的修改值時報錯,以及重復聲明報錯怎么實現的呢?
其實就是在編譯的時候直接給你報錯……
那循環中的 let 聲明呢?
var funcs = []; for (let i = 0; i < 10; i++) { funcs[i] = function () { console.log(i); }; } funcs[0](); // 0
Babel 巧妙的編譯成了:
var funcs = []; var _loop = function _loop(i) { funcs[i] = function () { console.log(i); }; }; for (var i = 0; i < 10; i++) { _loop(i); } funcs[0](); // 0項目實踐
在我們實際項目開發過程中,應該默認使用 let 定義可變的變量,使用 const 定義不可變的變量,而不是都使用 var 來定義變量。同時,變量定義位置也有一定區別,使用
var 定義變量都會在全局頂部或者函數頂部定義,防止變量提升造成的問題,對于使用 let 和 const 定義遵循就近原則,即變量定義在使用的最近的塊級作用域中。
ES6系列文章地址:https://github.com/changming-...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103514.html
摘要:塊級作用域存在于函數內部塊中字符和之間的區域和塊級聲明用于聲明在指定塊的作用域之外無法訪問的變量。和都是塊級聲明的一種。值得一提的是聲明不允許修改綁定,但允許修改值。這意味著當用聲明對象時沒有問題報錯臨時死區臨時死區,簡寫為。 塊級作用域的出現 通過 var 聲明的變量存在變量提升的特性: if (condition) { var value = 1; } console.lo...
摘要:一個對象若只被弱引用所引用,則被認為是不可訪問或弱可訪問的,并因此可能在任何時刻被回收。也就是說,一旦不再需要,里面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。 前言 我們先從 WeakMap 的特性說起,然后聊聊 WeakMap 的一些應用場景。 特性 1. WeakMap 只接受對象作為鍵名 const map = ...
摘要:最終的代碼如下第二版假設有這樣一段為了保持可讀性,我希望最終輸入的樣式為其實就是匹配每行前面的空格,然后將其替換為空字符串。 基礎用法 let message = `Hello World`; console.log(message); 如果你碰巧要在字符串中使用反撇號,你可以使用反斜杠轉義: let message = `Hello ` World`; console.log(mes...
摘要:前言這里的泛指之后的新語法這里的完全是指本文會不斷更新這里的使用是指本文會展示很多的使用場景這里的手冊是指你可以參照本文將項目更多的重構為語法此外還要注意這里不一定就是正式進入規范的語法。 前言 這里的 ES6 泛指 ES5 之后的新語法 這里的 完全 是指本文會不斷更新 這里的 使用 是指本文會展示很多 ES6 的使用場景 這里的 手冊 是指你可以參照本文將項目更多的重構為 ES6...
摘要:聲明之函數作用域和全局作用域。塊級作用域不能重復聲明臨時性死區等特性用來解決變量存在的種種問題。塊級作用域終于在外面訪問不到了。一些常量聲明使用聲明的變量名全部大寫。 ES5之前javascript語言只有函數作用域和全局作用域,使用var來聲明變量,var聲明的變量還存在變量提升使人困惑不已。我們先來復習一下ES5的var聲明,再對比學習let和const 。 var var聲明之函...
閱讀 2800·2021-11-22 14:44
閱讀 541·2021-11-22 12:00
閱讀 3683·2019-08-30 15:54
閱讀 1570·2019-08-29 17:15
閱讀 1898·2019-08-29 13:50
閱讀 1107·2019-08-29 13:17
閱讀 3513·2019-08-29 13:05
閱讀 1181·2019-08-29 11:31