溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中的對(duì)象原型和原型鏈?zhǔn)鞘裁匆馑?/h1>
發(fā)布時(shí)間:2020-11-21 10:01:05 來(lái)源:億速云 閱讀:158 作者:小新 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)JavaScript中的對(duì)象原型和原型鏈?zhǔn)鞘裁匆馑?,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

對(duì)象原型

相信大家都這樣用過(guò)  map

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

不知道你有沒(méi)有想過(guò), arr 本身并沒(méi)有設(shè)定 map 屬性,那為什么可以用 map 這個(gè)函數(shù)呢?

把它打印出來(lái)看看:

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

出現(xiàn)了一個(gè)名為 __proto__ 的對(duì)象,如果再將其展開,就會(huì)看到所有 Array 對(duì)象可以使用的函數(shù);當(dāng)然我們也可以在其中找到 map 函數(shù),而這也正是例子中所調(diào)用的 arr.map 這個(gè)函數(shù):

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

這里出現(xiàn)的 __proto__ 對(duì)象,也就是所謂的 原型對(duì)象(Prototype) 。

不同于 Java、C# 等基于類(Class) 的面向?qū)ο笳Z(yǔ)言,通過(guò)定義類、創(chuàng)建實(shí)例、指定繼承等方式來(lái)傳遞屬性及方法;Javascript 則是個(gè)基于原型(Prototype)的對(duì)語(yǔ)言 ,通過(guò)預(yù)先建立出的原型對(duì)象,當(dāng)新對(duì)象建立時(shí)指定對(duì)象的原型要參照哪個(gè)原型對(duì)象。

而當(dāng)我們調(diào)用對(duì)象的屬性或方法時(shí),若對(duì)象本身沒(méi)有這個(gè)屬性或方法,JavaScript 會(huì)自動(dòng)尋找它原型中的方法,這也就是為什么可以直接調(diào)用 arr.map 而不會(huì)出錯(cuò)的原因。

原型鏈

你可能已經(jīng)發(fā)現(xiàn)在前面的例子中,__proto__ 對(duì)象里仍然有 __proto__ 屬性:

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

在上述機(jī)制中,每當(dāng)對(duì)象建立時(shí)都會(huì)綁定原型,既然對(duì)象都有原型,對(duì)象原型本身也是對(duì)象,自然也不例外;由這個(gè)例子中我們可以看出:

  • arr 是數(shù)組實(shí)例,原型是 Array
  • arr.__proto__ 是數(shù)組的原型,原型是 Object
  • arr.__proto__.__proto__ 是對(duì)象的原型,原型是 null
  • arr.__proto__.__proto__.__proto__ 是 null,沒(méi)有任何屬性

由于每個(gè)對(duì)象都有原型,這樣就形成了一個(gè)關(guān)聯(lián)一個(gè)、層層相互依賴的從屬關(guān)系,我們把它們叫做原型鏈(Prototype Chain) ;通過(guò)這種機(jī)制,讓對(duì)象得以使用原型中的屬性和方法,并憑借原型鏈一層一層的按順序繼承,讓對(duì)象能擁有原型鏈上所有原型的功能,這就是 JavaScript 對(duì)象背后的運(yùn)作機(jī)制。

補(bǔ)充:在 JavaScript 中,幾乎每個(gè)原型鏈的末端都會(huì)是 Object,并最后指向到  null。

使用原型

說(shuō)了這么多,該來(lái)點(diǎn)代碼了,接下來(lái)就來(lái)練習(xí)一下原型的建立、設(shè)定及修改吧。

先來(lái)創(chuàng)建一個(gè)新的對(duì)象構(gòu)造函數(shù):

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: ?}

上面的例子創(chuàng)建了一個(gè)簡(jiǎn)單的對(duì)象構(gòu)造函數(shù) Person(),并在構(gòu)造函數(shù)中設(shè)定對(duì)象屬性。對(duì)象的方法中,由于方法不需要讓每個(gè)對(duì)象都獨(dú)自擁有一份,以避免造成冗余的內(nèi)存消耗,應(yīng)該要像前面 Array.prototype.map 的例子那樣把對(duì)象的方法設(shè)定給原型對(duì)象(Person.prototype),讓這個(gè)構(gòu)造函數(shù)創(chuàng)建出來(lái)的對(duì)象都可以共用這些方法。最后建立一個(gè)新的 Person 對(duì)象,并通過(guò) getPrototypeOf(obj) 獲取新產(chǎn)生對(duì)象的原型。

