BACK
Featured image of post Javascript ES6 特性

Javascript ES6 特性

ES 是 ECMAScript 的簡稱,ECMAScript 是腳本語言的規範。而我們所使用的 JavaScript 是 ECMAScript 的一種實現。

【尚硅谷】ES6教程 - 涵盖 ES6~ES11
給進入 Vue.js 前的 ES6 必備知識
超级实用的 ES6 特性
【詳細 MDN 文件】


ECMAScript

  • ESECMAScript 的簡稱,ECMAScript 是腳本語言的規範。而我們所使用的 JavaScriptECMAScript 的一種實現。
  • 簡言之:ECMA做出規範,各瀏覽器依照規範做出實現,因而不同瀏覽器會有兼容性不同的情況。
  • ES6ES 的經典版本,是前端工程師崗位的高頻需求,是前端開發工程師求職的必備技能。
  • 現階段前端行業發展迅猛,前端技術也在高速迭代, ES6-ES11 規范增加了很多 JavaScript 新特性。 ES 新特性已經成為前端技術發展的趨勢,語法簡潔,功能豐富,部分特性還有性能提升,前端開發三大框架 VueReactAngular 都用到了大量的新特性代碼,框架的升級也在向著新特性語法靠攏。

varletconst

  • varletconst 在 Javascript 都是用來宣告變數的語法,最大的差別是他們的scope(變數有效範圍)的不同。切分var作用範圍的最小單位為 function,而 letconst 的作用範圍是 block 也就是俗稱的大括號:{ } 來切分。
  • const 所宣告的變數還有另一個特性是無法被重新賦值 (re-assign)。

let 特性

  1. var 可以重複聲明,但 let 不能重複聲明。
演示:
1
2
3
4
5
var testA = "AAA";
var testA = "BBB"; // 不會報錯

let testB = "AAA";
let testB = "BBB"; // 會報錯
  1. let 為塊級作用域
  • 塊級作用域:變數只在代碼塊裡面有效({ ... }if elsewhilefor)
  • 在 ES5 中,作用域有:全局、函數、eval(嚴格模式下)
演示:
1
2
3
4
5
6
7
8
9
{
    var b = "BBB";
}
console.log(b); // "BBB",因為 var 非塊級作用域,所以聲明時會往外層(全局window)添加這個屬性

{
    let a = "AAA";
}
console.log(a); // 會報錯 a is not defined
  1. 不存在變數提升
  • 變數提升:代碼執行前會先進行變數搜集,var 聲明的變量在搜集時會先定義一個 undefined 的初始值。
演示:
1
2
3
4
5
6
7
// 在 a 用 var 聲明前輸出
console.log(a); // 不會報錯,會輸出 undefined
var a = "AAA";

// 在 b 用 let 聲明前輸出
console.log(b); // 會報錯,Cannot access 'b' before initialization
let b = "BBB";
  1. 不影響作用域鏈
演示:
1
2
3
4
5
6
7
{
    let school = "尚硅谷";
    function fn() {
        console.log(school);
    }
    fn(); // 輸出 "尚硅谷",在 fn 內沒有 school,會往外層尋找
}

let 經典範例實踐

  • 實作點擊 div 時切換顏色
代碼:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<html>
    <body>
        <div class="container">
            <h2 class="page-header">
                點擊切換顏色
            </h2>
            <div class="item"></div>
            <div class="item"></div>
            <div class="item"></div>
        </div>
        <script>
            // 獲取 div 元素對象
            let items = document.getElementsByClassName("item");
            
            // 遍歷並綁定事件
            for (var i = 0; i< items.length; i++) {
                item[i].onclick = function() {
                    // 修改當前元素的背景顏色
                    // 最佳寫法應為:
                    this.style.background = "pink";
                    
                    // 常見錯誤寫法:
                    // items[i].style.background = "pink";
                    // 
                    // 原因:
                    // i 使用 var 來聲明,var 聲明的變數非塊級作用域,
                    // 因此 i 被聲明在全局(window),此處取 i 會發現 window.i 已經等於 3
                    // 所以 items[3].style 會找不到。
                    // 
                    // 修正方式:
                    // 將 i 改用 let 來聲明,讓 i 只存在於 for 迴圈中。
                }
            }
        </script>
    </body>
