JS基礎—原型對象的那些事(一)

首次發表在:JS基礎—原型對象的那些事(一)

談起js的基礎,繞不過去的坎就是:原型鏈、作用域鏈、this(em...好吧,還有閉包),今天總結一下關于原型對象的一些知識,供自己和大家復習。

概念理解

什么是原型對象呢?有以下幾點:

1.構造函數有一個prototype屬性,指向其實例的原型對象。

而實例有一個 __proto__ 屬性,也指向該實例的原型對象。

注意函數的是prototype屬性,實例的是__proto__屬性,不要弄錯。

舉個栗子,我們有一個構造函數Person:


function Person(name) {

    this.name = name

}

這時,我們創建一個Person的實例person


var person = new Person("張三")

按照上邊的理論,就可以表示為:

Person.prototype === person.__proto__

他們指向的都是原型對象。

2.通過同一個構造函數實例化的多個實例對象具有同一個原型對象。


var person1 = new Person("張三")

var person2 = new Person("李四")

person1.__proto__person2.__proto__Person.prototype 他們是兩兩相等的。

3.原型對象有一個constructor屬性,指向該原型對象對應的構造函數。

Person.prototype.constructor === Person

person.__proto__.constructor === Person

4.實例對象本身并沒有constructor屬性,但它可以繼承原型對象的constructor屬性。

person1.constructor === Person

person2.constructor === Person

作用

OK,說清楚了什么是原型,就要說一下這玩意是干嘛用的,為啥要在構造函數里加這么個東西。

還是以構造函數Person為例,稍微改一下:


function Person(name) {

    this.name = name

    this.sayName = function() {

        console.log(this.name)

    }

}

var person1 = new Person("張三")

var person2 = new Person("李四")

我們在構造函數Person中增加了一個方法sayName,這樣Person的實例person1person2各自都有了一個sayName方法。

注意,我說的是各自,什么意思呢?就是說每次創建一個實例,就要在內存中創建一個sayName方法,這些sayName并不是同一個sayName

person1.sayName === person2.sayName

-> false

多個實例重復創建相同的方法,這顯然是浪費資源的。這個時候,我們的原型對象登場了。假如構造函數中的方法我們這樣寫:


function Person(name) {

    this.name = name

}

Person.prototype.sayName = function() {

    console.log(this.name)

}

var person1 = new Person("張三")

var person2 = new Person("李四")

和之前的區別是,我們將sayName方法寫到了構造函數的原型對象上,而不是寫在構造函數里。

這里要先提一個概念,就是當對象找屬性或者方法時,先在自己身上找,找到就調用。在自己身上找不到時,就會去他的原型對象上找。這就是原型鏈的概念,先點到這,大家知道這件事就可以了。

還記得之前說的嗎:

通過同一個構造函數實例化的多個實例對象具有同一個原型對象

person1person2上顯然是沒有sayName方法的,但是他們的原型對象有啊。

所以這里的person1.sayNameperson2.sayName,實際上都是繼承自他原型對象上的sayName方法,既然原型對象是同一個,那sayName方法自然也是同一個了,所以此時:

person1.sayName === person2.sayName

-> true

將需要共享的方法和屬性放到原型對象上,實例在調用這些屬性和方法時,不用每次都創建,從而節約資源,這就是原型對象的作用。

共享帶來的“煩惱”

但是,既然是共享,就有一點問題了,還是Person構造函數,我們再改造一下。


function Person(name) {

    this.name = name

}

Person.prototype.ageList = [12, 16, 18]

var person1 = new Person("張三")

var person2 = new Person("李四")

這個時候,我們在person1上做一些操作:

person1.ageList.push(30)

看一下此時person2.ageList是什么:

person2.ageList

-> [12, 16, 18, 30]

有點意思,person2上的ageList也多了30。

原因其實還是因為共享。

共享不好的地方就是:一個實例對引用類型(數組、對象、函數)的屬性進行修改,會導致原型對象上的屬性修改(其實修改的就是原型對象上的屬性,實例是沒有這個屬性的),進而導致所有的實例中,這個屬性都改了!

很顯然,大部分時候,我們喜歡共享,可以節約資源。但是不喜歡每一個實例都受影響,要不還創建不同的實例干嘛,用一個不就好了(攤手)。

所以,我們需要把那些需要共享的屬性和方法,寫在原型對象上,而每個實例單獨用的、不希望互相影響的屬性,就寫在構造函數里邊。類似這樣:


function Person(name) {

    this.name = name

    this.ageList = [12, 16, 18]

}

var person1 = new Person("張三")

var person2 = new Person("李四")

person1.ageList.push(30)

person1.ageList

-> [12, 16, 18, 30]

person2.ageList

-> [12, 16, 18]

此處有坑

關于原型對象,還有兩個坑,需要和大家說一下。


function Person(name) {

this.name = name

}

Person.prototype.ageList = [12, 16, 18]

var person1 = new Person("張三")

var person2 = new Person("李四")

person1.ageList.push(30)

person2.ageList

-> [12, 16, 18, 30]

這個沒毛病,但是假如我把操作

person1.ageList.push(30)

改為

person1.ageList = [1, 2, 3],結果會怎樣呢?

person2.ageList

-> [12, 16, 18]

這里就奇怪了,都是對person1.ageList進行操作,怎么就不一樣呢?

其實原因在于,person1.ageList = [1, 2, 3]是一個賦值操作。

我們說過,person1本身是沒有ageList屬性的,而賦值操作,會給person1增加自己的ageList屬性。既然自己有了,也就不用去原型對象上找了。這個時候,原型對象的ageList其實是沒有變化的。而person2沒有自己的ageList屬性,所以person2.ageList還是繼承自原型,就是[12, 16, 18]


function Person(name) {

    this.name = name

}

Person.prototype = {

    ageList : [12, 16, 18]

}

var person1 = new Person("張三")

var person2 = new Person("李四")

person1.ageList.push(30)

person2.ageList -> [12, 16, 18, 30]

這里依然沒毛病,但是寫法上有一個變化:我們不再采用Person.prototype.ageList = [12, 16, 18]的形式賦值,而是給Person.prototype賦值了一個對象,對象中有ageList

這樣看貌似沒有問題,用起來也都一樣:改變person1.ageListperson2.ageList也變化了,說明person1.ageListperson2.ageList還是繼承自同一個原型對象。

但是,這里有一個問題,之前我們說過:

實例對象本身并沒有constructor屬性,但它可以繼承原型對象的constructor屬性

但是此時

person1.constructor === Person

-> false

person2.constructor === Person

-> false

為什么呢?

因為通過給Person.prototype賦值一個對象,就修改了原型對象的指向,此時原型對象的constructor指向內置構造函數Object了,使用Person.prototype.ageList = [12, 16, 18]的形式賦值,就不會造成這樣的問題。

所以當給原型對象賦值一個新對象時,切記將原型對象的constructor指回原構造函數:


Person.prototype.constructor = Person

以上就是本次分享的內容,關于原型對象的其他知識,下一次再分享給大家。

ps:寫的啰啰嗦嗦的,也不知道大家會不會看著煩。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容