0%

JS核心-(49)-繼承與原型練-使用 Object.create 建立多層繼承

前言

建立多層繼承就需要調整原型鏈上的結構

多層繼承

我們先來看一下

var a = [];
// Object > Array > a(實體)

如果今天狀況是一個陣列十字的宣告的話,那麼原型鏈的關係就是陣列實體,再往上是陣列原型,再往上是物件原型。

也因此這個陣列的實體可以使用陣列原型以及物件原型原型鏈上的所有方法。

function Dog (name, color, size) {
    this.name = name;
    this.color = color;
    this.size = size;
}

var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi);
// Object > Dog > Bibi(實體)

前幾個章節我們也透過 Dog 的建構函式建立了 '比比' 這隻狗(實體),他是繼承於狗,再上一層是物件的原型,同時比比可以調

用建構函式 Dog 以及物件原型的所有方法。

狗其實都是屬於動物的一環,所以我們現在需要在物件原型 Dog 以及建構函式 Object 中間新增一層叫做 Animal 的關係的話

↓ ,我們應該怎麼做呢?

Object > Animal > Dog > Bibi(實體)

如果變成這樣的話 ↓ ,我們就可以創造出其他的物種,例如:貓

Object > Animal > Cat

我們就來試著做看看要怎麼新增一個層級吧!


Object.create( )

首先要先來介紹一個方法,叫做 Object.create( ) ,這個方法的主要功用,就是把其他的物件作為原型使用

先從簡單的例子的開始吧~

var Bibi = {
    name: '比比',
    color: '棕色',
    size: '小',
    bark: function () {
        console.log(this.name + '吠叫');

    }
};

// Object.create()
var Pupu = Object.create(Bibi);
console.log(Pupu);

我們讓 Pupu 使用 Object.create( ) 方法繼承 Bibi 為原型,雖然 Pupu 現在是空物件,但是可以透過 Pupu.name 的方法來

取得並且確認的確是繼承了 Bibi 為原型

當然我們也可以直接賦予屬性給 Pupu

var Pupu = Object.create(Bibi);
Pupu.name = '噗噗';
console.log(Pupu);

這就是 Object.create( ) 的用法。

而且在不改變屬性的情況下,所有的值都可以以 Bibi 為預設值,並且調用 Bibi 的方法。


創造多層的原型鏈結構

function Animal (family) {
    this.kingdom = '動物界';
    this.family = family || '人科';
}

首先我們先創立動物的建構函式 Animal ,並且允許可以傳入這個動物的科別,如果都沒有傳入的話就預設是 '人科'

並且我們也可以在 Animal 的原型 prototype 上加上動物的方法,也就是移動的方式。

Animal.prototype.move = function () {
    console.log(this.name + ' 移動');
};

接下來我們把狗加回來,並且重點來了,我們要利用 Object.create( ) 的方式把狗 Dog 的原型 prototype 重新賦值給動物

Animal 的原型 prototype

function Dog (name, color, size) {
    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

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

透過這樣的方式,就可以產生我們一開始所說的階級,接下來再把狗會叫的行為給加回來

Dog.prototype.bark = function () {
    console.log(this.name + '吠叫');
}

因此,我們就可以再用建構式的方式,把 Bibi 這隻狗給產生出來。

function Animal (family) {
    this.kingdom = '動物界';
    this.family = family || '人科';
}

Animal.prototype.move = function () {
    console.log(this.name + ' 移動');
};

function Dog (name, color, size) {
    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

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

Dog.prototype.bark = function () {
    console.log(this.name + '吠叫');
}

var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi);

可以看到 '比比' 的確繼承了 '狗',並且上一層是 Animal ,那麼接下來我們再來看看他是不是能夠正常的吠叫以及移動。

Bibi.bark();
Bibi.move();

看起來沒什麼問題,跟我們預期的結果差不多

可是其實還缺少了一些東西!

主要是 Bibi 其實並沒有繼承到 Animal 定義的科別以及動物界的屬性,也因此直接輸入 Bibi.family 的話會是 undefined

為什麼會這樣呢?

主要是因為 Dog 只有繼承 Animal 的原型,並沒有繼承整個建構函式,所以我們得在狗的建構函式內補一些內容

function Dog (name, color, size) {

    Animal.call(this, '犬科'); //新增了這行

    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

我們透過 call 的方式,將 Dog 裡面的 this 指定給 Animal 裡面的 this ,並且直接執行,就等於是幫 Dog 新增了

Animal 裡面的屬性,並且也特別傳入犬科的參數,讓 this.family 的值變為犬科。

到這裡其實程式碼的運作已經沒甚麼問題,但可以加上最後一行讓整個程式碼的邏輯更為完善

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; //增加這行

因為我們利用 Object.create 的方式繼承了 Animal ,那麼這個 constructor 也會被取代掉,所以透過這樣的方式補回來。

那麼這個 constructor 又是什麼呢?

var newAnimal = new Animal('新物種');
console.log(newAnimal);

當我們使用建構式的方式來產生一個新的物種的時候,其中 proto 的屬性 就會包含 constructor 這個屬性,那麼這個

constructor 的屬性就會指向原本的建構函式。所以可以看到這裡 newAmimal 是由 Animal 作為建構函式所產生的物件實體,

所以 newAmimalconstructor 就會指向 Animal

又所以,Dog.prototype.constructor = Dog; 才可以透過 Bibi.constructor 找到狗的原型喔!

整個的程式碼會如下所示:

function Animal (family) {
    this.kingdom = '動物界';
    this.family = family || '人科';
}

Animal.prototype.move = function () {
    console.log(this.name + ' 移動');
};

function Dog (name, color, size) {
    // 新增了這裡
    Animal.call(this, '犬科');
    // 新增了這裡
    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

Dog.prototype = Object.create(Animal.prototype);
// 邏輯更為完善
Dog.prototype.constructor = Dog;
// 邏輯更為完善

Dog.prototype.bark = function () {
    console.log(this.name + '吠叫');
}

var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi);

Bibi.bark();
Bibi.move();

接著我們就練習貓科的新增吧!

而貓不會吠叫只會喵喵叫,所以在貓的原型上要掛載的是喵喵叫的方法

function Animal (family) {
    this.kingdom = '動物界';
    this.family = family || '人科';
}

Animal.prototype.move = function () {
    console.log(this.name + ' 移動');
};

function Dog (name, color, size) {
    // 新增了這裡
    Animal.call(this, '犬科');
    // 新增了這裡
    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

Dog.prototype = Object.create(Animal.prototype);
// 邏輯更為完善
Dog.prototype.constructor = Dog;
// 邏輯更為完善

Dog.prototype.bark = function () {
    console.log(this.name + '吠叫');
}

var Bibi = new Dog('比比', '棕色', '小');
console.log(Bibi);

Bibi.bark();
Bibi.move();

// var newAnimal = new Animal('新物種');
// console.log(newAnimal);

function Cat (name, color, size) {
    Animal.call(this, '貓科');
    this.name = name;
    this.color = color || '白色';
    this.size = size || '小';
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function () {
    console.log(this.name + '喵喵叫');
}

var Kity = new Cat('凱蒂');
Kity.meow();
Kity.move();
Kity.bark();

依照這樣的方式,凱蒂能夠喵喵叫,也能夠移動,但是沒辦法像狗一樣吠叫。