</html>

const 特性

  1. 一定要賦初始值,且聲明後值不能被修改。
  2. 一般常數使用大寫(淺規則)。
  3. 也是塊級作用域
演示:
1
2
3
4
5
{
    const PLAYER = "UZI";
}

console.log(PLAYER); // 會報錯,PLAYER is not defined
  1. 對於 Array 和 Object 的元素修改,不算對常數的修改,不會報錯。
演示:
1
2
3
4
{
    const TEAM = ["UZI", "MXLG", "Ming", "Letme"];
    TEAM.push("Meiko"); // 不會報錯,因為變數所指向的地址沒有改變
}

函式的參數默認值

  • 在 ES5 中必須這麼寫:
1
2
3
4
function printText(text) {
    text = text || "default";
    console.log(text);
}
  • 在 ES6 以後可以這樣簡寫屬性:
1
2
3
function printText(text = "default") {
    console.log(text);
}

二進制與八進制字面量

  • ES6 支持二進制與八進制的字面量,通過在數字前面添加 0o 或者 0O 即可將其轉換為八進制值、添加 0b 或者 0B 即可將其轉換為二進制值
1
2
3
4
5
6
7
let oValue = "0o10";
console.log(oValue);
// >>> 8

let bValue = 0b10;
console.log(bValue);
// >>> 2

ES Module 與 import 、 export

  • Javascript 自從 ES6 開始新增了模組系統(ES Module),我們可以將每個 Javascript 的檔案當作是一個獨立的模組來看待,在 A 檔案匯出(export)在 B 檔案匯入(import)。
a.js
1
2
3
4
5
6
7
export const aString = "This is A String";

export function aFunction() {
    console.log("A Function test")
}

export const aObject = { a: 1 };
b.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { aString, aFunction, aObject } from "./a.js";

console.log(aString);
// >>> "This is A String"

console.log(aObject);
// >>> { a: 1 }

aFounction();
// >>> "A Function test"
  • 當然在 export 也可以不用給變數名稱:
c.js
1
2
3
export default function() {
    console.log("Hello 008 JS!!!");
}
  • 在另一個檔案就可以這樣來使用:
d.js
1
2
3
4
import greeting from "./c.js";

greeting();
// >>> "Hello 008 JS!!!"

箭頭函數與 this

  • 從 ES6 開始新增了一種叫做 「箭頭函式表示式」 (Arrow Function expression) 的函式表達式。快速看一下,如何將一般的函式轉換成箭頭函式的寫法:
1
2
3
const plus = function(numA, numB) {
    return numA + numB;
};
  • 首先我們把參數往前提,然後把關鍵字 function 刪掉改成箭頭符號 =>
1
2
3
const plus = (numA, numB) => {
    return numA + numB;
};
  • 如果這個函式只是想要回傳某個運算結果的時候,可以將 return 以及大括號 { } 省略:
1
const plus = (numA, numB) => numA + numB;
  • 而只有一個參數的時候,參數前面的小括號( )則可以省略:
1
2
3
4
const saySomething = msg => console.log(msg);

saySomething("Hello!");
// >>> "Hello!"
  • 另外需要注意的是,在箭頭函式使用 this 時,這時 this指向箭頭函式外面的 this,這個規則與原本 function 所宣告的函式不同,而且箭頭函式無法透過 bind() 強制指定裡面的 this

字串模板 (Template literals)

  • 以往我們在組合 JavaScript 的變數與 HTML 模板的時候,大多會透過「字串結合」 + 的模式,或透過陣列來新增字串,最後再用 [].join("") 的方式串接起來。但自 ES6 起,我們可以透過字串模板的語法,將變數、運算式等插入至我們的網頁模板當中,像這樣:
1
2
// 用「`...`」取代單/雙引號
`string text ${expression} string text`
  • 這樣我們就可以將這個 expression 所代表的運算式或數值置入到字串裡頭了。

