天天看點

原型(prototype)、原型鍊和原型繼承

轉載于《說說原型(prototype)、原型鍊和原型繼承》

一、原型 prototype 和 __ proto __

每個對象都有一個__proto__屬性,并且指向它的prototype原型對象

每個構造函數都有一個prototype原型對象

prototype原型對象裡的constructor指向構造函數本身

原型(prototype)、原型鍊和原型繼承

有的同學可能會問prototype 和 __proto__有什麼用呢?

執行個體對象的__proto__指向構造函數的prototype,進而實作繼承。

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
           

注意

  1. 當Object.prototype.proto 已被大多數浏覽器廠商所支援的今天,其存在和确切行為僅在ECMAScript 2015規範中被标準化為傳統功能,以確定Web浏覽器的相容性。為了更好的支援,建議隻使用 Object.getPrototypeOf()。
  2. Object.create(null) 建立的對象是沒有__proto__屬性的。

二、原型鍊

請看以下代碼

var arr = [1,2,3]

arr.valueOf() // [1, 2, 3]

我們再來看一張圖

原型(prototype)、原型鍊和原型繼承

按照之前的理論,如果自身沒有該方法,我們應該去Array.prototype對象裡去找,但是你會發現arr.__proto__上壓根就沒有valueOf方法,那它是從哪裡來的呢?

各位客官,請看這張圖

原型(prototype)、原型鍊和原型繼承

很奇怪我們在Array.prototype.__proto__裡找到了valueOf方法,為什麼呢?

查找valueOf方法的過程

當試圖通路一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜尋,直到找到一個名字比對的屬性或到達原型鍊的末尾。

查找valueOf大緻流程

目前執行個體對象obj,查找obj的屬性或方法,找到後傳回

沒有找到,通過obj. proto,找到obj構造函數的prototype并且查找上面的屬性和方法,找到後傳回

沒有找到,把Array.prototype當做obj,重複以上步驟

當然不會一直找下去,原型鍊是有終點的,最後查找到Object.prototype時

Object.prototype.proto === null,意味着查找結束

原型(prototype)、原型鍊和原型繼承

我們來看看上圖的關系

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
           

繼續閱讀