Skip to content
On this page

Prototype chain 原型鍵與繼承

Javascript 是以 物件導向 設計的程式語言,沒有其它語言的 Class 功能,它的繼承方式是使用 「原型」prototype 達成的。 JavaScript 所有東西都是一 「包」 動態的屬性的 object ,除了它自己的屬性,並擁有一個原型物件的鏈結,當物件試圖存取一個物件的屬性時,其不僅會尋找該物件,也會尋找該物件的原型、原型的原型...直到找到相符合的屬性,或是到達原型鏈的尾端 null

簡單說

所有 JavaScript 的物件,都是來自原型鏈最頂端的 [Object 原型 實例]
  • Javascript 所有 原型 的根本,就是 物件原型實例
  • 所有型別,都有自已的 原型 prototype ([[prototype]]) 方法,除此之外還會繼承 上層 的原型方法。
  • 存取 屬性 時,會一直向上尋找 原型 直接 null 為止。

注意

__proto__ 在不同瀏覽器,可能會有不同的命名。

Prototype 原型觀念

每一個型態資料,都會有屬於自已型態的 原型 屬性方法,會是以 [[prototype]] 來顯示,如果想要看到 字串原型 方法可以使用 String.prototype。在 第一層[[prototype]] 可以看到當下屬性的 原型 方法,而 [[prototype]] 內的 [[prototype]] (第二層) ,就是來自再上一層的繼承。

簡單說

原型繼承的的概念,就是透過 原型 的方法,去使用本來自已就沒有的某個屬性,而這個屬性是來自「上一層」或「上上...層」所設置的。

物件原型的鏈結

js
const user = { id: 1, name: "nike" };
console.log(user);

來自原型操作方法

當我們在操作 陣列型態 時,為什麼沒有定義它的操作方法,但是我們還是可以針對 陣列 直接操作,就是因為 Javascript 在最初就針對各型態定義了,原型 (prototype) 方法。

js
const ary = [1,3,4,5]
console.log(ary)

ary.push(9) // 來自原型的操作方法

原型方法的繼承

當你在 原型 上定義了一個方法,它就是共享的。陣列 可以共用 陣列 的原型方法、物件可以共享物件的原型方法,可以操作自已沒有的 方法 就是因為來自上層的「繼承」。

直接在obj1__proto__ (原型) 上定義一個屬性 text: 'text',會發現 obj2 也共享了這個 屬性,這只是一個範例,實際上不應該使用 __proto__ 來定義 原型

js
const obj1 = {}
const obj2 = {}

obj1.__proto__.test = 'test'

obj1.__proto__.test === obj1.__proto__.test // true

TIP

當你定義了一個共享的 原型 方法,方法不會重覆的被創建,也就可以避免不必要的記憶體耗能。

原型操作

透過 原型繼承 ,可以存取本身不存在的屬性。

注意

如果本身 屬性 名稱與 原型繼承屬性相沖的話(名稱相同),就會被自身的屬性覆蓋。

為對象指定原型

語法

Object.setPrototypeOf( obj , prototype)

  • obj 對象
  • prototype對象的新原型 (一個對象 或 null)
js
const rockman = {rock: true}
const cutman = {cut: true}

console.log(rockman.cut) // undefind

Object.setPrototypeOf(rockman, cutman)

console.log(rockman.cut) // true 此時,繼承了 cutman 的屬性

繼承兩個原型

Object.setPrototypeOf 只能針對一個 對象 來繼承一個 原型,但可以透過兩次的繼承,來擁有想要的原型。

js
const rockman = {rock: true} 

const cutman = {cut: true}

const powerman = {power: true}

Object.setPrototypeOf(rockman, cutman) // rockman 繼承 cutman 屬性
Object.setPrototypeOf(cutman, powerman) // cutman 繼承 powerman 屬性

console.log('cut' in rockman) // true
console.log('power' in rockman) // true

判斷屬性方式

除了直接存取之外 (.),也可以透過 in 來判斷是否擁有這個屬性

依原型創建對象

語法

Object.create(prototype[, propertiesObject])

  • prototype 原型
  • propertiesObject 要繼承原型的對象 (option)
js
let prototype = {id: 1}
let obj = Object.create(prototype)

console.log(obj)

這時,obj 為一個空的 物件,而也擁有了 原型 屬性。

依原型建建對象,同時指定對象

js
let prototype = { nft: true }
let newObj = {
  name: {
    value: 'naiky'
  }
}

const openSea = Object.create(prototype, newObj)
console.log(openSea)

這時 對象 不為空了,也繼承了原型。

注意

指定 propertiesObject 時,如果想要的物件內容是 {name: 'naiky'},你要這樣寫:

js
{
  name: {
    value: 'naiky'
  }
}

既有對象新增原型

  • 字串 類型,新增原型方法
    js
    let str = 'HelloWorld'
    
    // 為類型新增原型方法 prototype
    String.prototype.spacify = function() {
      return this.split('').join(' ')
    }
    
    str.spacify() //  H e l l o W o r l d'
    

工廠模式的應用

js
// 先建立原型
const prototype = {
    talk() {
        console.log(this.name)
    }
}

// 建立工廠模式
const personFactory = (name) => {
    return Object.create(prototype, {
        name: {
            value: name
        }
    })
}

const niki = personFactory('niki')
niki // {name: 'niki'}

// 執行原型繼承的函式
niki.talk() // niki

Reference