解構賦值 (Destructuring assignment)

  • ES6 提供了解構賦值的語法,可以將陣列或者物件裡面的資料解開變成獨立的變數:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const user = {
    id: 42,
    displayName: "jdoe",
    fullName: {
        firstName: "John",
        lastName: "Doe"
    }
};

const { id, displayName, fullName } = user;

console.log(id);
// >>> 42

console.log(displayName);
// >>> "jdoe"

console.log(fullName);
// >>> { firstName: "John", lastName: "Doe" }
  • 除了物件以外,陣列也可以:
1
2
3
4
5
6
7
8
9
const number = [1, 2, 3, 4, 5];

const [x, y] = number;

console.log(x);
// >>> 1

console.log(y);
// >>> 2

... 展開運算子 (Spread Operator) / 其餘運算子 (Rest Operator)

  • 雖然 ES6 提供的展開運算子與其餘運算子的語法都是 ...,不過它們兩者所代表的涵意還是不太一樣。

展開運算子

  • 展開運算子通常會用在陣列,或者是函式的參數,如:
1
2
3
4
5
const frameworks = ["Vue.js", "Angular", "React"];
const arr = ["Awesome", ...frameworks];

console.log(arr);
// >>> ["Awesome", "Vue.js", "Angular", "React"]

其餘運算子

  • 延續前面的例子,我們可以透過 「其餘運算子」 將剩下的部分拆解出來:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
console.log(arr);
// >>> ["Awesome", "Vue.js", "Angular", "React"]

const [a, b, ...others] = arr;

console.log(a);
// >>> "Awesome"

console.log(b);
// >>> "Vue.js"

console.log(others);
// >>> "Angular", "React"
  • 像這樣,我們可以搭配解構賦值的語法,將 arr 陣列拆解處來,並將剩餘的元素透過 ...others 分離。
  • 當然,使用在物件上也是可以的:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 其餘 Properties
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };

console.log(x);
// >>> 1
console.log(y);
// >>> 2
console.log(z);
// >>> { a: 3, b: 4 }


// 展開 Properties
const obj = { x, y, ...z };

console.log(obj);
// >>> { x: 1, y: 2, a: 3, b: 4 }
  • 要注意的是,其餘運算子所分離的部分只是陣列或物件的「淺拷貝」,若在多層物件使用時要特別小心。

Promise 物件

  • 為了解決過去同步與非同步的問題,ES6 提供了 Promise 物件:
1
2
3
4
const myPromiseFunc = new Promise((resolve, reject) => {
    resolve(someValue);             // 完成
    // reject("failure reason");    // 拒絕
});
  • Promise 的任務被完成的時候,我們就可以呼叫 resolve(),然後將取得的資料傳遞出去。 或是說想要拒絕這個 Promise,那麼就裡面呼叫 reject() 來拒絕他。
1
2
3
4
5
6
7
8
9
function myAsyncFunction(url) {
    return new Promise((resolve, reject) => {
        // resolve() or reject()
    });
}

// 透過 .then() 來取代過去的 callback hell
myAsyncFunction(...)
    .then(() => { ... });

asyncawait

  • 在後來,從 Promise 物件又延伸出 asyncawait 兩個新特性,其實本質上是更簡便的語法糖。

假設我們有兩個非同步任務要處理,並且我們希望在 asyncFunc1 執行完成之後才去執行 asyncFunc2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function asyncFunc1(url) {
    return new Promise((resolve, reject) => {
        // resolve() or reject()
    });
}

function asyncFunc2(url) {
    return new Promise((resolve, reject) => {
        // resolve() or reject()
    });
}

const asyncCall = async() => {
    const result1 = await asyncFunc1();
    const result2 = await asyncFunc2();
};

像這樣,透過 asyncawait 我們就可以擺脫過去一層層 callback 的惡夢,程式碼也更加簡潔。


簡寫屬性

  • 在 ES5 中必須這麼寫:
1
2
3
4
5
6
function createCoord(x, y) {
    return {
        x: x,
        y: y
    }
}
  • 在 ES6 以後可以這樣簡寫屬性:
