JS中的原型物件

noahlam發表於2018-04-03

JS中的原型物件

白天寫了一篇【JS中建立物件的方法】,寫完以後感覺意猶未盡(實際情況是感覺原型那塊內容沒有交 代清楚),所以開這一篇繼續聊聊關於JavaScript中的原型物件

相信用過vue的童鞋,都經常這樣做,用Vue.prototype.xxx = xxx 把一個方法或者屬性新增到Vue物件的原型上, 這樣,我們在vue例項的任何地方,都可以用這個方法或屬性了,我最喜歡用的,就是把非同步請求庫 (我比較喜歡用axios)掛載到vue原型上:

// 一般是./src/mian.js
// 這裡為了方便理解就直接引入axios,實際使用,我們可以先用axios封裝一個非同步請求模組
// 在模組裡做一些攔截或者處理,然後再匯入這個模組。具體做法看
import axios from 'axios'
import Vue from 'vue'
import App from './App'
// 為所有Vue例項新增一個post模組,可以在vue例項中直接使用this.post
Vue.prototype.post = axios
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})
複製程式碼

這了是prototype就是我們所說的原型,那什麼是原型呢? 我們看看MDN給的解釋

當談到繼承時,JavaScript 只有一種結構:物件。每個物件都有一個私有屬性(稱之為 [[Prototype]]), 它指向它的原型物件(prototype)。該 prototype 物件又具有一個自己的 prototype , 層層向上直到一個物件的原型為 null。根據定義,null 沒有原型,並作為這個原型鏈中的最後一個環節。

視乎看起好有點拗口,沒關係,我用自己的話總結了一下

1.prototype其實就是存在於物件中的一個特殊的物件,你可以把它理解為物件的一個屬性或方法, 如 a.prototype,看起來是不是很像物件a的一個屬性呢?

2.每個物件都有一個prototype,除了null

那這個prototype是幹嘛的呢? 其實回頭看看上面關於vue的程式碼就知道了, prototype最主要的作用就是該原型所屬物件的所有例項,都能共享prototype裡的屬性和方法 上面的程式碼中,通過向Vue.prototype中新增一個post方法,然後就可以在所有vue例項中使用該方法,就是個簡單的實踐。

我們回頭看看 【JS中建立物件的方法】裡面的原型模式

function Student(){}   // 宣告一個空函式
Student.prototype.name = 'xiaohong'
Student.prototype.age = 17
Student.prototype.gender = 'f'
Student.prototype.study = fucntion() { console.log('我在學習...')}
複製程式碼

我們先定義了一個空函式,注意:這個時候,我們並沒有認為的給函式新增一個prototype屬性/方法, 而Student卻自動有了prototype,然後我們往prototype裡面新增了name,age,gender屬性和study方法, 然後我們用new例項化2個Student物件出來

var studentA = new Student()
console.log(studentA.name)    // xiaohong
console.log(studentA.age)     // 17
console.log(studentA.gender)  // f
studentA.study()              // 我在學習...

var studentB = new Student()
console.log(studentB.age)     // xiaohong
console.log(studentB.name)    // 17
console.log(studentB.gender)  // f
studentB.study()              // 我在學習...
複製程式碼

上面的例子可以看出,物件的prototype裡面的屬性和方法,在該物件的所有例項裡面,都是共享的 那如果我們想要讓例項物件有自己的屬性/方法,該怎麼辦呢? 比如,我想讓studentB的名字是'lili', 很簡單,直接在例項物件上新增該屬性/方法:

studentA.name = 'lili'
studentA.study = function () {
    console.log('我在偷懶')
}

console.log(studentA.name)  // lili
console.log(studentB.name)  // xiaohong
studentA.study()            // 我在偷懶
studentB.study()            // 我在學習...
複製程式碼

可以看出,studentA的屬性/方法被改變的時候,studentB沒有對應的跟著改變,這是為什麼呢? 不是說好的全所有prototype裡的屬性/方法都是共享的嗎?事實上,prototype裡的屬性/方法,確實是共享的, 問題出在我們是在例項物件上賦值,所以這個屬性/方法,是屬於例項的,而不是屬於prototype的,prototype的屬性, 也無法在例項物件上寫入,也就是說,例項物件和prototype上,同時存在了 name屬性和study方法,那麼, 為什麼 studentA 和 studentB 訪問到的屬性/方法 會不一樣呢? 其實每次訪問一個屬性/方法的時候, 都會先從例項物件開始查詢,如果例項上有,就直接返回,如果例項上沒有,就繼續往prototype上查詢,有就返回, 如果prototype上還有 prototype,那麼還會繼續網上查詢,直到原型鏈的最頂層。如果都沒有查到,則會返回undefined。

那麼新的問題來了,我們該如何判斷一個屬性,是屬於例項本身的,還是屬於prototype的? 答案是hasOwnProperty方法, hasOwnProperty方法可以檢測到例項物件裡面有沒有給定的屬性,該方法只能檢測到例項裡面的屬性,檢測不到prototype上的

studentA.hasOwnProperty('name')  // true
studentB.hasOwnProperty('name')  // false
複製程式碼

那如果想同時查詢例項物件和原型物件prototype呢?我們可以用 in 操作符

'name' in studentA    // true
'name' in studentB    // true
複製程式碼

有了這2個方法,我們就可以組合起來判斷屬性是屬於例項還是原型了。

這裡老是說到例項,不得不提一下,例項物件雖然是建構函式“構造”出來的,但是其實跟建構函式沒有直接聯絡, 例項物件內部指向的是建構函式的prototype(原型)。 例項跟建構函式的一個間接關係是 例項.prototype.constructor --> 建構函式

關於原型的介紹就到這裡,有需要更深入的童鞋,建議去讀一下javascript權威指南。裡面關於原型的介紹比我這詳細。

下面列幾個javascript權威指南里面介紹的關於原型的方法

  1. 獲取一個例項物件的原型 (ES5才支援)

    Object.getPrototypeOf(studentA) // Student.prototype

    部分瀏覽器(chrome,safari,firefox)也支援一個屬性 __proto__

    studentA.__proto__ == Student.prototype

  2. 判斷一個建構函式是否是指定例項物件的原型

    Student.prototype.isPrototypeOf(studentA) // true

相關文章