切換語言為:簡體
JavaScript物件的原型和原型鏈

JavaScript物件的原型和原型鏈

  • 爱糖宝
  • 2024-11-14
  • 2020
  • 0
  • 0

一.寫在前面

原型和原型鏈是 JavaScript 中的重難點之一,雖然 ES6 我們已經可以使用class進行定義類,可以使用extends來繼承父類,但究其本質在 JavaScript 的內部還是使用的原型和原型鏈來實現的,所以學習和理解原型和原型鏈對於理解和深入 JavaScript 是必不可少的,好了 🦀 廢話不多說,讓我們開始今天的學習吧! 這篇文章我們會按照下述的內容模組進行學習和介紹。

JavaScript物件的原型和原型鏈

二.普通物件的原型

當我們在瀏覽器上執行如下的程式碼的時候,我們會看到輸出的內容中有一個比較特殊的物件[[Prototype]],這個物件就是我們編寫的物件的原型物件.

let obj = {
  name: 'CodeUp',
  age: 12,
}
console.log(obj)

JavaScript物件的原型和原型鏈

我們想要獲取這個物件不能直接使用obj.[[Prototype]]的方式來獲取,JavaScript 給我們提供了 API 來獲取普通物件的原型物件。

console.log(obj.__proto__) // 不推薦,僅僅是瀏覽器的實現
console.log(Object.getPrototypeOf(obj)) // 推薦,標準的實現

JavaScript物件的原型和原型鏈

我們 來試一下,發現都是可以正常獲取到的,但是在開發中推薦使用標準的寫法__proto__可能在某些瀏覽器上存在不相容的情況,但是鑑於大多數瀏覽器都支援,這個 API 使用起來也方便,我們在使用的時候增加判斷,防止某些情況下的不相容即可。

三.函式物件的原型

在 JavaScript 函式也是物件的一種,它是Object類的子類,關於Object我們在下面會介紹,函式物件也具有(__proto__),因為不經常使用我們稱之為隱式原型,同時也具有作為函式自身的原型(prototype)由於經常使用我們稱之為顯式原型,簡單理解就是普通物件有的函式也有,但是是有差別的,作為函式自身它具有prototype這個纔是函式貨真價實的原型。

function foo() {}
console.log(foo.prototype)
console.log(foo.__proto__)

函式物件和普通的物件是有關係的,為什麼這麼說哪? 因為所有的函式都是可以new的,也就是透過函式可以建立物件,他們之間的關係我們可以透過以下的程式碼來看出來

function Foo(name, age) {
  this.name = name
  this.age = age
}

let obj1 = new Foo('芒果', 12)
let obj2 = new Foo('招財', 13)

console.log(Foo.prototype === obj1.__proto__)
console.log(Foo.prototype === obj2.__proto__)

透過程式碼我們可以看出,函式的prototype和所建立物件的__proto__指向的是同一個物件,也就是本質上說這兩個東西是相等的。

四.函式原型的 constructor

事實上原型物件上還有一個屬性叫做constructor屬性,預設情況下原型上都會新增一個屬性叫做 constructor,這個 constructor 指向當前的函式物件,我們可以使用程式碼來驗證下

function Person() {}
var personPrototype = Person.prototype
console.log(personPrototype)
console.log(personPrototype.constructor)
console.log(personPrototype.constructor === Person) // true

講到這裏我們已經基本理清楚了,物件,函式,隱式原型,顯式原型以及constructor之前的關係,一圖勝千言,他們之間的關係用圖來表示其實就是這樣的。

JavaScript物件的原型和原型鏈

五.重寫原型物件

如果我們需要在原型上新增非常多的東西的時候我們可能需要重寫原型物件,我們可以直接對原型物件進行賦值,舉個簡單的例子,當我們在編寫一個函式的時候需要往原型上新增很多的屬性,就像下列的程式碼一樣。

function Person() {}
Person.prototype.message = 'Hello Person'
Person.prototype.info = { name: '哈哈哈', age: 30 }
Person.prototype.running = function () {}
Person.prototype.eating = function () {}

但是需要掛載在原型上的東西非常多的時候我們也可以直接對原型物件進行重寫。

Person.prototype = {
  message: 'Hello Person',
  info: { name: '哈哈哈', age: 30 },
  running: function () {},
  eating: function () {},
}

但是雖然重寫比較方便但是這樣其實也會造成一個問題,那就是constructor從上面的關係圖我們可以看出來,建構函式會指向這個函式,所以我們就需要增加一行程式碼,並且原來的constructor是不可列舉的,所以我們還需要透過屬性描述符來進行不可列舉的控制。

Person.prototype = {
  message: 'Hello Person',
  info: { name: '哈哈哈', age: 30 },
  running: function () {},
  eating: function () {},
}
Object.defineProperty(Person.prototype, 'constructor', {
  value: Person,
  enumerable: false,
})

六.物件的原型鏈

當我們瞭解了物件的原型以及物件的原型與函式的原型,以及和constructor之間的關係後,我們就可以透過物件之間一層一層的關係來研究和學習一下一個重要的概念原型鏈

var obj = {
  name: 'mongo',
  age: 1,
}

console.log(obj.message)

當我們在瀏覽器中執行上述的程式碼的時候,會經歷如下幾個階段

  1. obj上查詢message屬性

  2. obj.__proto__上面查詢對應的屬性

  3. obj.__proto__.__proto__上查詢結果為 null 返回 undefined

上述的這個過程就是標準的原型鏈中查詢對應屬性的過程,接下來我們來對上述的程式碼進行改造一下

obj.__proto__ = {
  message: 'Hello AAA',
}

obj.__proto__.__proto__ = {
  message: 'Hello BBB',
}

obj.__proto__.__proto__.__proto__ = {
  message: 'Hello CCC',
}

我們可以對原型物件進行重新賦值,然後obj查詢就會沿著原型鏈進行查詢,當最後沒有繼續賦值的話預設會指向Object.prorotype然後會指向null

JavaScript物件的原型和原型鏈

七.Object 詳解

在 JavaScript 中Object是一個非常特別的存在,如果你熟悉 JavaScript 那麼你肯定見過如下的程式碼

let obj = new Object()
let obj = {}

其實這兩種操作本質上是一樣的,物件字面量本質上就是使用Object建構函式來建立的,在這裏Object是一個函式,從瀏覽器的輸出我們可以看出來指向的是一個相同的物件,並且constructor屬性指向的是一個函式。

JavaScript物件的原型和原型鏈

其實我們在上面對物件進行重寫的時候我們直接使用物件覆蓋了物件的原型,同時也覆蓋了對應的constructor如果我們不重寫constructor函式將它賦值為原函式,其實賦值的這個物件的原型就是指向Object.prototype這個物件的原型是物件的預設指向。

let obj = {
  name: 'CodeUp',
  age: 12,
}

obj.__proto__ = {
  message: '招財是隻鳥',
}
console.log(obj.__proto__.__proto__)

JavaScript物件的原型和原型鏈

八.總結

這篇文章我們學習了原型和原型鏈,普通物件和函式都有自己的原型,但是比較常用的是函式的prototype我們稱之為顯式原型,物件的原型預設指向的是Object.prototype當我們對某個物件的原型進行重寫後預設指向的都是這個,原型之間這樣一層一層組成的鏈,我們稱之為原型鏈,在 JavaScript 中繼承是基於原型的,這寫內容也是我們在後續學習 ES5 相關繼承方式的前提,雖然原型設計的非常巧妙,但是它並非是 JavaScript 的原創,但是確是這門語言的設計哲學的精妙所在。


0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.