在之前的篇章裡已經詳細的介紹過要如何建立 JavaScript 的物件導向程式,然而我們尚未好好的看過整個全局
為什麼這麼說呢?讓我們回到 Animal
與 Dog
的範例
為了方便了解 prototype,我們先不採用 ES6 的物件導向語法糖實作:
function Animal(name, gender, age) {
this.name = name;
this.gender = gender;
this.age = age;
}
Animal.prototype.speak = function() {
console.log('some sounds');
};
function Dog(name, gender, age) {1
this.name = name;
this.gender = gender;
this.age = age;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
console.log('Bow-wow');
};
var dog = new Dog('Blacky', 'male', 3);
dog.speak(); // "Bow-wow"
dog.hasOwnProperty('name'); // "Bow-wow"
有沒有發現我們竟還還有一個 hasOwnProperty
方法,但明明都沒有定義過啊!?
讓我們思考一下 dog
的型別是什麼:
Dog
當然是,畢竟 dog
是由 new Dog()
建構式產生的。
Animal
也是,因為 Dog.prototype
的 [[Prototype]]
會連結到 Animal.prototype
。
Object
依然是,還記得我們曾說過:JavaScript 中除了原始型別以外的一切都是物件嗎?dog
不是原始型別,所以他一定是物件。
因此我們的答案是以上皆是。
那 hasOwnProperty
這個方法是哪來的就呼之欲出了,既然我們在 Dog
跟 Animal
都沒定義過 hasOwnProperty
,那他必然就是 Object
的內建屬性了。
現在就讓我們把 dog
所有的 __proto__
連結展開看看 hasOwnProperty
藏在哪:
找到了!跟我們預想的一樣,hasOwnProperty
就是 Object
的方法。
那就讓我們詳細了解一下 JavaScript 尋找屬性的規則:Prototype Chain 吧
JavaScript 屬性查找的機制。
在了解規則前我們要先意識到,所有物件的最基層原型物件都會是 Object.prototype
,因為只要不是原始型別,都會是屬於物件的子型別。
接著了解了這個的前提下,就讓我們來看看規則吧:
[[Prototype]]
上層原型物件查找,找到同名屬性就回傳該屬性Object.prototype
都沒找到就回傳 undefined
[[Prototype]]
上的原型物件是否有同名屬性,發現的話
writable
為 true
,就寫入writable
為 false
,就不寫入Object.prototype
都沒找到就寫入屬性到該物件在讀取屬性的規則中可以看到,Prototype Chain 與 Scope 查找機制很像,JavaScript 依然會以從物件開始往上層物件尋找,也就是只要找到最距離接近物件的屬性就會取用該屬性。
後面原型物件的屬性雖然還存在,但就不會被拿來使用了,這就是 Prototype Chain 的遮蔽特性。有了 Prototype Chain 的遮蔽特性,我們才能複寫父類別方法來仿造物件導向的繼承概念。
舉例來說,當範例中呼叫
dog.speak();
JavaScript 會發現 dog
上根本沒有 speak
這個方法 (如圖,只有 age
、gender
、name
三個屬性),因此就會往他的 [[Prototype]]
上的 Dog.prototype
查找。在 Dog.prototype
馬上發現 speak
方法,因此就停止查找,取用該方法。
也就是說雖然 Animal.prototype.speak
這個方法依然存在,但是因為優先查找到 Dog.prototype.speak
所以被遮蔽掉了,因此我們執行 dog.speak()
的結果才會是印出 "Bow-wow"
。
這篇中我們學到了 Prototype Chain 的規則,主要有幾個重點:
所有物件的最基層原型物件都會是 Object.prototype
讀取規則
[[Prototype]]
上層原型物件查找,找到同名屬性就回傳該屬性Object.prototype
都沒找到就回傳 undefined
寫入規則
[[Prototype]]
上的原型物件是否有同名屬性,發現的話
writable
為 true
,就寫入writable
為 false
,就不寫入因為這個特性,因此子類別屬性會遮蔽父類別屬性,這也是我們能寫出仿物件導向繼承的重要原因之一。
You Don't Know JS: this & object prototypes