1
2
3
4
5
6
function createCoord(x, y) {
    return {
        x,
        y
    }
}

方法屬性

  • 在 ES5 中必須這麼寫:
1
2
3
4
5
const math = {
    add: function(a, b) { return a + b; },
    sub: function(a, b) { return a - b; },
    multiply: function(a, b) { return a * b; }
}
  • 在 ES6 以後可以這樣簡寫屬性:
1
2
3
4
5
const math = {
    add(a, b) { return a + b; },
    sub(a, b) { return a - b; },
    multiply(a, b) { return a * b; }
}

陣列方法

ES6 引入了許多有用的陣列方法,例如:

  • find():查找陣列中的成員,返回 null 表示沒找到
  • findIndex():查找陣列成員的索引
  • some():檢查某個斷言是否至少一個成員在陣列中
  • includes:陣列是否包含某項目
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const array = [{ id: 1, checked: true }, { id: 2 }];

arr.find(item => item.id === 2)
// >>> { id: 2 }

arr.findIndex(item => item.id === 2)
// >>> 1

arr.some(item => item.checked)
// >>> true

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

numberArray.includes(2);
// >>> true

ES6 的 class

  • ES6 支持 class 語法,但不是新的對象繼承模型,只是原型鍊的語法糖。

函式中使用 static 關鍵字定義構造函式的方法與屬性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Student {
    constructor() {
        console.log("I'm a student.");
    }
    
    study() {
        console.log("study!");
    }
    
    static read() {
        console.log("Reading Now.");
    }
}

console.log(typeof Student);
// >>> Function

let stu = new Student();
// >>> "I'm a student."

stu.study();
// >>> "study!"

stu.read();
// >>> "Reading Now."

class 的繼承(extends)

  • extends 允許一個子類繼承父類,需要注意的是,子類的 constructor 函式中需要執行 supre() 函式。
  • 當然你也可以在子類方法中調用父類的方法,如 supre.parentMethodName()
  • class 的聲明不會提升 hoisting ,如果你要使用某個 class ,那你必須在使用之前定義他,否則會拋出 reference error 的錯誤。
  • class 中定義函式不需要使用 function 關鍵字。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Phone {
    constructor() {
        console.log("I'm a phone.");
    }
}

class MI extends Phone {
    constructor() {
        supre();
        console.log("I'm a phone designed by xiaomi.");
    }
}

let mi8 = new MI();
// >>> "I'm a phone."
// >>> "I'm a phone designed by xiaomi."

classsuper 方法

  • super 關鍵字被使用於通過函式存取父層

【詳細 MDN 文件】

語法

1
2
super([arguments]); // calls the parent constructor.
super.functionOnParent([arguments]);
  • 當使用建構子super 關鍵字必須出現this 關鍵字之前使用,super 關鍵字也可以使用在呼叫函式與父對象。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let parent = {
    foo() {
        console.log("Hello from the Parent");
    }
}

let child = {
    foo() {
        super.foo();
        console.log("Hello from the Child");
    }
}

Object.setPrototypeOf(child, parent);
child.foo();
// >>> Hello from the Parent
// >>> Hello from the Child

非同步處理工具 - Generator(生成器函式)

【詳細 MDN 文件】

語法

1
2
3
4
5
6
7
8
function* gen() {
    yield 1;
    yield 2;
    yield 3;
}

let g = gen();
// "Generator { }"

方法

範例:一個無限迭代器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function* idMaker() {
    let index = 0;
    while(true)
        yield index++;
}

let gen = idMaker();    // "Generator { }"

console.log(gen.next().value);
// >>> 0

console.log(gen.next().value);
// >>> 1

console.log(gen.next().value);
// >>> 2
// ...

生成器對象

  • Generator.prototype.next():返回 yield 表達式生成的值。
  • Generator.prototype.close():關閉生成器,因此執行該函式後調用next()方法時將會拋出 StopIteration 錯誤。
  • Generator.prototype.send():用於將值發送到生成器。該值由yield表達式返回,並且返回下一個yield表達式生成的值。
  • Generator.prototype.throw():向生成器拋出錯誤。

comments powered by Disqus