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

談起js的基礎,繞不過去的坎就是:原型鏈、做用域鏈、this(em...好吧,還有閉包),今天總結一下關於原型對象的一些知識,供本身和你們複習。javascript

概念理解

什麼是原型對象呢?有如下幾點:
1.構造函數有一個prototype屬性,指向構造函數的原型對象。而實例有一個__proto__屬性,也指向原型對象。java

PS: 準確的說,實例指向原型對象的是[[Prototype]]屬性,但這是一個隱式屬性,腳本不可訪問。所以瀏覽器廠商提供了一個屬性__proto__,用來顯式指向原型對象,但它並非ECMA規範。segmentfault

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

舉個栗子,咱們有一個構造函數Person:瀏覽器

function Person(name) {
    this.name = name
}

這時,咱們建立一個Person的實例person閉包

var person = new Person("張三")

按照上邊的理論,就能夠表示爲:函數

Person.prototype === person.__proto__this

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

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

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]

此處有坑

關於原型對象,還有兩個坑,須要和你們說一下。

1.

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]

2.

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

以上就是本次分享的內容,關於原型對象的其餘知識,下一篇JS基礎—原型對象的那些事(二)會講到。

ps:寫的囉囉嗦嗦的,也不知道你們會不會看着煩。。。

相關文章
相關標籤/搜索