var,let和const深入解析(一)
es6有許多特別棒的特性,你可能對該語言的整體非常熟悉,但是你知道它在內部是如何工作的嗎?當我們知道它的內部原理以後,我們使用起來也會更加的安心一些。這裡我們想逐步的引導你,讓你對其有一個更深入,更淺顯的認識。讓我們就先從es6中的變數開始講起吧。
let和const
在es6中新引入了兩種方式來申明變數,我們仍然可以使用廣為傳誦的var變數(然而你不應該繼續使用它了,繼續閱讀來了解其中原因),但是現在我們有了兩種更牛的工具去使用:let和const。
let
let和var非常的相似,在使用方面,你可以使用完全相同的方式來宣告變數,例如:
let myNewVariable = 2;
var myOldVariable = 3;
console.log(myNewVariable); // 2
console.log(myOldVariable); // 3
但是實際上,他們之間有幾處明顯的不同。他們不僅僅是關鍵字變了,而且實際上它還讓會簡化我們的一些工作,防止一些奇怪的bug,其中這些不同點是:
let是塊狀作用域(我將會在文章後面著重講一下作用域相關的東西),而var是函式作用域。
let不能在定義之前訪問該變數(var是可以的,它確實是js世界中許多bug和困擾的源頭)。
let不能被重新定義。
在我們講解這些不同點之前,首先我們看一個更酷的變數:const
const
const和let非常像(跟var相比來說,他們之間有許多相同點),但是他們有一個主要的不同點:let可以重新賦值,但是const不能。因此const定義的變數只能有一個值,並且這個值在宣告的時候就會被賦值。因此我們來看下下面的例子。
const myConstVariable = 2;
let myLetVariable = 3;
console.log(myConstVariable); // 2
myLetVariable = 4; // ok
myConstVariable = 5; //wrong - TypeError thrown
但是const是完全不可變的嗎?
有一個常見的問題:雖然變數不能被重新賦值,但是也不能讓他們真正的變為不可變的狀態。如果const變數有一個陣列或者物件作為其值的話,你可能會像下面的程式碼一樣改變它的值。
const myConstObject = {mutableProperty: 2};
// myConstObject = {}; - TypeError
myConstObject.mutableProperty = 3; //ok
console.log(myConstObject.mutableProperty); // 3
const myConstArray = [1];
// myConstArray = []; - TypeError
myConstArray.push(2) //ok
console.log(myConstArray); // [1, 2]
當然你不能用原始資料型別的值,比如string,number,boolean等,因為他們本質上是不可變的。
真正的不可變
如果你想讓我們的變數真正的不可變的話,可以使用Object.freeze(), 它可以讓你的物件保持不可變。不幸的是,他僅僅是淺不可變,如果你物件裡巢狀著物件的話,它依然是可變的。
const myConstNestedObject = {
immutableObject: {
mutableProperty: 1
}
};
Object.freeze(myConstNestedObject);
myConstNestedObject.immutableObject = 10; // won`t change
console.log(myConstNestedObject.immutableObject); // {mutableProperty: 1}
myConstNestedObject.immutableObject.mutableProperty = 10; // ok
console.log(myConstNestedObject.immutableObject.mutableProperty); // 10
變數的作用域
在介紹了一些基礎知識以後,下面我們要進入一個更高階的話題。現在我們要開始講解es5和es6變數中的第一個不同-作用域
注意:下面的例子都用的是let,它的規則在const上同樣也適用
全域性變數和函式作用域變數
在js中,究竟什麼是作用域呢?本文不會給出一個關於作用域的完整解釋。簡單來說,變數的作用域決定了變數的可用位置。從不同的角度來看,可以說作用域是你可以在特定區域內使用的那些變數(或者是函式)的宣告。作用域可以是全域性的(因此在全域性作用域中定義的變數可以在你程式碼中任何部分訪問)或者是區域性的。
很顯然,區域性作用域只能在內部訪問。在ES6以前,它僅僅允許一種方式來定義區域性作用域 – function,我們們來看一下下面的例子:
// global scope
var globalVariable = 10;
function functionWithVariable() {
// local scope
var localVariable = 5;
console.log(globalVariable); // 10
console.log(localVariable); // 5
}
functionWithVariable();
//global scope again
console.log(globalVariable); // 10
console.log(localVariable); // undefined
上面的例子中,變數globalVariable是全域性變數,所以它可以在我們程式碼中的函式內或者是其他區域內被訪問到,但是變數localVariable定義在函式內,所以它只在函式內可訪問。
因此,所有在函式內建立的內容都可以在函式內被訪問到,包括函式內部裡所有的巢狀函式(可能會被巢狀多層)。在這裡可能要感謝閉包了,但是在文章裡我們並不打算介紹它。不過請繼續關注,因為我們在未來的博文中,會更多的介紹它。
提升
簡單來說,提升是一種吧所有的變數和函式宣告“移動”到作用域的最前面的機制。讓我們看一下下面的例子。
function func() {
console.log(localVariable); // undefined
var localVariable = 5;
console.log(localVariable); // 5
}
func();
它為什麼依然會正常工作呢?我們還沒有定義這個變數,但是它依然通過console.log()列印出了undefined。為什麼不會報出一個變數未定義的錯誤呢?讓我們再仔細看一下。
編譯變數
Javascript解析器要遍歷這個程式碼兩次。第一次被稱為編譯狀態,這一次的話,程式碼中定義的變數就會提升。在他之後,我們的程式碼就變成類似於下面的這樣子的(我已經做了一些簡化,只展示出相關的部分)。
function func() {
var localVariable = undefined;
console.log(localVariable); // undefined
localVariable = 5;
console.log(localVariable); // 5
}
func();
我們看到的結果是,我們的變數localVariable已經被移動到func函式的作用域的最前面。嚴格來說,我們變數的宣告已經被移動了位置,而不是宣告的相關程式碼被移動了位置。我們使用這個變數並列印出來。它是undefined是因為我們還沒有定義它的值,它預設使用的undefined。
提升的例子 – 會出什麼問題
來讓我們看一個令人討厭的例子,我們的作用域範圍對於我們來說,是弊大於利的。也不是說函式作用域是不好的。而是說我們必須要警惕一些由於提升而引起的一些陷進。我們來看看下面的程式碼:
var callbacks = [];
for (var i = 0; i < 4; i++) {
callbacks.push(() => console.log(i));
}
callbacks[0]();
callbacks[1]();
callbacks[2]();
callbacks[3]();
你認為輸出的值是多少呢?你猜可能是0 1 2 3,是嗎?如果是的話,對於你來說,可能會有一些驚喜。實際上,他真實的結果是4 4 4 4。等等,它到底發生了什麼?我們來“編譯”一下程式碼,程式碼現在看起來就像這樣:
var callbacks;
var i;
callbacks = [];
for (i = 0; i < 4; i++) {
callbacks.push(() => console.log(i));
}
callbacks[0]();
callbacks[1]();
callbacks[2]();
callbacks[3]();
你看出問題所在了嗎?變數i在整個作用域下都是可以被訪問到的,它不會被重新定義。它的值只會在每次的迭代中不斷地被改變。然後呢,當我們隨後想通過函式呼叫列印它的值得時候,他實際上只有一個值 – 就是在最後一次迴圈賦給的那個值。
我們只能這樣了嗎?不是的
Let和Const的拯救
除了定義變數的新方式以外,還引入了一種新的作用域:塊級作用域。塊就是由花括號括起來的所有的內容。所以它可以是if,while或者是for宣告中的花括號,也可以是單獨的一個花括號甚至是一個函式(對,函式作用域是塊狀作用域)。let和const是塊作用域。意味著無論你在塊中無論定義了什麼變數,什麼時候定義的,它都不會跑到塊作用域外面去。我們來看一下下面的例子:
function func() {
// function scope
let localVariable = 5;
var oldLocalVariable = 5;
if (true) {
// block scope
let nestedLocalVariable = 6;
var oldNestedLocalVariable = 6;
console.log(nestedLocalVariable); // 6
console.log(oldNestedLocalVariable); // 6
}
// those are stil valid
console.log(localVariable); // 5
console.log(oldLocalVariable); // 5
// and this one as well
console.log(oldNestedLocalVariable); // 6
// but this on isn`t
console.log(nestedLocalVariable); // ReferenceError: nestedLocalVariable is not defined
你能看出來差別嗎?你能看出來怎麼使用let來解決早些時候提出問題的嗎?我們的for迴圈包含一組花括號,所以它是塊作用域。所以如果在定義迴圈變數的時候,使用的是let或者是const來代替var的話,程式碼會轉為下面的形式。注意:我實際上已經簡化了很多,不過我確定你能理解我的意思。
let callbacks = [];
for (; i < 4; i++) {
let i = 0 //, 1, 2, 3
callbacks.push(() => console.log(i));
}
callbacks[0]();
callbacks[1]();
callbacks[2]();
callbacks[3]();
現在的每一次迴圈都有它自己的變數定義,所以變數不會被重寫,我們確信這行程式碼可以完成讓他做的任何事情。
這是這一部分結束的例子,但是我們再看一下下面的例子,我相信你明白列印出來的值的原因,以及對應的表現是什麼。
function func() {
var functionScopedVariable = 10;
let blockScopedVariable = 10;
console.log(functionScopedVariable); // 10
console.log(blockScopedVariable); // 10
if (true) {
var functionScopedVariable = 5;
let blockScopedVariable = 5;
console.log(functionScopedVariable); // 5
console.log(blockScopedVariable); // 5
}
console.log(functionScopedVariable); // 5
console.log(blockScopedVariable); // 10
}
func();
本文翻譯自:
https://blog.pragmatists.com/let-your-javascript-variables-be-constant-1633e56a948d
本文轉載自:http://www.lht.ren/article/15/
相關文章
- let var與const
- var和let/const的區別
- var、let和const的區別
- 深入理解JS:var、let、const的異同JS
- var let const區別
- let const var 區別
- let,const,var區別
- var、let和const的知識點
- var、const、let 的區別
- 深入理解ES6之var,let,const區別
- 5分鐘掌握var,let和const異同
- 前端 let、const和var你真的瞭解麼?前端
- 1.變數:var,let,const變數
- ES6之var、let、const
- ES6中let和var和const的區別
- var、let和const三者有哪些區別?
- 前端學習筆記 - var、let和const的用法前端筆記
- When to use var vs let vs const in JavaScriptJavaScript
- JavaScript中let、const、var 的區別JavaScript
- 變數和函式宣告提升,let和var const區別變數函式
- ES5 和 ES6:let const var 區別
- JavaScript 中的 Var,Let 和 Const 有什麼區別JavaScript
- JavaScript中的var、let 及 const 區別JavaScript
- var、let、const宣告變數的區別變數
- let和const
- ES6中var,let,const的區別
- es6 let const與var 的區別
- ES6系列——let和const深入理解
- 1分鐘帶你瞭解var let 和 const 的區別
- [譯] 在JavaScript中何時使用var、let及constJavaScript
- var、let、const、解構、展開、new、this、class、函式函式
- 【前端面試】(四)JavaScript var let const的區別前端面試JavaScript
- JavaScript 高階—— ES6新增語法 const(let const var區別)JavaScript
- 詳解 let 和 var
- var、let、const變數宣告的區別及特點變數
- 【ES6】var、let、const三者的區別
- 一些八股:1.fetch 的理解。2.let、const、var
- 【譯】深入理解 ES2015,第一趴:塊作用域 let 和 const