JavaScript ES6 核心功能一覽

JavaScript ES6 核心功能一覽(ES6 亦作 ECMAScript 6 或 ES2015+)

JavaScript 在過(guò)去幾年里發(fā)生了很大的變化。這里介紹 12 個(gè)你馬上就能用的新功能。

JavaScript 歷史

新的語(yǔ)言規(guī)范被稱作 ECMAScript 6。也稱為 ES6 或 ES2015+ 。

自從 1995 年 JavaScript 誕生以來(lái),它一直在緩慢地發(fā)展。每隔幾年就會(huì)增加一些新內(nèi)容。1997 年,ECMAScript 成為 JavaScript 語(yǔ)言實(shí)現(xiàn)的規(guī)范。它已經(jīng)有了好幾個(gè)版本,比如 ES3 , ES5 , ES6 等等。

如你所見(jiàn),ES3,ES5 和 ES6 之間分別存在著 10 年和 6 年的間隔。像 ES6 那樣一次進(jìn)行大幅修改的模式被逐年漸進(jìn)式的新模式所替代。

瀏覽器支持

所有現(xiàn)代瀏覽器和環(huán)境都已支持 ES6。

來(lái)源:https://kangax.github.io/compat-table/es6/

Chrome,MS Edge,F(xiàn)irefox,Safari,Node 和許多其他的環(huán)境都已內(nèi)置支持大多數(shù)的 JavaScript ES6 功能。所以,在本教程中你學(xué)到的每個(gè)知識(shí),你都可以馬上開(kāi)始應(yīng)用。

讓我們開(kāi)始學(xué)習(xí) ECMAScript 6 吧!

核心 ES6 功能

你可以在瀏覽器的控制臺(tái)中測(cè)試所有下面的代碼片段。

不要篤信我的話,而是要親自去測(cè)試每一個(gè) ES5 和 ES6 示例。讓我們開(kāi)始動(dòng)手吧??

變量的塊級(jí)作用域

使用 ES6,聲明變量我們可以用var,也可以用let或const。

var有什么不足?

使用var的問(wèn)題是變量會(huì)漏入其他代碼塊中,諸如for循環(huán)或if代碼塊。

// ES5

var x = 'outer';

function test(inner) {

if (inner) {

var x = 'inner'; // 作用于整個(gè) function

return x;

}

return x; // 因?yàn)榈谒男械穆暶魈嵘恢匦露x

}

test(false); // undefined ??

test(true); // inner

對(duì)于test(fasle),你期望返回outer,但是,你得到的是undefined。

為什么?

因?yàn)楸M管沒(méi)有執(zhí)行if代碼塊,第四行中的表達(dá)式var x也會(huì)被提升。

var提升

var是函數(shù)作用域。在整個(gè)函數(shù)中甚至是聲明語(yǔ)句之前都是可用的。

聲明被提升。所以你能在聲明之前使用一個(gè)變量。

初始化是不被提升的。如果你使用var聲明變量,請(qǐng)總是將它放在頂部。

在應(yīng)用了聲明提升規(guī)則之后,我們就能更容易地理解發(fā)生了什么:

```

// ES5

var x = 'outer';

function test(inner) {

var x; // 聲明提升

if (inner) {

x = 'inner'; // 初始化不被提升

return x;

}

return x;

}

```

ECMAScript 2015 找到了解決的辦法:

// ES6

let x = 'outer';

function test(inner) {

if (inner) {

let x = 'inner';

return x;

}

return x; // 從第一行獲取到預(yù)期結(jié)果

}

test(false); // outer

test(true); // inner

將var改為let,代碼將像期望的那樣運(yùn)行。如果if代碼塊沒(méi)有被調(diào)用,x變量也就不會(huì)在代碼塊外被提升。

let提升和“暫存死區(qū)(temporal dead zone)”

在 ES6 中,let將變量提升到代碼塊的頂部(不是像 ES5 那樣的函數(shù)頂部)。

然而,代碼塊中,在變量聲明之前引用它會(huì)導(dǎo)致ReferenceError錯(cuò)誤。

let是塊級(jí)作用域。你不能在它被聲明之前引用它。

