淺談ES6 常用 新特性 並瞭解其相容性解決方案

DeronTan發表於2017-10-19

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。

相關文章