轉載于《說說原型(prototype)、原型鍊和原型繼承》
一、原型 prototype 和 __ proto __
每個對象都有一個__proto__屬性,并且指向它的prototype原型對象
每個構造函數都有一個prototype原型對象
prototype原型對象裡的constructor指向構造函數本身
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL1UERPNTUq1UeNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4gTM3IDNwQTM0AzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
有的同學可能會問prototype 和 __proto__有什麼用呢?
執行個體對象的__proto__指向構造函數的prototype,進而實作繼承。
prototype對象相當于特定類型所有執行個體對象都可以通路的公共容器
看一下代碼就清楚了
function Person(nick, age){
this.nick = nick;
this.age = age;
}
Person.prototype.sayName = function(){
console.log(this.nick);
}
var p1 = new Person('Byron', 20);
var p2 = new Person('Casper', 25);
p1.sayName() // Byron
p2.sayName() // Casper
p1.__proto__ === Person.prototype //true
p2.__proto__ === Person.prototype //true
p1.__proto__ === p2.__proto__ //true
Person.prototype.constructor === Person //true
注意
- 當Object.prototype.proto 已被大多數浏覽器廠商所支援的今天,其存在和确切行為僅在ECMAScript 2015規範中被标準化為傳統功能,以確定Web浏覽器的相容性。為了更好的支援,建議隻使用 Object.getPrototypeOf()。
- Object.create(null) 建立的對象是沒有__proto__屬性的。
二、原型鍊
請看以下代碼
var arr = [1,2,3]
arr.valueOf() // [1, 2, 3]
我們再來看一張圖
按照之前的理論,如果自身沒有該方法,我們應該去Array.prototype對象裡去找,但是你會發現arr.__proto__上壓根就沒有valueOf方法,那它是從哪裡來的呢?
各位客官,請看這張圖
很奇怪我們在Array.prototype.__proto__裡找到了valueOf方法,為什麼呢?
查找valueOf方法的過程
當試圖通路一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜尋,直到找到一個名字比對的屬性或到達原型鍊的末尾。
查找valueOf大緻流程
目前執行個體對象obj,查找obj的屬性或方法,找到後傳回
沒有找到,通過obj. proto,找到obj構造函數的prototype并且查找上面的屬性和方法,找到後傳回
沒有找到,把Array.prototype當做obj,重複以上步驟
當然不會一直找下去,原型鍊是有終點的,最後查找到Object.prototype時
Object.prototype.proto === null,意味着查找結束
我們來看看上圖的關系
arr.__proto__ === Array.prototype
true
Array.prototype.__proto__ === Object.prototype
true
arr.__proto__.__proto__ === Object.prototype
true
// 原型鍊的終點
Object.prototype.__proto__ === null
true
原型鍊如下:
arr —> Array.prototype —> Object.prototype —> null
這就是傳說中的原型鍊,層層向上查找,最後還沒有就傳回undefined
三、JavaScript 中的繼承
3.1 什麼是繼承?
繼承是指一個對象直接使用另外一個對象的屬性和方法
由此可見隻要實作屬性和方法的繼承,就達到繼承的效果
得到一個對象的屬性
得到一個對象的方法
3.2 屬性如何繼承?
我們先建立一個Person類
function Person (name, age) {
this.name = name
this.age = age
}
// 方法定義在構造函數的原型上
Person.prototype.getName = function () { console.log(this.name)}
此時我想建立一個Teacher類,我希望它可以繼承Person所有的屬性,并且額外添加屬于自己特定的屬性;
一個新的屬性,subject——這個屬性包含了教師教授的學科。
定義Teacher的構造函數
function Teacher (name, age, subject) {
Person.call(this, name, age)
this.subject = subject
}
屬性的繼承是通過在一個類内執行另外一個類的構造函數,通過call指定this為目前執行環境,這樣就可以得到另外一個類的所有屬性。
Person.call(this, name, age)
我們執行個體化一下看看
var teacher = new Teacher(‘jack’, 25, Math)
teacher.age
25
teacher.name
“jack”
很明顯Teacher成功繼承了Person的屬性
3.3 方法如何繼承?
我們需要讓Teacher從Person的原型對象裡繼承方法。我們要怎麼做呢?
我們都知道類的方法都定義在prototype裡,那其實我們隻需要把Person.prototype的備份指派給Teacher.prototype即可
Teacher.prototype = Object.create(Person.prototype)
Object.create簡單說就是建立一個對象,使用現有的對象指派給建立對象的__proto__
可能有人會問為什麼是備份呢?
因為如果直接指派,那會是引用關系,意味着修改Teacher. prototype,也會同時修改Person.prototype,這是不合理的。
另外注意一點就是,在給Teacher類添加方法時,應該在修改prototype以後,否則會被覆寫掉,原因是指派前後的屬性值是不同的對象。
最後還有一個問題,我們都知道prototype裡有個屬性constructor指向構造函數本身,但是因為我們是複制其他類的prototype,是以這個指向是不對的,需要更正一下。
如果不修改,會導緻我們類型判斷出錯
Teacher.prototype.constructor = Teacher
Teacher.prototype = Object.create(Person.prototype)
Teacher.prototype.constructor
ƒ Person(name, age) {
this.name = name
this.age = age
}
---
Teacher.prototype.constructor = Teacher
ƒ Teacher(name, age, subject) {
Person.call(this, name, age)
this.subject = subject
}
繼承方法的最終方案:
Teacher.prototype = Object.create(Person.prototype)
Teacher.prototype.constructor = Teacher
3.4 hasOwnProperty
在原型鍊上查詢屬性比較耗時,對性能有影響,試圖通路不存在的屬性時會周遊整個原型鍊。
周遊對象屬性時,每個可枚舉的屬性都會被枚舉出來。 要檢查是否具有自己定義的屬性,而不是原型鍊上的屬性,必須使用hasOwnProperty方法。
hasOwnProperty 是 JavaScript 中唯一處理屬性并且不會周遊原型鍊的方法。
四、總結
prototype 和 proto
每個對象都有一個__proto__屬性,并且指向它的prototype原型對象
每個構造函數都有一個prototype原型對象
prototype原型對象裡的constructor指向構造函數本身
原型鍊
每個對象都有一個__proto__,它指向它的prototype原型對象,而prototype原型對象又具有一個自己的prototype原型對象,就這樣層層往上直到一個對象的原型prototype為null
這個查詢的路徑就是原型鍊
JavaScript 中的繼承
屬性繼承
function Person (name, age) {
this.name = name
this.age = age
}
// 方法定義在構造函數的原型上
Person.prototype.getName = function () { console.log(this.name)}
function Teacher (name, age, subject) {
Person.call(this, name, age)
this.subject = subject
}
方法繼承
Teacher.prototype = Object.create(Person.prototype)
Teacher.prototype.constructor = Teacher