“暫存死區(qū)(Temporal dead zone)”是指從代碼塊開(kāi)始直到變量被聲明之間的區(qū)域。

IIFE

在解釋 IIFE 之前讓我們看一個(gè)例子。來(lái)看一下:

// ES5

{

var private = 1;

}

console.log(private); // 1

如你所見(jiàn),private漏出(代碼塊)。你需要使用 IIFE(immediately-invoked function expression,立即執(zhí)行函數(shù)表達(dá)式)來(lái)包含它:

// ES5

(function(){

var private2 = 1;

})();

console.log(private2); // Uncaught ReferenceError

如果你看一看 jQuery/loadsh 或其他開(kāi)源項(xiàng)目,你會(huì)注意到他們用 IIFE 來(lái)避免污染全局環(huán)境而且只在全局中定義了諸如_,$和jQuery。

在 ES6 上則一目了然,我們可以只用代碼塊和let,也不再需要使用 IIFE了。

// ES6

{

let private3 = 1;

}

console.log(private3); // Uncaught ReferenceError

Const

如果你想要一個(gè)變量保持不變(常量),你也可以使用const。

總之:用let,const而不是var

對(duì)所有引用使用const;避免使用var。

如果你必須重新指定引用,用let替代const。

模板字面量

有了模板字面量,我們就不用做多余的嵌套拼接了。來(lái)看一下:

// ES5

var first = 'Adrian';

var last = 'Mejia';

console.log('Your name is ' + first + ' ' + last + '.');

