iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 30
0
自我挑戰組

邁向 JavaScript 核心之路 系列 第 30

[Day 30] JavaScript ES6 語法- 類別與非同步處理

  • 分享至 

  • xImage
  •  

類別 (Class)

ES6 類別語法並沒有改變 JavaScript 基於 prototype-based 的本質,只是在此提供一種語法糖,主要的目的是可以更貼近傳統物件導向語言的寫法。

今天,我們同樣會分別使用 ES5、ES6 來讓各位看一下差異


    // ES5
    
    function Shape1(id, x, y) {
        this.id = id;
        this.move(x, y);
    };
    
    Shape1.prototype.move = function(x, y) {
        this.x = x;
        this.y = y;
    };
    
    var a = new Shape1("a", 0, 0);
    
    console.log(a); // Shape1 {id: "a", x: 0, y: 0}
    
    // ES6
    
    class Shape2 {
        constructor(id, x, y) {
            this.id = id
            this.move(x, y)
        }
        move(x, y) {
            this.x = x
            this.y = y
        }
    }
    
    var a = new Shape2("a", 0, 0);
    
    console.log(a); // Shape2 {id: "a", x: 0, y: 0}

ES6 中繼承更簡潔。


    // ES5
    
    Function.prototype.inherits = function(superCtor) {
        Object.setPrototypeOf(this.prototype, superCtor.prototype);
        this.super = superCtor.prototype;
    };
    
    function Shape1(id, x, y) {
        this.id = id;
        this.move(x, y);
    };
    
    Shape1.prototype.move = function(x, y) {
        this.x = x;
        this.y = y;
    };
    
    function Rectangle1(id, x, y, width, height) {
        Shape1.call(this, id, x, y);
        this.width = width;
        this.height = height;
    };
    
    Rectangle1.inherits(Shape1);
    function Circle1(id, x, y, radius) {
        Shape1.call(this, id, x, y);
        this.radius = radius;
    };
    
    Circle1.inherits(Shape1);
    
    var a = new Rectangle1("a", 0, 0, 100, 100);
    console.log(a); // Rectangle1 {id: "a", x: 0, y: 0, width: 100, height: 100}
    
    var b = new Circle1("b", 0, 0, 100);
    console.log(b); // Circle1 {id: "b", x: 0, y: 0, radius: 100}
    
    // ES6
    
    class Shape2 {
        constructor(id, x, y) {
            this.id = id
            this.move(x, y)
        }
        move(x, y) {
            this.x = x
            this.y = y
        }
    }
    
    class Rectangle2 extends Shape2 {
        constructor(id, x, y, width, height) {
            super(id, x, y)
            this.width = width
            this.height = height
        }
    }
    
    class Circle2 extends Shape2 {
        constructor(id, x, y, radius) {
            super(id, x, y)
            this.radius = radius
        }
    }
    
    var a = new Rectangle2("a", 0, 0, 100, 100);
    console.log(a); // Rectangle2 {id: "a", x: 0, y: 0, width: 100, height: 100}
    
    var b = new Circle2("b", 0, 0, 100);
    console.log(b); // Circle2 {id: "b", x: 0, y: 0, radius: 100}

原型方法則是透過 super.move(x,y) 就能呼叫父類別的原型方法。

ES6 是不是把繼承變得更簡潔了呢?

接下來,讓我們來看一下 Getter 與 Setter 寫法上有什麼差異吧!


    // ES5
    
    function Rectangle1(width) {
        this._width = width;
    };
    
    Rectangle1.prototype = {
        set width(width) {
            this._width = width;
        },
        get width() {
            return this._width;
        }
    };
    
    var r = new Rectangle1(50);
    r.width = 1000;
    
    console.log(r.width); // 1000
    
    // ES6
    
    class Rectangle2 {
        constructor(width) {
            this._width = width
        }
        set width(width) {
            this._width = width
        }
        get width() {
            return this._width
        }
    }
    
    var r = new Rectangle2(50)
    r.width = 1000;
    
    console.log(r.width); // 1000

非同步處理 (Promise&Async...Await)

JavaScript 會產生非同步的方法有 setTimeout、setInterval、XMLHttpRequest、fetch、Promise...等等。

在 ES5 我們處理非同步行為會使用 Callback ,但這會讓程式碼難以維護,相信大家對於波動拳應該不陌生 XDD, 而 ES6 提供了 Promise 是將非同步行為統一相同介面接口,透過 then 或 catch 去接收或捕捉非同步的結果,因此不會產生多層的 Callback 結構。
ES7 開始支援 Async...Await 語法讓程式碼閱讀起來都是同步的,但實際上他仍是用非同步處理。