Q:為什么不直接用 __proto__ 獲取原型對(duì)象?
A:因?yàn)殡m然 __proto__ 被幾乎所有的瀏覽器支持,但它是非標(biāo)準(zhǔn)屬性;通過(guò) getPrototypeOf 取得對(duì)象的原型是正確的方法。

提醒:Person.prototype 不是 Person 的原型,而是構(gòu)造函數(shù)執(zhí)行后所建立的新對(duì)象的原型;千萬(wàn)不要把構(gòu)造函數(shù)的 prototype 屬性對(duì)象的原型搞混!

原型繼承

接著再創(chuàng)建新的對(duì)象原型,并繼承 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: ?}

這里建立了新的對(duì)象 Engineer 的原型,并通過(guò) Engineer.prototype 的指定,讓它的原型繼承自 Person.prototype,最后再重新設(shè)定 Engineer.prototype.constructor,讓構(gòu)造函數(shù)重新指回自己;這樣就完成了最基本的原型繼承。

Q:為什么需要重新設(shè)定 constructor?
A:這邊功過(guò) Object.create 復(fù)制了 Person.prototype 的全部屬性,連同 constructor 屬性都會(huì)被覆蓋掉,如果 constructor 屬性錯(cuò)誤,在做 instanceof 判斷時(shí)會(huì)產(chǎn)生錯(cuò)誤的結(jié)果;因此這邊設(shè)定繼承時(shí)需要再次將 constructor 重新指定回構(gòu)造函數(shù)本身。

修改原型

原型的引用、繼承是直接參照到原型對(duì)象上,并非是在每個(gè)對(duì)象都復(fù)制一份原型;因此可以利用這個(gè)特性,在原型上增加自定義的屬性和方法,讓所有該類型的對(duì)象都能得到新方法;許多針對(duì)舊版瀏覽器的 Polyfill 就是這樣實(shí)現(xiàn)的。

例如我們?cè)趯?Vue 項(xiàng)目的時(shí)候,可能都有做過(guò)類似的操作,把共用性高的屬性方法放到  Vue.prototype 中:

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

這樣的確很方便,但也要提醒開大家,當(dāng)我們?cè)谧鲈托薷牡臅r(shí)候要特別小心。接著剛才的例子,如果嘗試對(duì) Person 原型中的方法做個(gè)修改:

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

如結(jié)果所示,當(dāng)對(duì)象原型做修改時(shí),所有原型鏈上有這個(gè)原型的對(duì)象,通通都會(huì)受到影響,不管對(duì)象是在修改前還是修改后創(chuàng)建的。

建議大家除非是 Polyfill,否則應(yīng)該要極力避免對(duì)原生對(duì)象的原型進(jìn)行修改,防止造成可能的意外結(jié)果。

ES6 的 Class

看完前面這一大段,是不是覺(jué)得心很累?別擔(dān)心,從 ES6 開始添加了 Class 語(yǔ)法糖,使開發(fā)者體驗(yàn)提升了很多。下面把前面的例子用  Class 重構(gòu)一下:

class Person {
  constructor (name){
    this.name = name
  }
  // 方法會(huì)自動(dòng)放到 Person.prototype
  hello() {
    console.log(`Hello ${this.name}.`)
  }
}class Engineer extends Person {
  constructor (name, skill){
    super(name) // 調(diào)用 Person 的構(gòu)造函數(shù)
    this.skill = skill
  }
}let alice = new Engineer('Alice', 'JavaScript')
alice.hello() // Hello Alice.Object.getPrototypeOf(alice) 
// Person {constructor: ?}

很方便,同樣的功能,代碼的可讀性卻提高了不少,繁瑣的設(shè)定也都能交給語(yǔ)法自動(dòng)幫你完成。不過(guò)方便的語(yǔ)法背后,底層仍然是對(duì)象原型及原型鏈。

關(guān)于JavaScript中的對(duì)象原型和原型鏈?zhǔn)鞘裁匆馑季头窒淼竭@里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI