1. 程式人生 > >JS 基礎篇(二):理解JS原型物件與原型鏈

JS 基礎篇(二):理解JS原型物件與原型鏈

目錄:

一、什麼是原型物件和原型鏈

JavaScript 常被描述為一種基於原型的語言 (prototype-based language)——每個物件對應擁有一個原型,物件以其原型為模板、從原型繼承方法和屬性。而同時原型也是物件,它也擁有原型,並從中繼承方法和屬性,一層一層、以此類推。這種關係常被稱為原型鏈 (prototype chain),它解釋了為何一個物件會擁有定義在其他物件中的屬性和方法。

注:因為原型同時也是物件,所以也可以稱呼為原型物件。

二、為什麼使用原型物件

1、Javascript 並沒有類繼承模型,而是使用原型物件進行原型式繼承。

2、原型物件的用途是為每個例項物件儲存共享的方法和屬性,它僅僅是一個普通物件而已。並且所有的例項是共享同一個原型物件,因此有別於例項方法或屬性,原型物件僅有一份。

三、原型物件的理解

1、物件__proto__屬性的值就是它所對應的原型物件,即“物件.__proto__ === 物件對應的原型物件”。

2、在Javascript中,每個函式都有一個屬性為prototype指向函式自身的原型。即“函式.prototype === 自身對應的原型物件”。

案例分析:

code1

function Person(){ }  //建構函式建立物件
Person.prototype.name = "Jie"; //新增屬性值到原型物件上
console.log(Person.prototype);

在這裡插入圖片描述 原型物件內包含我們之間新增到原型物件上的name屬性,還包含一個"constructor"屬性,這個屬性對應建立所有指向該原型的例項的建構函式。

code2


function Person(){ }  //建構函式建立物件
Person.prototype.name = "Jie"; //新增屬性值到原型物件上

var person = new Person(); //建立例項物件
person.age = 23;  //建立屬性值到例項物件上
console.log(person);

在這裡插入圖片描述

通過上面的兩段程式碼分析,我們可以得出例項物件person的__proto__屬性就是Person的prototype屬性。person.__proto__與Person.prototype均指向了原型物件。

並且通過上面的結果可以看出,例項物件person的原型(person.__proto__)的原型(person.__proto__.__proto__)是Object物件的原型。

可以將上面的分析進行圖解,如下所示: 在這裡插入圖片描述

  • prototype: 在函式身上,指向原型物件
  • __proto__: 在物件身上(包括函式建立的物件, 函式本身和原型物件),指向自身的原型
  • constructor: 在原型物件上,指向建構函式, 在多級繼承的時候,指明建構函式方便在物件上擴充套件原型屬性
  • Object.__protp__為null: 原型的頂端

在Javascript中,每個函式都有一個原型屬性prototype指向函式自身的原型,而由這個函式建立的物件也有一個__proto__屬性指向這個原型。(即person.proto === Person.prototype; //true)

而函式的原型是一個物件,所以這個物件也會有一個__proto__指向自己的原型,這樣逐層深入直到Object物件的原型(null),這樣就形成了原型鏈。

函式的原型物件的constructor預設指向函式本身,原型物件除了有原型屬性外,為了實現繼承,還有一個原型鏈指標__proto__,該指標指向上一層的原型物件,而上一層的原型物件的結構依然類似,這樣利用__proto__一直指向Object的原型物件上,而Object的原型物件用Object.prototype.__proto__ = null表示原型鏈的最頂端,如此變形成了javascript的原型鏈繼承,同時也解釋了為什麼所有的javascript物件都具有Object的基本方法。

四、“prototype"和”__proto__"區別

對於所有的物件,都有__proto__屬性,這個屬性對應該物件的原型。 對於函式物件,除了__proto__屬性之外,還有prototype屬性,當一個函式被用作建構函式來建立例項時,該函式的prototype屬性值將被作為原型賦值給所有物件例項(也就是被設定為所有例項的__proto__屬性值)

五、查詢屬性

當訪問一個物件的屬性時,Javascript 會從物件本身開始往上遍歷整個原型鏈,直到找到對應屬性為止。如果此時到達了原型鏈的頂部,也就是 Object.prototype,仍然未發現需要查詢的屬性,那麼 Javascript 就會返回 undefined 值。

例如:當你訪問 person 的一個屬性, 瀏覽器首先查詢 person 是否有這個屬性. 如果 person 沒有這個屬性, 然後瀏覽器就會在 person 的 __proto__ 中查詢這個屬性(也就是 Person.prototype). 如果 person 的 __proto__ 有這個屬性, 那麼 person 的 __proto__ 上的這個屬性就會被使用. 否則, 如果 person 的 __proto__ 沒有這個屬性, 瀏覽器就會去查詢 person 的 __proto__ 的 __proto__ ,看它是否有這個屬性. 預設情況下, 所有函式的原型屬性的 __proto__ 就是 Object.prototype. 所以 person 的 __proto__ 的 __proto__ (也就是 Person.prototype 的 __proto__ (也就是 Object.prototype)) 會被查詢是否有這個屬性. 如果沒有在它裡面找到這個屬性, 然後就會在 person 的 __proto__ 的 __proto__ 的 __proto__ 裡面查詢. 然而這有一個問題: person 的 __proto__ 的 __proto__ 的 __proto__ 不存在. 最後, 原型鏈上面的所有的 __proto__ 都被找完了, 瀏覽器所有已經聲明瞭的 __proto__ 上都不存在這個屬性,然後就得出結論,這個屬性是 undefined.

在這裡插入圖片描述

六、原型物件操作

觀察以下程式碼:

function Person(){
    this.name = "Jie";
}
var person = new Person();

Person.prototype.say = function(){
    console.log("success");
}

我們的程式碼中定義了構造器,然後用這個構造器建立了一個物件例項,此後向構造器的 prototype 添加了一個新的方法。 say() 方法仍然可用於 person 物件例項——舊有物件例項的可用功能被自動更新了。這證明了先前描述的原型鏈模型。這種繼承模型下,上游物件(父類)的方法不會複製到下游的物件(子類)例項中;下游物件本身雖然沒有定義這些方法,但瀏覽器會通過上溯原型鏈、從上游物件中找到它們。這種繼承模型提供了一個強大而可擴充套件的功能系統。

因此,我們可以將一些通用的,公用的屬性或方法定義在prototype中。

例如: 1、"getInfo"方法是建構函式Person的一個成員,當通過Person構造兩個例項的時候,每個例項都會包含一個"getInfo"方法。 在這裡插入圖片描述

2、原型就是為了方便實現屬性的繼承,所以可以將"getInfo"方法當作Person原型(Person.__proto__)的一個屬性,這樣所有的例項都可以通過原型繼承的方式來使用"getInfo"這個方法了。並且改變getInfo方法全部的例項都會同步。

function Person(name, age){
    this.name = name;
    this.age = age;
}

Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
};

在這裡插入圖片描述