說了這麼多,還是讓我們先看一段程式碼吧!


    // ES5
    
    function asyncTask1(success, time, callback) {
        setTimeout(function() {
            if (success) {
                callback(true, time);
            } else {
                callback(false, time);
            }
        }, time);
    }
    
    // 非同步同時完成
    
    function fetchAll(tasks, success, error) {
        var results = Array(tasks.length);
        var count = 0;
        for (var i = 0; i < tasks.length; i++) {
            (function(i) {
                tasks[i](function(resolve, data) {
                    if (resolve) {
                        count++;
                        results[i] = data;
                        if (count === tasks.length) {
                            success(results);
                        }
                    } else {
                        error(data);
                    }
                });
            })(i);
        }
    }
    
    fetchAll([
        function(cb) { asyncTask1(true, 1000, cb) },
        function(cb) { asyncTask1(true, 5000, cb) },
        function(cb) { asyncTask1(true, 2500, cb) }    
    ],function(data) {
        console.log("ok:", data);
    },function(err) {
        console.log("err:", err);
    });
    
    // 非同步依序完成
    
    asyncTask1(true, 1000, function(resolve, data) {
        resolve && console.log("ok:", data);
        resolve && asyncTask1(true, 5000, function(resolve, data) {
            resolve && console.log("ok:", data);
            resolve && asyncTask1(true, 2500, function(resolve, data) {
                resolve && console.log("ok:", data);
            });
        });
    });
    
    // ES6
    
    function asyncTask2(success, time) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (success) {
                    resolve(time);
                } else {
                    reject(time);
                }
            }, time);
        });
    }
    
    // 非同步同時完成
    
    Promise.all([
        asyncTask2(true, 1000),
        asyncTask2(true, 5000),
        asyncTask2(true, 2500)
    ]).then(data => {
        console.log("ok:", data);
    }, err => {
        console.log("err:", err);
    });
    
    // 非同步依序完成
    
    asyncTask2(true, 1000).then(result => {
        console.log(result);
        return asyncTask2(true, 5000);
    }).then(result => {
        console.log(result);
        return asyncTask2(true, 2500);
    }).then(result => {
        console.log(result);
        console.log("依序完成");
    }).catch(err => {
        console.log("err:" + err);
    });
    
    // ES7 的 Async 和 Await 用法
    
    (async () => {
    
        console.log("begin");
        
        var a = await asyncTask2(true, 1000);
        console.log("ok:", a);
   
        var b = await asyncTask2(true, 5000);
        console.log("ok:", b);
        
        var c = await asyncTask2(true, 2500);
        console.log("ok:", c);
        
        console.log("end");
    })()
    
    console.log("other code");

對於 Promise.all 覺得陌生的讀者別緊張,我會在下方提供相關參考的資料,相信看完以上範例的朋友,都會覺得越往下看越直覺越好懂,對吧?


鐵人賽在這邊也正式告一個段落了,很慶幸一個月前的自己衝動地做了這個決定,我覺得鐵人賽是一段自我驗證的旅程,在這段旅程中,逐漸增強自己的信心、改善了拖延的習慣,也藉由這次的系列文,我發現了自己的幾個缺點

  1. 思路上還不夠清晰 - 常常想描述很多東西,但每個概念都只描述到一點,顯得雜亂無章。
  2. 發文的時間點 - 因為沒有存稿的關係,所以常常都是下班吃完飯開始寫,好幾次都是壓線發文 XDD,也藉此發現大概在七、八點左右發的文,觀看數會比較好,趕在十二點發的文,通常閱讀數就沒這麼漂亮,所以希望明年能改善壓底線的狀況。
  3. 過多的範例程式碼 - 在描述一些 JavaScript 概念時,常常使用過多的程式碼來去描述,覺得這樣似乎太嚴肅,讀者不易懂,可以參雜一些圖片來改善。
  4. 錯字 - 常常被同事糾正 在、再不分 XDD
  5. 相關知識量不足 - 曾經在一本書上看到這樣的概念,靈感是一張充滿記憶點的網,一旦靈感來了,不用打草稿也能生出滿意的文章,而我常常坐在電腦前好幾個小時,打了一大串文字後,刪刪減減,到最後沒生出一個字 囧。

希望明年的鐵人賽,我能針對這些缺點做改進,造就更強悍的自己,謝謝各位。( 下台一鞠躬

參考資料:

Tommy - 深入 JavaScript 核心課程

MDN - Promise

MDN - async function


上一篇
[Day 29] JavaScript ES6 語法- 增強物件屬性與解構分配
系列文
邁向 JavaScript 核心之路 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言