聊聊 JavaScript 的原型

JavaScript 中的原型也是一個(gè)非常讓人頭疼的東西,很多前端同學(xué)對此也是一知半解,比如我。今天我們就好好捋一捋這個(gè)原型。

創(chuàng)建對象的方式

下面就是創(chuàng)建對象的幾種方式:

var o1 = {
    a: 123,
    b: 'hello world'
}
console.log(o1.b)


function fun2() {
    this.a = 33
    this.b = 'hello o2'
}

var o2 = new fun2()
console.log(o2.b)

class Fun3 {
    constructor() {
        this.a = 365
        this.b = 'hello class'
    }
}
var o3 = new Fun3()
console.log(o3.b)

有人說這是三種創(chuàng)建方式,但是我認(rèn)為其實(shí)是兩種創(chuàng)建方式(因?yàn)?class 語法糖的本質(zhì)還是 function):直接定義對象使用 new 關(guān)鍵詞構(gòu)造對象

原型和原型鏈

當(dāng)我們創(chuàng)建了一個(gè)對象之后,就產(chǎn)生了原型(Object.create(null) 是特例)。

prototype 和 __proto__ 的區(qū)別

__proto__ 是一個(gè)非正式的屬性,很多環(huán)境中不支持該屬性。它指向當(dāng)前對象的原型。如下圖:

__proto__

上面的代碼是一段原型繼承,可以看到對象 obj1 繼承了對象 obj,所以 obj1 的 __proto__ 就指向了 obj,而 obj 的 __proto__ 則指向了 Object。所有對象的原型鏈最終都將指向 Object。

而關(guān)于 prototype 我摘錄了一段話:

當(dāng)你創(chuàng)建函數(shù)時(shí),JS 會為這個(gè)函數(shù)自動(dòng)添加 prototype 屬性,值是一個(gè)有 constructor 屬性的對象。而一旦你把這個(gè)函數(shù)當(dāng)作構(gòu)造函數(shù)(constructor)調(diào)用(即通過new關(guān)鍵字調(diào)用),那么 JS 就會幫你創(chuàng)建該構(gòu)造函數(shù)的實(shí)例,實(shí)例繼承構(gòu)造函數(shù) prototype 的所有屬性和方法。

prototype

可以看到,對象 bar 的 __proto__ 屬性指向了函數(shù) func 的 prototype

總結(jié)下,__proto__ 指向原型,而 prototype 是函數(shù)獨(dú)有且構(gòu)造的對象原型指向 prototype。

理解原型鏈

每個(gè)對象都是原型,而對象之間是可以繼承的。所以就產(chǎn)生了原型鏈??磮D說話:

原型鏈

很好理解了,我們創(chuàng)建了四個(gè)對象逐層進(jìn)行原型繼承。最后打印 obj3 對象可以看到 obj3 -> obj2 -> obj1 -> obj -> Object 這就是原型鏈。

如果我要在 obj3 對象上訪問 a 屬性,那么 JavaScript 就會順著原型鏈逐層往下找,最終在 obj 對象上找到了a 屬性,這就是原型鏈查找數(shù)據(jù)的方式。如果找到 Object 也沒有找到屬性就返回 undefined

原型鏈查找

為對象指定原型的兩種方式

那么如何為對象添加原型呢?

1. new 關(guān)鍵字

第一種就是通過構(gòu)造器的方式來創(chuàng)建。

function Foo () {
    this.a = 11
    this.b = 22
}
Foo.prototype.c = 33
Foo.prototype.func = () => {
    console.log('hello')
}

var f = new Foo()

console.log(f)
console.log(Object.getPrototypeOf(f))

當(dāng)然,不得不說的是 ES6 的 class 語法糖寫法:

class Foo {
    constructor() {
        this.a = 11
        this.b = 22
    }

    func() {
        console.log('hello')
    }
}

var f = new Foo()

兩者其實(shí)是一樣的效果,但是 class 寫法更接近常規(guī)的類寫法。(終于可以讓 function 回歸它原本的作用上了。)

2. Object.create(obj) 面向?qū)ο?/h2>

Object.create() 可以很好的實(shí)現(xiàn)原型繼承行為,也能通過 Object API 來修改原型:

var obj = { a: 123, b: 456 }
Object.setPrototypeOf(obj, { c: 789 })
var obj2 = Object.create(obj)
obj2.e = 555

代碼輸出結(jié)果如下圖,的確實(shí)現(xiàn)了為對象指定原型的行為。

原型繼承和修改

引用流還是復(fù)制流?

使用 JavaScript 原型是特別要主義的一個(gè)點(diǎn)是:JavaScript 對于原型的繼承是一種引用行為,即所引用的對象改變,繼承對象的原型也會改變。

與之相反的,有些語言會使用復(fù)制的方式。即在原型繼承時(shí)復(fù)制一份原型到當(dāng)前對象,從此被復(fù)制的對象和復(fù)制對象再無瓜葛。

總結(jié)

隨著 Object.create() 等一系列新 API 和 ES6 的 class 寫法的出現(xiàn),使用 function 作為構(gòu)造器并使用 prototype 來修改原型的方式將逐漸被拋棄。但是由于歷史原因這部分知識還是要理解其中原理的。

__proto__ 屬性是非正式屬性,不適合在通用場景下使用。

而對于原型的寫法,我認(rèn)為有兩種不錯(cuò)的處理方式:

  1. 完全使用 class 構(gòu)造器寫法來替代使用 function 構(gòu)造器的寫法來進(jìn)行面向類的開發(fā)方式。
  2. 放棄原型寫法,使用 Object 系列 API 進(jìn)行面向?qū)ο?/strong>的開發(fā)(行為委托就是這樣的方式)。

最后

關(guān)于原型,先聊這么多。明天我們聊聊基于 Object API 來實(shí)現(xiàn)的面向?qū)ο竽J?—— 行為委托,敬請期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第3章 基本概念 3.1 語法 3.2 關(guān)鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,489評論 0 21
  • ??面向?qū)ο螅∣bject-Oriented,OO)的語言有一個(gè)標(biāo)志,那就是它們都有類的概念,而通過類可以創(chuàng)建任意...
    霜天曉閱讀 2,245評論 0 6
  • 在JavaScript中,原型鏈作為一個(gè)基礎(chǔ),老生長談,今天我們就來深入的解讀一下原型鏈。 本章主要講的是下面幾點(diǎn)...
    Devinnn閱讀 1,496評論 1 6
  • 原先使用 CACurrentMediaTime() 進(jìn)行基準(zhǔn)測試,因?yàn)槠涫褂玫氖窍到y(tǒng)內(nèi)建時(shí)鐘。不同于 NSDate...
    NSPanda閱讀 1,956評論 2 1
  • 在通道間歸一化模式(ACROSS_CHANNELS)中,局部區(qū)域范圍在相鄰?fù)ǖ篱g,但沒有空間擴(kuò)展(即尺寸為 loc...
    zhenggeaza閱讀 1,009評論 0 0

友情鏈接更多精彩內(nèi)容