[JavaScriptWeird]No.50 Object.create() 與 純粹的原型繼承

前言

我們已經知道了如何使用函式建構子建立物件的方法,但除了透過函式建構子之外其實還有別的方法可以建立物件且指定原型。

Object.create()

Object.create() ,MDN 的說明為指定其原型物件與屬性,創建一個新物件,直接透過範例來了解:

1
2
3
4
5
6
7
8
9
10
var person = {  
firstName: 'Default',
lastName: 'Default',
greet: function() {
return 'hello ' + this.firstName;
}
}

var john = Object.create(person);
console.log(john);

我們建立物件 person ,並且使用 Object.create() 方法建立新的物件,把 person 當成參數傳入,接著印出 john

我們並沒有改變什麼,只是說有其他種方式可以建立物件。

john 現在是一個空物件,且原型是 person 物件,因此可以呼叫 greet 方法。

1
2
console.log(john.greet());  
// hello Default

因為 john 現在是空物件,所以它的 firstName 會到原型鏈上尋找,因此我們只要讓 john 有對應的屬性就可以了,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
var person = {  
firstName: 'Default',
lastName: 'Default',
greet: function() {
return 'hello ' + this.firstName;
}
}
var john = Object.create(person);
console.log(john);
console.log(john.greet()); // hello Default
john.firstName = 'John';
console.log(john.greet()); // hello John

簡單來說我們只是建立一個物件,並且以這個物件為基底,在這物件之上建立了新物件,然後我們就可以在這之上進行覆寫、增加屬性或方法。

這麼做還有一個好處,就是原型物件非常直觀就可以進行修改,而且以該物件為原型的其他物件都可以取用這些方法,這就是純粹的原型繼承。

如果與先前的函式建構子對照起來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var person = {  
firstName: 'Default',
lastName: 'Default',
greet: function() {
return 'hello ' + this.firstName;
}
}

function person1(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function(){
return this.firstName;
}
}

var john = Object.create(person);
console.log(john);
john.firstName = 'John';

var mary = new person1('mary', 'Doe');
console.log(mary);

結構略有不同,個人較偏好使用 Object.create() 建立原型物件。

Polyfill

Object.create() 這麼神奇好用,是不是可以廣泛應用在任何地方?很遺憾,雖然大部分瀏覽器都支援這麼方法,但如果需要支援到 IE8 的話這是沒有辦法使用的,但也有相對應的措施,那就是找找看有沒有 Polyfill 。

Polyfill 是將引擎缺少的功能增加到程式中,使之可以正常運作,所以如果我們想要在 IE8 或者更舊的環境下使用 Object.create() 可以這麼做:

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
37
38
// Polyfill  
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (!(proto === null || typeof proto === "object" || typeof proto === "function")) {
throw TypeError('Argument must be an object, or null');
}
var temp = new Object();
temp.__proto__ = proto;
if(typeof propertiesObject === "object")
Object.defineProperties(temp, propertiesObject);
return temp;
};
}

var person = {
firstName: 'Default',
lastName: 'Default',
greet: function() {
return 'hello ' + this.firstName;
}
}

function person1(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function(){
return this.firstName;
}
}

var john = Object.create(person);
console.log(john);
console.log(john.greet());
john.firstName = 'John';
console.log(john.greet());

var mary = new person1('mary', 'Doe');
console.log(mary);

MDN 很貼心的將 Object.create() 的 Polyfill 放上去了,所以可以像這樣直接貼上到程式碼內,MDN 其實提共了不少現成的 Polyfill,讓我們方便使用。

同樣的方式我們可以再新增一個 mary 物件,並且設定原型為 john 然後觀察輸出情形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var person = {  
firstName: 'Default',
lastName: 'Default',
greet: function() {
return 'hello ' + this.firstName;
}
}
var john = Object.create(person);
console.log(john);
// console.log(john.greet());
john.firstName = 'John';
john.sayHi = function(){
return 'Hi ' + this.firstName;
}
console.log(john.greet());
console.log(john.sayHi());

var mary = Object.create(john);
mary.firstName = 'mary';
console.log(mary);
console.log(mary.greet());
console.log(mary.sayHi());

觀察後發現 mary 物件的原型鏈的下一層就是 john ,最底層是 person ,因此也可以使用 greetsayHi 方法。

0%