這篇教學介紹JavaScript中2種建立物件的方法及原理: 1. 使用new運算子及建構函式(Function Constructor),2. 使用Object.assign

用new運算子和建構函式(Function Constructor)創造物件

簡單地說,建構式就是一個用來創造新物件的函式。

那要如何使用建構式呢?舉個例子,如果要建構Cat物件,並且每個物件都有各自的name特性(property),那我們可以定義Cat建構式如下:

// Constructor
function Cat(name) {
	this.name = name;
}

那要如何定義物件的方法呢?JS的每個函式都內建prototype特性,我們只需要將方法定義在prototype特性裡,如下:

// Define 'speak' method for Cat objects
Cat.prototype.speak = function() {
	console.log(this.name + ": meow!");
};

定義完建構式和方法後,我們用new運算子呼叫建構式:

var kitty = new Cat("Kitty");
kitty.speak(); // Kitty: meow!

這樣就創造出了一個kitty物件,可以呼叫我們定義的speak方法!

簡單總結一下,建構式有幾個必要的步驟:

  1. 定義建構式
  2. 將方法定義在建構式的prototype特性裡
  3. new運算子呼叫建構式

new運算子和建構函式(Function Constructor)是如何運作的?

要了解其運作原理,首先我們要了解何謂原型繼承(Prototypal Inheritance)。

原型繼承用一句話形容就是:JS的每個物件都繼承自一個原型委託(delegate prototype)物件,如果對物件查詢某個特性(property)失敗時,就會去查詢他的原型物件上是否存在這個特性。這個機制使我們可以在prototype物件上定義特性或方法,所有繼承同一個prototype的物件都可以透過原型委託使用這些特性或方法。

接著我們來瞭解new運算子的機制。

當我們呼叫new Cat("Kitty")的時候,JS在背後做了幾件事:

  1. 建立新物件,
  2. 新物件的繼承自建構式的prototype特性,也就是Cat.prototype
  3. 將新物件綁定到建構式的this物件,並呼叫建構式。
  4. (在不特別寫明return值的情況下) 回傳剛創造的新物件。

第2步將新物件的原型設為Cat.prototype。所以對新物件呼叫speak()方法時,會先在本身尋找此方法。然後會發現自己身上找不到此方法,於是再到自己的prototype(也就是Cat.prototype)上尋找。因為我們定義了Cat.prototype.speak,所以可以順利找到此方法。

簡單地說,當你使用建構式來創造新物件,新物件的原型就是建構式上的prototype特性。而在原型上定義方法,就等於所有物件都可以透過原型委託的方式使用原型上的方法。

使用建構式的優缺點

建構式長得很像C++或Java之類的class,可能對慣用其他語言的人比較容易理解JS的物件,但經過上面的說明,我們可以知道建構式跟class簡直是天差地遠呀。

除此之外,建構式必須和new運算子搭配使用,但萬一我們忘了,直接呼叫建構式:

var kitty = Cat("kitty");

此時並不會有任何錯誤或警告,this會直接bind到全域變數,有可能會導致很難察覺的bug!

Object.create()創造新物件

ES5中提供了Object.create()的方法,用來創造新物件。使用方法:

Object.create(proto[, propertiesObject])

傳入作為參數的proto物件,將會被當作回傳新物件的prototype。舉例而言,我們可以創造一個物件cat。裡面定義了speak()方法:

var cat = {
	speak: function() {
		console.log(this.name + ": meow!");
	}
};

當我們呼叫Object.create(cat)時,回傳的新物件將會繼承自cat

// Create a new cat
var kitty = Object.create(cat);
kitty.name = "Kitty";
kitty.speak(); // Kitty: meow!

kitty物件裡找不到speak()方法,於是接下來到他的原型物件(也就是cat物件)上面尋找。cat物件裡定義了speak()方法,於是呼叫成功。

Object.create()是如何運作的?

被傳進作為參數的物件,將會被當成新物件的原型物件。所以Object.create()的內部可能會長得像這樣(示意):

if (!Object.create) {
	Object.create = function(o) {
		function F() {}
		F.prototype = o;
		return new F();
	};
}

其中F()是建構式,建構式上的prototype特性設為o,並且由new運算子呼叫建構式。所以新物件的特性查找將會委託給o

####使用Object.create()的優缺點

Object.create()似乎更簡單一些,而且還省去了可能會忘記用new呼叫建構式的風險。但是會有瀏覽器相容性的問題。

結論

JS中可以用建構式,或者是ES5的Object.create()來創造新物件。

使用建構式創造的新物件,將繼承自建構式上的prototype特性。

使用Object.create(obj)創造的新物件,將繼承自obj

這篇沒有講到該如何在這兩種模式底下利用closure做到private data member的效果,還有另一種常見於JS的mixin模式,希望之後有動力研究XD

參考資料

覺得這篇文章對你有幫助的話,歡迎分享👉