es6 相容性 如何使用babel來編譯我們的js(es6)程式碼
首先來談一下es6 es6在大多數情況是沒問題,據瞭解國內的環境,大部分業務(銀行系統除外)支援到IE8就可以了,如果要相容到IE6+,可以藉助babel外掛來完成,只要babel支援的都沒問題的。
ES6新特性在Babel下的相容性列表
ES6特性 | 相容性 |
---|---|
箭頭函式 | 支援 |
類的宣告和繼承 | 部分支援,IE8不支援 |
增強的物件字面量 | 支援 |
字串模板 | 支援 |
解構 | 支援,但注意使用方式 |
引數預設值,不定引數,擴充引數 | 支援 |
let與const | 支援 |
for of | IE不支援 |
iterator, generator | 不支援 |
模組 module、Proxies、Symbol | 不支援 |
Map,Set 和 WeakMap,WeakSet | 不支援 |
Promises、Math,Number,String,Object 的新API | 不支援 |
export & import | 支援 |
生成器函式 | 不支援 |
陣列拷貝 | 支援 |
那麼我們來說一下如何使用這個babel來相容我們的es6 程式碼(當然前提是babeljs檔案已經存在在專案中,這裡只是說明如何在webpack中讓babel編譯我們的js或者是jsx檔案)
上一小小段程式碼:
module: {
loaders: [
//
{
test: /\.(jsx|js)$/,
loader: ['babel-loader'],
exclude: /node_modules/
}
]
}複製程式碼
一般處理我們的打包檔案,我們都會放在module下的loader裡去處理,上段程式碼中將以jsx/js為結尾的檔案都會做一個babel來編譯。
es6好用的新特性
1.var let const
在之前的js的版本中,我們多會選擇用var來宣告定義一個變數,這麼做的弊端會造成浪費和佔用了大量記憶體,在新的js版本es6中出現了兩個新的用來宣告識別符號的方式:let 和 const
其實很好理解let其實定義了一個擁有著自己程式碼塊的變數,所謂程式碼塊其實就是當你在一個{}中使用let 定義一個變數後,let只在{}中存在,但是與js的函式作用域不同的是let定義的變數不會被提升。
var a = 1;
function fn1(){
console.log(a);
a = 2;
}
console.log(a);//1
fn1(); // 2
var b = 2;
function fn2(){
console.log(b);
let b = 1;
}
console.log(b)//2
fn2(); //複製程式碼
從這個例子中我們可以看到分別列印a,b變數 用let改的值在出了函式{}後失去了作用,而不是用let的賦值在出了{}後依然生效。
再舉一個好用的應用場景,我們在一些特定需求時,會避免不了使用閉包,不瞭解閉包請移步跨域 閉包 ,例如一個常見的爛大街的問題,一個ul裡的li列印它的索引或者內容。在es5中我們是這樣做的:
var arr = [];
for(var i=0; i<5; i++){
arr.push((function (a){
return function (){
console.log(a);
}
})(i))
}
arr[1]() // 1
arr[2]() // 2
arr[3]() // 3複製程式碼
閉包的缺點時會佔用記憶體不是放掉,那麼我們來換一種寫法:
var arr = []; // let arr = [] 都可以
for(let i=0; i<5; i++){
arr.push(function (){
console.log(i)
})
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2複製程式碼
使用let會完美的解決這個問題。
再說說const就很好理解了,它就是定義一個常量的宣告方式,擁有其他語言基礎這個概念就不難理解,再次就不在做多餘贅述。
2.解構賦值
ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。
let [a, b, c] = [1, 2, 3];
//等同於
let a = 1;
let b = 2;
let c = 3;複製程式碼
物件的解構賦值:獲取物件的多個屬性並且使用一條語句將它們賦給多個變數。
var {
StyleSheet,
Text,
View
} = React;
等同於
var StyleSheet = React.StyleSheet;
var Text = React.Text;
var View = React.View;複製程式碼
3.箭頭函式
ES6中新增箭頭操作符用於簡化函式的寫法,操作符左邊為引數,右邊為具體操作和返回值。
var sum = (num1, num2) => { return num1 + num2; }
//等同於
var sum = function(num1, num2) {
return num1 + num2;
};複製程式碼
同時箭頭函式還有修復了this的指向,使其永遠指向詞法作用域:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj物件
return fn();
}
};
obj.getAge(); // 25複製程式碼
4.類 class
ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為物件的模板。通過class關鍵字,可以定義類,與多數傳統語言類似。
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}複製程式碼
同樣的繼承使用的是extends關鍵字
5.字串
新增字串處理函式:
startsWith() //檢查是否以指定字串開頭,返回布林值
let str = 'javascript';
str.startsWith('java');
endsWith() //檢查是否以指定字串結尾,返回布林值
let str = 'javascript'
str.endsWith('pt');
includes() //檢查字串是否包含指定字串 ,返回布林值
let str = 'javascript';
str.includes('j');
repeat() //指定字串重複次數
let str = 'javascript';
str.repeat('3');複製程式碼
Template Literals 字串模板
使用 字串模板字面量,我可以在字串中直接使用特殊字元,而不用轉義。
var text = "This string contains \"double quotes\" which are escaped.";
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;複製程式碼
字串模板字面量 還支援直接插入變數,可以實現字串與變數的直接連線輸出.
var name = 'Tiger';
var age = 13;
console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
更簡單的版本:
const name = 'Tiger';
const age = 13;
console.log(`My cat is named ${name} and is ${age} years old.`);複製程式碼
ES5中,我們要這樣生成多行文字:
var text = (
'cat\n' +
'dog\n' +
'nickelodeon'
);
或者:
var text = [
'cat',
'dog',
'nickelodeon'
].join('\n');複製程式碼
字串模板字面量 讓我們不必特別關注多行字串中的換行轉義符號,直接換行即可:
let text = ( `cat
dog
nickelodeon`
);複製程式碼
字串模板字面量 內部可以使用表示式,像這樣:
let today = new Date();
let text = `The time and date is ${today.toLocaleString()};複製程式碼
6.第七種基本型別 Symbol
Symbols和其它基本型別大不一樣。
從建立開始就是不可變的。你不能為它設定屬性(如果你在嚴謹模式下嘗試,會報型別錯誤)。它可以作為屬性名。這是它的類字串性質。
另一方面,每一個symbol都是唯一的。與其他的不同(就算他們的描述是一樣的)你可以很容易地新建立一個。這是它的類物件性質。
ES6 symbols與Lisp和Ruby中的更傳統的symbols很類似,但是沒有如此緊密地整合到語言中。在Lisp中,所有的識別符號都是symbols。在JS中,識別符號和大多數屬性的鍵值的首先仍是字串,Symbols只是為開發人員提供了一個額外選擇。
關於symbols的一個忠告:與JS中的其它型別不同,它不能被自動轉換為字串。試圖拼接symbol與字串將會引起型別錯誤。
(表示讀過文件後依然蒙圈 先選擇性放棄這個點)
7.Promise
一個 Promise 物件可以理解為一次將要執行的操作(常常被用於非同步操作),使用了 Promise 物件之後可以用一種鏈式呼叫的方式來組織程式碼,讓程式碼更加直觀。而且由於 Promise.all 這樣的方法存在,可以讓同時執行多個操作變得簡單。接下來就來簡單介紹 Promise 物件。
直觀些直接上程式碼:
function helloWorld (ready) {
return new Promise(function (resolve, reject) {
if (ready) {
resolve("Hello World!");
} else {
reject("Good bye!");
}
});
}
helloWorld(true).then(function (message) {
alert(message);
}, function (error) {
alert(error);
});複製程式碼
上面的程式碼實現的功能非常簡單,helloWord 函式接受一個引數,如果為 true 就列印 "Hello World!",如果為 false 就列印錯誤的資訊。helloWord 函式返回的是一個 Promise 物件。
在 Promise 物件當中有兩個重要方法————resolve 和 reject。
resolve 方法可以使 Promise 物件的狀態改變成成功,同時傳遞一個引數用於後續成功後的操作,在這個例子當中就是 Hello World!字串。
reject 方法則是將 Promise 物件的狀態改變為失敗,同時將錯誤的資訊傳遞到後續錯誤處理的操作。
Promise 的三種狀態
上面提到了 resolve 和 reject 可以改變 Promise 物件的狀態,那麼它究竟有哪些狀態呢?
Promise 物件有三種狀態:
- Fulfilled 可以理解為成功的狀態
- Rejected 可以理解為失敗的狀態
- Pending 既不是 Fulfilld 也不是 Rejected 的狀態,可以理解為 Promise 物件例項建立時候的初始狀態
helloWorld 的例子中的 then 方法就是根據 Promise 物件的狀態來確定執行的操作,resolve 時執行第一個函式(onFulfilled),reject 時執行第二個函式(onRejected)。
then 和 catch
then
helloWorld 的例子當中利用了 then(onFulfilld, onRejected) 方法來執行一個任務列印 "Hello World!",在多個任務的情況下 then 方法同樣可以用一個清晰的方式完成。function printHello (ready) { return new Promise(function (resolve, reject) { if (ready) { resolve("Hello"); } else { reject("Good bye!"); } }); }複製程式碼
上述例子通過鏈式呼叫的方式,按順序列印出了相應的內容。then 可以使用鏈式呼叫的寫法原因在於,每一次執行該方法時總是會返回一個 Promise 物件。另外,在 then onFulfilled 的函式當中的返回值,可以作為後續操作的引數,因此上面的例子也可以寫成:
printHello(true).then(function (message) {
return message;
}).then(function (message) {
return message + ' World';
}).then(function (message) {
return message + '!';
}).then(function (message) {
alert(message);
});
function printWorld () {
alert("World");
}
function printExclamation () {
alert("!");
}
printHello(true)
.then(function(message){
alert(message);
})
.then(printWorld)
.then(printExclamation);複製程式碼
同樣可以列印出正確的內容。
catch
catch 方法是 then(onFulfilled, onRejected) 方法當中 onRejected 函式的一個簡單的寫法,也就是說可以寫成 then(fn).catch(fn),相當於 then(fn).then(null, fn)。使用 catch 的寫法比一般的寫法更加清晰明確。
Promise.all 和 Promise.race
Promise.all 可以接收一個元素為 Promise 物件的陣列作為引數,當這個陣列裡面所有的 Promise 物件都變為 resolve 時,該方法才會返回。
var p1 = new Promise(function (resolve) {
setTimeout(function () {
resolve("Hello");
}, 3000);
});
var p2 = new Promise(function (resolve) {
setTimeout(function () {
resolve("World");
}, 1000);
});
Promise.all([p1, p2]).then(function (result) {
console.log(result); // ["Hello", "World"]
});複製程式碼
上面的例子模擬了傳輸兩個資料需要不同的時長,雖然 p2 的速度比 p1 要快,但是 Promise.all 方法會按照陣列裡面的順序將結果返回。
日常開發中經常會遇到這樣的需求,在不同的介面請求資料然後拼合成自己所需的資料,通常這些介面之間沒有關聯(例如不需要前一個介面的資料作為後一個介面的引數),這個時候 Promise.all 方法就可以派上用場了。
還有一個和 Promise.all 相類似的方法 Promise.race,它同樣接收一個陣列,不同的是隻要該陣列中的 Promise 物件的狀態發生變化(無論是 resolve 還是 reject)該方法都會返回。
相容性
最後是關於 Promise 物件的相容性問題。
在瀏覽器端,一些主流的瀏覽器都已經可以使用 Promise 物件進行開發,在 Node.js 配合 babel 也可以很方便地使用。
如果要相容舊的瀏覽器,建議可以尋找一些第三方的解決方案,例如 jQuery 的 $.Deferred。