首頁  >  文章  >  web前端  >  了解JavaScript中的物件原型和原型鏈

了解JavaScript中的物件原型和原型鏈

青灯夜游
青灯夜游轉載
2020-11-20 17:26:329552瀏覽

本篇文章帶大家介紹一下JavaScript中的物件原型和原型鏈。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

了解JavaScript中的物件原型和原型鏈

物件原型

相信大家都這樣用過 map

let arr = [0, 1, 2]
let doubleArr = arr.map(c => c * 2)
console.log(doubleArr) // 0, 2, 4

不知道你有沒有想過, arr 本身並沒有設定map 屬性,那為什麼可以用map 這個函數呢?

把它印出來看看:

console.log(arr)
// 0: 0
// 1: 1
// 2: 2
// length: 3
// __proto__: Array(0)

出現了一個名為__proto__ 的對象,如果再將其展開,就會看到所有Array 物件可以使用的函數;當然我們也可以在其中找到map 函數,而這也正是例子中所呼叫的arr.map 這個函數:

console.log(arr.map === arr.__proto__.map) // true

這裡出現的__proto__ 對象,也就是所謂的原型對象(Prototype)

不同於Java、C# 等基於類別(Class) 的物件導向語言,透過定義類別、建立實例、指定繼承等方式來傳遞屬性及方法;Javascript 則是個基於原型(Prototype)的對語言 ,透過預先建立的原型對象,當新物件建立時指定物件的原型要參考哪個原型物件。

而當我們呼叫物件的屬性或方法時,若物件本身沒有這個屬性或方法,JavaScript 會自動尋找它原型中的方法,這也就是為什麼可以直接呼叫arr.map 而不會出錯的原因。

原型鏈

你可能已經發現在前面的例子中,__proto__ 物件裡仍然有__proto__ 屬性:

console.log(arr.__proto__) // Array 的 Prototype
console.log(arr.__proto__.__proto__) // Object 的 Prototype
console.log(arr.__proto__.__proto__.__proto__) // null

在上述機制中,每當物件建立時都會綁定原型,既然物件都有原型,物件原型本身也是對象,自然也不例外;由這個例子中我們可以看出:

  • arr 是陣列實例,原型是Array
  • #arr.__proto__ 是陣列的原型,原型是Object
  • #arr.__proto__.__proto__ 是物件的原型,原型是null
  • arr.__proto__.__proto__.__proto__ 是null,沒有任何屬性

由於每個物件都有原型,這樣就形成了一個關聯一個、層層相互依賴的從屬關係,我們把它們叫做原型鏈(Prototype Chain) ;透過這個機制,讓物件得以使用原型中的屬性和方法,並憑藉原型鏈一層一層的依序繼承,讓物件能擁有原型鏈上所有原型的功能,這就是JavaScript 物件背後的運作機制。

補充:在 JavaScript 中,幾乎每個原型鏈的末端都會是 Object,並且最後指向到  null

使用原型

說了這麼多,該來點程式碼了,接下來就來練習一下原型的建立、設定及修改吧。

先來建立一個新的物件建構子:

function Person(name) {
  this.name = name
}Person.prototype.hello = function () {
  console.log(`Hello ${this.name}.`)
}let gary = new Person('Gary')
gary.hello() // Hello Gary.Object.getPrototypeOf(gary) // {hello: ƒ, constructor: ƒ}

上面的範例建立了一個簡單的物件建構子Person(),並在建構子中設定對象屬性。在物件的方法中,由於方法不需要讓每個物件獨自擁有一份,以避免造成冗餘的記憶體消耗,應該要像前面Array.prototype.map 的範例那樣把物件的方法設定給原型物件(Person.prototype),讓這個建構函式建立出來的物件都可以共用這些方法。最後建立一個新的 Person 對象,並透過 getPrototypeOf(obj) 取得新產生對象的原型。

Q:為什麼不直接用 __proto__ 取得原型物件?
A:因為雖然 __proto__ 被幾乎所有的瀏覽器支持,但它是非標準屬性;透過 getPrototypeOf 取得物件的原型是正確的方法。

提醒:Person.prototype 不是Person 的原型,而是建構子執行後所建立的新物件的原型;千萬不要把建構函數的prototype 屬性物件的原型搞混!

原型繼承

接著再建立新的物件原型,並繼承Person

function Engineer(name, skill) {
  Person.call(this, name)
  this.skill = skill
}
Engineer.prototype = Object.create(Person.prototype)
Engineer.prototype.constructor = Engineerlet alice = new Engineer('Alice', 'JavaScript')
alice.hello() // Hello Alice.
console.log(alice.skill) // JavaScriptObject.getPrototypeOf(alice) 
// Person {constructor: ƒ}

這裡建立了新的物件Engineer 的原型,並透過Engineer.prototype 的指定,讓它的原型繼承自Person.prototype,最後重新設定Engineer.prototype.constructor,讓建構子重新指回自己;這樣就完成了最基本的原型繼承。

Q:为什么需要重新设定 constructor
A:这边功过 Object.create 复制了 Person.prototype 的全部属性,连同 constructor 属性都会被覆盖掉,如果 constructor 属性错误,在做 instanceof 判断时会产生错误的结果;因此这边设定继承时需要再次将 constructor 重新指定回构造函数本身。

修改原型

原型的引用、继承是直接参照到原型对象上,并非是在每个对象都复制一份原型;因此可以利用这个特性,在原型上增加自定义的属性和方法,让所有该类型的对象都能得到新方法;许多针对旧版浏览器的 Polyfill 就是这样实现的。

例如我们在写 Vue 项目的时候,可能都有做过类似的操作,把共用性高的属性方法放到  Vue.prototype 中:

Object.defineProperty(Vue.prototype, '$date', { value: dateTimeFormat })
// 之后就可以这样用
vm.$date(dateObj)

这样的确很方便,但也要提醒开大家,当我们在做原型修改的时候要特别小心。接着刚才的例子,如果尝试对 Person 原型中的方法做个修改:

Person.prototype.hello = function () {
  console.log(`Bye ${this.name}.`)
}gary.hello() // Bye Gary.
alice.hello() // Bye Alice.

如结果所示,当对象原型做修改时,所有原型链上有这个原型的对象,通通都会受到影响,不管对象是在修改前还是修改后创建的。

建议大家除非是 Polyfill,否则应该要极力避免对原生对象的原型进行修改,防止造成可能的意外结果。

ES6 的 Class

看完前面这一大段,是不是觉得心很累?别担心,从 ES6 开始添加了 Class 语法糖,使开发者体验提升了很多。下面把前面的例子用  Class 重构一下:

class Person {
  constructor (name){
    this.name = name
  }
  // 方法会自动放到 Person.prototype
  hello() {
    console.log(`Hello ${this.name}.`)
  }
}class Engineer extends Person {
  constructor (name, skill){
    super(name) // 调用 Person 的构造函数
    this.skill = skill
  }
}let alice = new Engineer('Alice', 'JavaScript')
alice.hello() // Hello Alice.Object.getPrototypeOf(alice) 
// Person {constructor: ƒ}

很方便,同样的功能,代码的可读性却提高了不少,繁琐的设定也都能交给语法自动帮你完成。不过方便的语法背后,底层仍然是对象原型及原型链。

总结

以上是 JavaScript 中关于对象原型的说明,希望能帮你理解对象原型,在这个什么都是对象的语言中,充分理解并掌握对象原型,是成为专业码农必须要突破的关卡之一。

更多编程相关知识,请访问:编程课程!!

以上是了解JavaScript中的物件原型和原型鏈的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除