現(xiàn)在你可以使用反引號(hào) (`) 和字符串插值${}:

// ES6

const first = 'Adrian';

const last = 'Mejia';

console.log(`Your name is ${first} ${last}.`);

多行字符串

我們?cè)僖膊恍枰砑?+\n來(lái)拼接字符串了:

// ES5

var template = '

\n' +

'?

\n' +

'? ? \n' +

'? ? \n' +

'? ? \n' +

'? \n' +

'? \n' +

'';

console.log(template);

在 ES6 上, 我們可以同樣使用反引號(hào)來(lái)解決這個(gè)問(wèn)題:

// ES6

const template = `

  • `;

    console.log(template);

    兩段代碼的結(jié)果是完全一樣的。

    解構(gòu)賦值

    ES6 的解構(gòu)不僅實(shí)用而且很簡(jiǎn)潔。如下例所示:

    從數(shù)組中獲取元素

    // ES5

    var array = [1, 2, 3, 4];

    var first = array[0];

    var third = array[2];

    console.log(first, third); // 1 3

    等同于:

    const array = [1, 2, 3, 4];

    const [first, ,third] = array;

    console.log(first, third); // 1 3

    交換值

    // ES5

    var a = 1;

    var b = 2;

    var tmp = a;

    a = b;

    b = tmp;

    console.log(a, b); // 2 1

    等同于:

    // ES6

    let a = 1;

    let b = 2;

    [a, b] = [b, a];

    console.log(a, b); // 2 1

    多個(gè)返回值的解構(gòu)

    // ES5

    function margin() {

    var left=1, right=2, top=3, bottom=4;

    return { left: left, right: right, top: top, bottom: bottom };

    }

    var data = margin();

    var left = data.left;

    var bottom = data.bottom;

    console.log(left, bottom); // 1 4

    在第 3 行中,你也可以用一個(gè)像這樣的數(shù)組返回(同時(shí)省去了一些編碼):

    return [left, right, top, bottom];

    但另一方面,調(diào)用者需要考慮返回?cái)?shù)據(jù)的順序。

    var left = data[0];

    var bottom = data[3];

    用 ES6,調(diào)用者只需選擇他們需要的數(shù)據(jù)即可(第 6 行):

    // ES6

    function margin() {

    const left=1, right=2, top=3, bottom=4;

    return { left, right, top, bottom };

    }

    const { left, bottom } = margin();

    console.log(left, bottom); // 1 4

    注意:在第 3 行中,我們使用了一些其他的 ES6 功能。我們將{ left: left }簡(jiǎn)化到只有{ left }。與 ES5 版本相比,它變得如此簡(jiǎn)潔。酷不酷?

    參數(shù)匹配的解構(gòu)

    // ES5

    var user = {firstName: 'Adrian', lastName: 'Mejia'};

    function getFullName(user) {

    var firstName = user.firstName;

    var lastName = user.lastName;

    return firstName + ' ' + lastName;

    }

    console.log(getFullName(user)); // Adrian Mejia

    等同于(但更簡(jiǎn)潔):

    // ES6

    const user = {firstName: 'Adrian', lastName: 'Mejia'};

    function getFullName({ firstName, lastName }) {

    return `${firstName} ${lastName}`;

    }

    console.log(getFullName(user)); // Adrian Mejia

    深度匹配

    // ES5

    function settings() {

    return { display: { color: 'red' }, keyboard: { layout: 'querty'} };

    }

    var tmp = settings();

    var displayColor = tmp.display.color;

    var keyboardLayout = tmp.keyboard.layout;

    console.log(displayColor, keyboardLayout); // red querty

    等同于(但更簡(jiǎn)潔):

    // ES6

    function settings() {

    return { display: { color: 'red' }, keyboard: { layout: 'querty'} };

    }

    const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();

    console.log(displayColor, keyboardLayout); // red querty

    這也稱作對(duì)象的解構(gòu)。

    如你所見(jiàn),解構(gòu)是非常實(shí)用的而且有利于促進(jìn)良好的編碼風(fēng)格。

    最佳實(shí)踐:

    使用數(shù)組解構(gòu)去獲取元素或交換值。它可以避免創(chuàng)建臨時(shí)引用。

    不要對(duì)多個(gè)返回值使用數(shù)組解構(gòu),而是要用對(duì)象解構(gòu)。

    類和對(duì)象

    用 ECMAScript 6,我們從“構(gòu)造函數(shù)”??來(lái)到了“類”??。

    在 JavaScript 中,每個(gè)對(duì)象都有一個(gè)原型對(duì)象。所有的 JavaScript 對(duì)象都從它們的原型對(duì)象那里繼承方法和屬性。

    在 ES5 中,為了實(shí)現(xiàn)面向?qū)ο缶幊蹋∣OP),我們使用構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象,如下:

    // ES5

    var Animal = (function () {

    function MyConstructor(name) {

    this.name = name;

    }

    MyConstructor.prototype.speak = function speak() {

    console.log(this.name + ' makes a noise.');

    };

    return MyConstructor;

    })();

    var animal = new Animal('animal');

    animal.speak(); // animal makes a noise.

    ES6 中有了一些語(yǔ)法糖。通過(guò)像class和constructor這樣的關(guān)鍵字和減少樣板代碼,我們可以做到同樣的事情。另外,speak()相對(duì)照constructor.prototype.speak = function ()更加清晰:

    // ES6

    class Animal {

    constructor(name) {

    this.name = name;

    }

    speak() {

    console.log(this.name + ' makes a noise.');

    }

    }

    const animal = new Animal('animal');

    animal.speak(); // animal makes a noise.

    正如你所見(jiàn),兩種式樣(ES5 與 6)在幕后產(chǎn)生相同的結(jié)果而且用法一致。

    最佳實(shí)踐:

    總是使用class語(yǔ)法并避免直接直接操縱prototype。為什么?因?yàn)樗尨a更加簡(jiǎn)潔和易于理解。

    避免使用空的構(gòu)造函數(shù)。如果沒(méi)有指定,類有一個(gè)默認(rèn)的構(gòu)造函數(shù)。

    繼承

    基于前面的Animal類。 讓我們擴(kuò)展它并定義一個(gè)Lion類。

    在 ES5 中,它更多的與原型繼承有關(guān)。

    // ES5

    var Lion = (function () {

    function MyConstructor(name){

    Animal.call(this, name);

    }

    // 原型繼承

    MyConstructor.prototype = Object.create(Animal.prototype);

    MyConstructor.prototype.constructor = Animal;

    MyConstructor.prototype.speak = function speak() {

    Animal.prototype.speak.call(this);

    console.log(this.name + ' roars ??');

    };

    return MyConstructor;

    })();

    var lion = new Lion('Simba');

    lion.speak(); // Simba makes a noise.

    // Simba roars.

    我不會(huì)重復(fù)所有的細(xì)節(jié),但請(qǐng)注意:

    第 3 行中,我們添加參數(shù)顯式調(diào)用了Animal構(gòu)造函數(shù)。

    第 7-8 行,我們將Lion原型指派給Animal原型。

    第 11行中,我們調(diào)用了父類Animal的speak方法。

    在 ES6 中,我們有了新關(guān)鍵詞extends和super

    // ES6

    class Lion extends Animal {

    speak() {

    super.speak();

    console.log(this.name + ' roars ??');

    }

    }

    const lion = new Lion('Simba');

    lion.speak(); // Simba makes a noise.

    // Simba roars.

    雖然 ES6 和 ES5 的代碼作用一致,但是 ES6 的代碼顯得更易讀。更勝一籌!

    最佳實(shí)踐:

    使用extends內(nèi)置方法實(shí)現(xiàn)繼承。

    原生 Promises

    從回調(diào)地獄??到 promises??。

    // ES5

    function printAfterTimeout(string, timeout, done){

    setTimeout(function(){

    done(string);

    }, timeout);

    }

    printAfterTimeout('Hello ', 2e3, function(result){

    console.log(result);

    // 嵌套回調(diào)

    printAfterTimeout(result + 'Reader', 2e3, function(result){

    console.log(result);

    });

    });

    我們有一個(gè)接收一個(gè)回調(diào)的函數(shù),當(dāng)done時(shí)執(zhí)行。我們必須一個(gè)接一個(gè)地執(zhí)行它兩次。這也是為什么我們?cè)诨卣{(diào)中第二次調(diào)用printAfterTimeout的原因。

    如果你需要第 3 次或第 4 次回調(diào),可能很快就會(huì)變得混亂。來(lái)看看我們用 promises 的寫(xiě)法:

    // ES6

    function printAfterTimeout(string, timeout){

    return new Promise((resolve, reject) => {

    setTimeout(function(){

    resolve(string);

    }, timeout);

    });

    }

    printAfterTimeout('Hello ', 2e3).then((result) => {

    console.log(result);

    return printAfterTimeout(result + 'Reader', 2e3);

    }).then((result) => {

    console.log(result);

    });

    如你所見(jiàn),使用 promises 我們能在函數(shù)完成后進(jìn)行一些操作。不再需要嵌套函數(shù)。

    箭頭函數(shù)

    ES6 沒(méi)有移除函數(shù)表達(dá)式,但是新增了一種,叫做箭頭函數(shù)。

    在 ES5 中,對(duì)于this我們有一些問(wèn)題:

    // ES5

    var _this = this; // 保持一個(gè)引用

    $('.btn').click(function(event){

    _this.sendData(); // 引用的是外層的 this

    });

    $('.input').on('change',function(event){

    this.sendData(); // 引用的是外層的 this

    }.bind(this)); // 綁定到外層的 this

    你需要使用一個(gè)臨時(shí)的this在函數(shù)內(nèi)部進(jìn)行引用或用bind綁定。在 ES6 中,你可以用箭頭函數(shù)。

    // ES6

    // 引用的是外部的那個(gè) this

    $('.btn').click((event) =>? this.sendData());

    // 隱式返回

    const ids = [291, 288, 984];

    const messages = ids.map(value => `ID is ${value}`);

    For…of

    從for到forEach再到for...of:

    // ES5

    // for

    var array = ['a', 'b', 'c', 'd'];

    for (var i = 0; i < array.length; i++) {

    var element = array[i];

    console.log(element);

    }

    // forEach

    array.forEach(function (element) {

    console.log(element);

    });

    ES6 的 for…of 同樣可以實(shí)現(xiàn)迭代。

    // ES6

    // for ...of

    const array = ['a', 'b', 'c', 'd'];

    for (const element of array) {

    console.log(element);

    }

    默認(rèn)參數(shù)

    從檢查一個(gè)變量是否被定義到重新指定一個(gè)值再到default parameters。 你以前寫(xiě)過(guò)類似這樣的代碼嗎?

    // ES5

    function point(x, y, isFlag){

    x = x || 0;

    y = y || -1;

    isFlag = isFlag || true;

    console.log(x,y, isFlag);

    }

    point(0, 0) // 0 -1 true ??

    point(0, 0, false) // 0 -1 true ????

    point(1) // 1 -1 true

    point() // 0 -1 true

    可能有過(guò),這是一種檢查變量是否賦值的常見(jiàn)模式,不然則分配一個(gè)默認(rèn)值。然而,這里有一些問(wèn)題:

    第 8 行中,我們傳入0, 0返回了0, -1。

    第 9 行中, 我們傳入false但是返回了true。

    如果你傳入一個(gè)布爾值作為默認(rèn)參數(shù)或?qū)⒅翟O(shè)置為 0,它不能正常起作用。你知道為什么嗎?在講完 ES6 示例后我會(huì)告訴你。

    用 ES6,現(xiàn)在你可以用更少的代碼做到更好!

    // ES6

    function point(x = 0, y = -1, isFlag = true){

    console.log(x,y, isFlag);

    }

    point(0, 0) // 0 0 true

    point(0, 0, false) // 0 0 false

    point(1) // 1 -1 true

    point() // 0 -1 true

    請(qǐng)注意第 5 行和第 6 行,我們得到了預(yù)期的結(jié)果。ES5 示例則無(wú)效。首先檢查是否等于undefined,因?yàn)閒alse,null,undefined和0都是假值,我們可以避開(kāi)這些數(shù)字,

    // ES5

    function point(x, y, isFlag){

    x = x || 0;

    y = typeof(y) === 'undefined' ? -1 : y;

    isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;

    console.log(x,y, isFlag);

    }

    point(0, 0) // 0 0 true

    point(0, 0, false) // 0 0 false

    point(1) // 1 -1 true

    point() // 0 -1 true

    當(dāng)我們檢查是否為undefined后,獲得了期望的結(jié)果。

    剩余參數(shù)

    從參數(shù)到剩余參數(shù)和擴(kuò)展操作符。

    在 ES5 中,獲取任意數(shù)量的參數(shù)是非常麻煩的:

    // ES5

    function printf(format) {

    var params = [].slice.call(arguments, 1);

    console.log('params: ', params);

    console.log('format: ', format);

    }

    printf('%s %d %.2f', 'adrian', 321, Math.PI);

    我們可以用 rest 操作符...做到同樣的事情。

    // ES6

    function printf(format, ...params) {

    console.log('params: ', params);

    console.log('format: ', format);

    }

    printf('%s %d %.2f', 'adrian', 321, Math.PI);

    展開(kāi)運(yùn)算符

    從apply()到展開(kāi)運(yùn)算符。我們同樣用...來(lái)解決:

    提醒:我們使用apply()將數(shù)組轉(zhuǎn)換為一列參數(shù)。例如,Math.max()作用于一列參數(shù),但是如果我們有一個(gè)數(shù)組,我們就能用apply讓它生效。

    正如我們較早之前看過(guò)的,我們可以使用apply將數(shù)組作為參數(shù)列表傳遞:

    // ES5

    Math.max.apply(Math, [2,100,1,6,43]) // 100

    在 ES6 中,你可以用展開(kāi)運(yùn)算符:

    // ES6

    Math.max(...[2,100,1,6,43]) // 100

    同樣,從concat數(shù)組到使用展開(kāi)運(yùn)算符:

    // ES5

    var array1 = [2,100,1,6,43];

    var array2 = ['a', 'b', 'c', 'd'];

    var array3 = [false, true, null, undefined];

    console.log(array1.concat(array2, array3));

    在 ES6 中,你可以用展開(kāi)運(yùn)算符來(lái)壓平嵌套:

    // ES6

    const array1 = [2,100,1,6,43];

    const array2 = ['a', 'b', 'c', 'd'];

    const array3 = [false, true, null, undefined];

    console.log([...array1, ...array2, ...array3]);

    總結(jié)

    JavaScript 經(jīng)歷了相當(dāng)多的修改。這篇文章涵蓋了每個(gè) JavaScript 開(kāi)發(fā)者都應(yīng)該了解的大多數(shù)核心功能。同樣,我們也介紹了一些讓你的代碼更加簡(jiǎn)潔,易于理解的最佳實(shí)踐。

  • 最后編輯于
    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
    平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

    推薦閱讀更多精彩內(nèi)容