一文告訴你原型與原型鏈是什么?


構造函數

在 c++ 中,我們可以知道,類是事物的抽象,通過類可以生成一個個實例化的具體對象,類提供着生成對象的“模板”。在 JavaScript 中構造函數(constructor)就起着“模板”的作用,通過構造函數,我們可以生成實例化的對象。

function Cat() {
    this.color = 'orange'
}

var cat = new Cat()
console.log(cat.color)     // orange

在上面的代碼中,Cat 就是構造函數,使用 new 關鍵字來調用構造函數生成實例對象,我們約定構造函數的函數名要大寫,在構造函數的內部可以使用 this 關鍵字來添加屬性。

prototype

了解完了構造函數,我們來看一下與函數相關的 prototype 關鍵字。每個函數都有一個 prototype 屬性,它其實是個對象,我們可以通過代碼來看一下。

function Cat() {
    this.color = 'orange'
}

console.log(Cat.prototype)

打開 chrome 瀏覽器的開發者工具,在 console 欄輸入上面的代碼,你可以看到 Cat.prototype 的值:

__proto__

在 JavaScript 中,每個實例對象都有一個私有屬性 [[Prototype]],該屬性指向了這個實例對象的原型,你可以通過 ES6 的 Object.getPrototypeOf() 來訪問該屬性,許多瀏覽器也對 [[Prototype]] 進行了實現,也就是我們經常見到的 __proto__,沒錯,__proto__ 指向了實例對象的原型,它也是一個對象。

function Cat() {
    this.color = 'orange'
}

var cat = new Cat()
console.log(cat.__proto__)
console.log(Object.getPrototypeOf(cat) === cat.__proto__)  // true

細心的你可能會發現,實例對象的 __proto__ 與創建該實例對象的構造函數的 prototype 是相等的,是的沒錯,構造函數的 prototype 指向調用該構造函數而創建的實例對象的原型,我們可以通過代碼來看一下。

function Cat() {
    this.color = 'orange'
}

var cat = new Cat()

console.log(cat.__proto__ === Cat.prototype)   // true

有關構造函數的 prototype 和實例對象的 __proto__ 的關系,我們可以用張圖來體現一下。

constructor

在上文的 Cat.prototype 打印截圖中,相信你已經看到了 constructor 這個字段,字段的內容是一個函數,函數名和構造函數竟然一樣。可以說,每個原型對象都有一個 constructor 屬性,指向相關聯的構造函數,所以構造函數和構造函數的 prototype 是可以相互指向的。

function Cat() {
    this.color = 'orange'
}

console.log(Cat.prototype.constructor === Cat)    // true

原型鏈

 

在上文我們理解了原型,原型鏈肯定是與原型有關了,是一個個原型鏈接起來的么?我們先通過下面的代碼來觀察一下。

function Cat() {
    this.color = 'orange'
}

Cat.prototype.age = 4

var cat = new Cat()

console.log(cat.color)    // orange
console.log(cat.age)      // 4

在構造函數中,我並沒有設置有關 age 的屬性,只是把 age 設置在了實例原型上,然后我們通過實例對象也能訪問到 age 屬性。在 JavaScript 中,如果想訪問某個屬性,首先會在實例對象(cat)的內部尋找,如果沒找到,就會在該對象的原型(cat.__proto__,即 Cat.prototype)上找,我們知道,對象的原型也是對象,它也有原型,如果在對象的原型上也沒有找到目標屬性,則會在對象的原型的原型(Cat.prototype.__proto__)上尋找,以此內推,直到找到這個屬性或者到達了最頂層。在原型上一層一層尋找,這便就是原型鏈了。

那么原型鏈的最頂層是什么呢?我們可以在控制台測試看看。

function Cat() {
    this.color = 'orange'
}

var cat = new Cat()

console.log(Cat.prototype.__ptoto__)

我們在控制台輸出了實例對象原型的原型:

輸出了一大堆東西,好像看不出個所以然,比較直觀的是,它是個對象,第一行的 constructor 是一個 Object 函數。我們在上文提過,每個原型對象都有一個 constructor 屬性,指向相關聯的構造函數,比如 Cat.prototype.constructor === Cat,在上面的截圖中,我們可以猜測,xx.prototype.constructor === Object,可以知道 xx 就是構造函數 Object。

上面的輸出內容其實就是 Object.prototype,我們用代碼驗證一下。

function Cat() {
    this.color = 'orange'
}

console.log(Cat.prototype.__ptoto__ === Object.prototype)    // true

如果再往上尋找呢?Object.prototype 的原型會是什么?

console.log(Object.prototype.__proto__)   // null

它就是 null,null 沒有原型,所以 Object.prototype 就是原型鏈的最頂端。

 

可以說,JavaScript 中的所有對象都來自 Object,Object 位於原型鏈的最頂端,幾乎所有 JavaScript 的實例對象都是基於 Object。

 

我們可以將圖片更新一下:

關於繼承

JavaScript 的繼承是基於原型鏈的,在原型鏈的任何位置設置屬性,都能被對象訪問到,原型的作用也是在此,它可以包含所有實例共享的屬性和方法,就像該屬性本來就在實例對象上一樣,與其說是繼承,不如說原型鏈建立了一個鏈條,可以順藤摸瓜,實例對象可以訪問這根鏈條上的屬性和方法。

function Cat() {
    this.color = 'orange'
    this.age = 4
}

Cat.prototype.getColor = function() {
    console.log(this.color)
}

Object.prototype.getAge = function() {
    console.log(this.age)
}

var cat = new Cat()

cat.getColor()       // orange
cat.getAge()

基於原型鏈的繼承其實隨處可見,只是我們沒有意識到。當你隨手新建一個數組,是否想過它怎么會有 splice、indexOf 等方法,新建一個函數怎么可以直接使用 call 和 bind?其實數組都繼承於 Array.prototype,函數都繼承於 Function.prototype,它們分別包含了數組和函數的基本方法,嘗試去控制台打印出 Array.prototype 和 Function.prototype,上面的疑問便可得到解答。

var a = ['hello', 'world']
function f() {}

console.log(a.__proto__ === Array.prototype)      // true
console.log(f.__proto__ === Function.prototype)   // true

小結

JavaScript 對象(除了 null)在創建的時候就會關聯一個對象,這個對象就是原型,每一個對象都會從原型上繼承屬性,原型也是對象,所以原型也有原型對象,層層往上,直到 Object.prototype,這就是原型鏈。對象都會有一個 __proto__  屬性來訪問自己的原型,同時這個原型就是生成該對象的構造函數的 prototype 屬性值。每個原型對象都有一個 constructor 屬性,指向相關聯的構造函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM