位元詩人 現代 [JavaScript] 程式設計教學:藉由繼承 (Inheritance) 重用程式碼

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在 JavaScript 中,繼承物件有以下兩種手法:

  • 原型 (prototype) 繼承
  • (新) 使用 extends 繼承

第一種手法是傳統的方法,第二種是 ES6 以後新增的語法糖;如果情境許可,應優先使用第二種手法,因為程式碼相對比較乾淨;相對來說,許有現存的程式碼還沒轉換到新的語法,所以還是要能讀傳統手法所寫的程式碼。

在本文中,我們會製作 PersonEmployee 兩個物件,後者會繼承前者。在本文中,我們會展示兩種手法,供讀者比較。

使用原型繼承

以下是製作 Person 物件的程式碼範例:

let Person = function (name, age) {
    var _age = 0.0;

    Object.defineProperty(this, "age", {
        get: function () {
            return _age;
        },
        set: function (value) {
            if (value <= 0) {
                throw "Invalid age";
            }

            _age = value;
        }
    });

    // Init the object.
    this.name = name;
    this.age = age;

    return this;
};

由於我們要將 age 限制在合理的範圍值內,故我們設置 age 屬性 (property),用來包覆私有屬性 (field) _age

接著,建立 Employee 物件,該物件會繼承 Person 的建構函式:

let Employee = function (name, age, salary) {
    // Inherit the constructor of `Person`.
    Person.call(this, name, age);

    var _salary = 0.0;

    Object.defineProperty(this, "salary", {
        get: function () {
            return _salary;
        },
        set: function (value) {
            if (value <= 0.0) {
                throw "Invalid salary";
            }

            _salary = value;
        }
    });

    // Init the object.
    this.salary = salary;

    return this;
};

這裡的關鍵在於使用 Person.call(this, name, age) 呼叫 Person 物件的建構函式,我們沒有在 Employee 建構函式中建立 nameage 屬性,但卻可以使用。

除了繼承建構函式外,也要繼承原型鍵:

// Inherit the prototype of `Person`
Employee.prototype = Object.create(Person.prototype);

但這時候 Employee 的建構函式會改成 Person 的建構函式,故要進行修正:

// Resume the constructor of `Employee`
Object.defineProperty(Employee, "constructor", {
    value: Employee, 
    enumerable: false,
    writable: true
});

透過這些步驟,我們就可以完整地將 Person 物件繼承到 Employee 物件中。

以下是使用 Employee 的範例:

let e = new Employee("Michelle", 30, 1000);
console.log(e.name);
console.log(e.age);
console.log(e.salary);

使用 extends 保留字繼承

在 ES6 之後,新增 extends 做為繼承物件的關鍵字,由於 JavaScript 本質上仍採用以原型為基礎的物件,可將 extends 視為一種語法糖。

class 建立 Person 物件:

let Person = (function () {
    let fields = new WeakMap();

    return class Person {
        constructor (name, age) {
            fields.set(this, {});

            this.name = name;
            this.age = age;
        }

        get age () {
            return fields.get(this).age;
        }

        set age (value) {
            if (value <= 0.0) {
                throw "Invalid age";
            }

            fields.get(this).age = value;
        }
    };
})();

如同先前的例子,我們用 fields 做為私有變數。

接著,我們用 class 製作 Employee 物件,該物件會透過 extends 繼承 Person

let Employee = (function () {
    let fields = new WeakMap();

    return class Employee extends Person {
        constructor (name, age, salary) {
            super(name, age);

            fields.set(this, {});

            this.salary = salary;
        }

        get salary () {
            return fields.get(this).salary;
        }

        set salary (value) {
            if (value <= 0.0) {
                throw "Invalid salary";
            }

            fields.get(this).salary = value;
        }
    };
})();

由這段程式碼可觀察到,我們不用再寫繼承原型鍵的部分。

使用方式和先前相同:

let e = new Employee("Michelle", 30, 1000);
console.log(e.name);
console.log(e.age);
console.log(e.salary);

結語

JavaScript 在本質上是以原型來繼承程式碼,但是若專案允許用 ES6 後的語法來寫 JavaScript 代碼,應優先使用 extend 保留字來繼承代碼,這樣寫起來會比較簡單。

至於原有的原型繼承模式則要能讀懂,畢竟許多現存的程式碼仍然是使用這些手法來寫的。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。