一、新的變數聲明方式 let/cons 與var不同,新的變數聲明方式帶來了一些不一樣的特性,其中最重要的兩個特性就是提供了塊級作用域與不再具備變數提升。 若是對變數提升不怎麼瞭解的話可以去參考我的其他文章 javascript預編譯的過程 。 什麼是塊級作用域膩? 寫在 “{}” 內的內容 都是塊 ...
一、新的變數聲明方式 let/cons
與var不同,新的變數聲明方式帶來了一些不一樣的特性,其中最重要的兩個特性就是提供了塊級作用域與不再具備變數提升。
若是對變數提升不怎麼瞭解的話可以去參考我的其他文章 javascript預編譯的過程 。
什麼是塊級作用域膩?
寫在 “{}” 內的內容 都是塊級作用域。
在es6之前,我們想保護一個變數怎麼辦,將其放在一個立即執行函數裡面,寫在局部作用域中。
這樣寫是不是挺麻煩的捏,多謝幾個單詞不花時間麽?!
在ES6中,我們想保護一個變數 只要 寫在花括弧中就好了。
{
let a=10;
}
下麵有兩個例子:
{ let a = 20; } console.log(a); // a is not defined //寫在塊級作用域中的內容會被保護起來,所以會列印 a is not defined
而這個簡單的例子,會被編譯為:
{ let _a = 20; } console.log(a); // a is not defined // ES5 console.log(a); // undefined var a = 20; // 在es5中var一個變數 會變數提升 // 如同 // var a; // console.log(a); //從上向下順序執行 當然會列印 undefined 的捏。 // a=20 // ES6 console.log(a); // a is not defined let a = 20; //變數不會提升 列印時當然是 a is not defined
當然,你的代碼編譯成為了ES5之後,仍然會存在變數提升,因此這一點只需要我們記住即可。
在實際使用中,也需要儘量避免使用變數提升的特性帶來的負面影響。只有在面試題中,才會對變數提升不停的濫用。使用ES6,我們需要全面使用let/const替換var,那麼什麼時候用let,什麼時候用const就成為了一個大家要熟練區分的一個知識點。
我們常常使用let來聲明一個值會被改變的變數,而使用const來聲明一個值不會被改變的變數,也可以稱之為常量。當值為基礎數據類型時,那麼這裡的值,就是指值本身。而當值對應的為引用數據類型時,那麼我這裡說的值,則表示指向該對象的引用。
這裡需要註意,正因為該值為一個引用,只需要保證引用不變就可以,我們仍然可以改變該引用所指向的對象。當我們試圖改變const聲明的變數時,則會報錯。
寫幾個例子,大家可以仔細揣摩一下:
let a = null; a = 20; const obDev = { a: 20, b: 30 } obDev.a = 30; console.log(obDev); // Object {a: 30, b: 30} const fn = function() {} const a = obDev.a; ... ...
只要抓住上面我說的特性,那麼在使用let/const時就會顯得游刃有餘。根據我自己的經驗,使用const的場景要比使用let的場景多很多。
二,解構賦值
我們經常定義許多對象和數組,然後有組織地從中提取相關的信息片段。在ES6中添加了可以簡化這種任務的新特性:解構。解構是一種打破數據結構,將其拆分為更小部分的過程。
在ES5中,開發者們為了從對象和數組中獲取特定數據並賦值給變數,編寫了許多看起來同質化的代碼
let options = { repeat: true, save: false }; // 從對象中提取數據 let repeat = options.repeat, save = options.save;
這段代碼從options對象中提取repeat和save的值,並將其存儲為同名局部變數,提取的過程極為相似
如果要提取更多變數,則必須依次編寫類似的代碼來為變數賦值,如果其中還包含嵌套結構,只靠遍歷是找不到真實信息的,必須要深入挖掘整個數據結構才能找到所需數據
所以ES6添加瞭解構功能,將數據結構打散的過程變得更加簡單,可以從打散後更小的部分中獲取所需信息
對象解構:
對象字面量的語法形式是在一個賦值操作符左邊放置一個對象字面量。
let node = { type: "Identifier", name: "foo" }; let { type, name } = node; console.log(type); // "Identifier" console.log(name); // "foo"
let node = { type: "Identifier", name: "foo" }, type = "Literal", name = 5; // 使用解構來分配不同的值 ({ type, name } = node); console.log(type); // "Identifier" console.log(name); // "foo"
在這個示例中,聲明變數type和name時初始化了一個值,在後面幾行中,通過解構賦值的方法,從node對象讀取相應的值重新為這兩個變數賦值
[註意]一定要用一對小括弧包裹解構賦值語句,JS引擎將一對開放的花括弧視為一個代碼塊。語法規定,代碼塊語句不允許出現在賦值語句左側,添加小括弧後可以將塊語句轉化為一個表達式,從而實現整個解構賦值過程。
數組解構:
與對象解構的語法相比,數組解構就簡單多了,它使用的是數組字面量,且解構操作全部在數組內完成,而不是像對象字面量語法一樣使用對象的命名屬性
let colors = [ "red", "green", "blue" ]; let [ firstColor, secondColor ] = colors; console.log(firstColor); // "red" console.log(secondColor); // "green"
在這段代碼中,我們從colors數組中解構出了"red"和"green"這兩個值,並分別存儲在變數firstColor和變數secondColor中。在數組解構語法中,我們通過值在數組中的位置進行選取,且可以將其存儲在任意變數中,未顯式聲明的元素都會直接被忽略
在解構模式中,也可以直接省略元素,只為感興趣的元素提供變數名
let colors = [ "red", "green", "blue" ]; let [ , , thirdColor ] = colors; console.log(thirdColor); // "blue"
這段代碼使用解構賦值語法從colors中獲取第3個元素,thirdColor前的逗號是前方元素的占位符,無論數組中的元素有多少個,都可以通過這種方法提取想要的元素,不需要為每一個元素都指定變數名
混合解構:
可以混合使用對象解構和數組解構來創建更多複雜的表達式,如此一來,可以從任何混雜著對象和數組的數據解構中提取想要的信息
let node = { type: "Identifier", name: "foo", loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }; let { loc: { start }, range: [ startIndex ] } = node; console.log(start.line); // 1 console.log(start.column); // 1 console.log(startIndex); // 0
這段代碼分別將node.loc.start和node.range[0]提取到變數start和startlndex中
解構模式中的loc和range僅代表它們在node對象中所處的位置(也就是該對象的屬性)。當使用混合解構的語法時,則可以從node提取任意想要的信息。這種方法極為有效,尤其是從JSON配置中提取信息時,不再需要遍歷整個結構了
三、 箭頭函數的使用
之前我說ES6顛覆了js的編碼習慣,箭頭函數的使用占了很大一部分。
首先是寫法上的不同:
// es5 var fn = function(a, b) { return a + b; } // es6 箭頭函數寫法,當函數直接被return時,可以省略函數體的括弧 const fn = (a, b) => a + b; // es5 var foo = function() { var a = 20; var b = 30; return a + b; } // es6 const foo = () => { const a = 20; const b = 30; return a + b; }
箭頭函數可以替換函數表達式,但是不能替換函數聲明
其次還有一個至關重要的一點,那就是箭頭函數中,沒有this。如果你在箭頭函數中使用了this,那麼該this一定就是外層的this。
也正是因為箭頭函數中沒有this,因此我們也就無從談起用call/apply/bind來改變this指向。記住這個特性,能讓你在react組件之間傳值時少走無數彎路。
var person = { name: 'tom', getName: function() { return this.name; } } // 我們試圖用ES6的寫法來重構上面的對象 const person = { name: 'tom', getName: () => this.name } // 但是編譯結果卻是 var person = { name: 'tom', getName: function getName() { return undefined.name; } };
在ES6中,會預設採用嚴格模式,因此this也不會自動指向window對象了,而箭頭函數本身並沒有this,因此this就只能是undefined,這一點,在使用的時候,一定要慎重慎重再慎重,不然踩了坑你都不知道自己錯在哪!這種情況,如果你還想用this,就不要用使用箭頭函數的寫法。
// 可以稍做改動 const person = { name: 'tom', getName: function() { return setTimeout(() => this.name, 1000); } } // 編譯之後變成 var person = { name: 'tom', getName: function getName() { var _this = this; // 使用了我們在es5時常用的方式保存this引用 return setTimeout(function () { return _this.name; }, 1000); } };
先記住箭頭函數的寫法,並留意箭頭函數中關於this的特殊性,更過實踐與註意事項我們在封裝react組件時再慢慢來感受。
還有就是 原函數中 arguments (實參) 在箭頭函數中是是用不了的。
四,promise (承諾)
他就是一個對象,主要是用來處理非同步數據的。
在promise中,有三種狀態:
pending(等待,處理中) --> 1.resolve(完成) 2.rejected(失敗,拒絕)。
又,這三種狀態的變化只有兩種模式,並且一旦狀態改變,就不會再變:
1、非同步操作從pending到resolved;
2、非同步操作從pending到rejected;
好了,既然它是屬於ES6規範,我們再通過chrome,直接列印出Promise,看看這玩意:
恩,一目瞭然,Promise為構造函數,歐克,這樣通過它,我們就可以實例化自己的Promise對象了,並加以利用。
Promise對象中的then方法
可以接收構造函數中處理的狀態變化,並分別對應執行。then方法有2個參數,第一個函數接收resolved狀態的執行,第二個參數接收reject狀態的執行。
function fn(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(); } else { reject(); } }).then(function() { console.log('參數是一個number值'); }, function() { console.log('參數不是一個number值'); }) } fn('hahha'); fn(1234);
then方法的執行結果也會返回一個Promise對象。因此我們可以進行then的鏈式執行,這也是解決回調地獄的主要方式。
function fn(num) { return new Promise(function(resolve, reject) { if (typeof num == 'number') { resolve(); } else { reject(); } }) .then(function() { console.log('參數是一個number值'); }) .then(null, function() { console.log('參數不是一個number值'); }) } fn('hahha'); fn(1234);
then(null, function() {}) 就等同於catch(function() {})
catch的用法
我們知道Promise對象除了then方法,還有一個catch方法,它是做什麼用的呢?其實它和then的第二個參數一樣,用來指定reject的回調,用法是這樣:getNumber() .then(function(data){ console.log('resolved'); console.log(data); }) .catch(function(reason){ console.log('rejected'); console.log(reason); });
效果和寫在then的第二個參數裡面一樣。不過它還有另外一個作用:在執行resolve的回調(也就是上面then中的第一個參數)時,如果拋出異常了(代碼出錯了),那麼並不會報錯卡死js,而是會進到這個catch方法中。請看下麵的代碼:
getNumber() .then(function(data){ console.log('resolved'); console.log(data); console.log(somedata); //此處的somedata未定義 }) .catch(function(reason){ console.log('rejected'); console.log(reason); });在resolve的回調中,我們console.log(somedata);而somedata這個變數是沒有被定義的。如果我們不用Promise,代碼運行到這裡就直接在控制台報錯了,不往下運行了。但是在這裡,會得到這樣的結果: 也就是說進到catch方法裡面去了,而且把錯誤原因傳到了reason參數中。即便是有錯誤的代碼也不會報錯了,這與我們的try/catch語句有相同的功能。
all的用法
Promise的all方法提供了並行執行非同步操作的能力,並且在所有非同步操作執行完後才執行回調。我們仍舊使用上面定義好的runAsync1、runAsync2、runAsync3這三個函數,看下麵的例子:Promise .all([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });用Promise.all來執行,all接收一個數組參數,裡面的值最終都算返回Promise對象。這樣,三個非同步操作的並行執行的,等到它們都執行完後才會進到then裡面。那麼,三個非同步操作返回的數據哪裡去了呢?都在then裡面呢,all會把所有非同步操作的結果放進一個數組中傳給then,就是上面的results。所以上面代碼的輸出結果就是: 有了all,你就可以並行執行多個非同步操作,並且在一個回調中處理所有的返回數據,是不是很酷?有一個場景是很適合用這個的,一些游戲類的素材比較多的應用,打開網頁時,預先載入需要用到的各種資源如圖片、flash以及各種靜態文件。所有的都載入完後,我們再進行頁面的初始化。
race的用法
all方法的效果實際上是「誰跑的慢,以誰為準執行回調」,那麼相對的就有另一個方法「誰跑的快,以誰為準執行回調」,這就是race方法,這個詞本來就是賽跑的意思。race的用法與all一樣,我們把上面runAsync1的延時改為1秒來看一下:Promise .race([runAsync1(), runAsync2(), runAsync3()]) .then(function(results){ console.log(results); });這三個非同步操作同樣是並行執行的。結果你應該可以猜到,1秒後runAsync1已經執行完了,此時then裡面的就執行了。結果是這樣的: