原型鏈(Prototype Chain)


Posted by rockyooooooo on 2021-06-30

JavaScript 繼承機制 中,prototype 扮演很重要的角色,然而什麼叫做 Prototype Chain 呢?

prototype 與 __proto__

現在更好的用法是 Object.getPrototypeOf(),雖然有些瀏覽器還是支援 __proto__,但應該盡量避免使用,詳情請見 Object.prototype.__proto__

先來看看這個例子:

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')
var p2 = new Person('kaneshiro')
p1.sayHello() // allen: hello~
p2.sayHello() // kaneshiro: hello~
console.log(p1.sayHello === p2.sayHello) // true

我們有一個建構函式 Person,且它有兩個 instance p1p2,還有一個放在 Person.prototype 裡面的方法 sayHello,這樣可以讓 p1p2 共用 sayHello 這個方法。

但是我們明明是把 sayHello 放在 Person.prototype,為什麼 p1p2 這樣就可以用了呢?

其實當我們 new 了一個新的 instance 時,會發生以下幾件事:

  1. 建立一個空白的 JavaScript 物件
  2. 新增 __proto__ 屬性到該新物件,這個屬性會指向建構函式的 prototype 物件
  3. this 綁定到該新物件,也就是建構函式中的 this 會指向該新物件
  4. 如果函式沒有 return 任何東西,就會 return this

所以 p1.__proto__ 會指向 Person.prototype,當 JavaScript 在 p1 找不到 sayHello 這個方法,就會透過 p1.__proto__ 找到 Person.prototype,並在 Person.prototype 找到 sayHello

假設在 Person.prototype 還是沒有找到,就會在往 Person.prototype.__proto__ 去找,直到某個東西的 __proto__null 為止,代表找到最上層了。這樣一直用點相連起來的鏈結,就叫做原型鏈(Prototype Chain)

寫成程式碼應該會比較有感覺:

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

console.log(p1.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) //true
console.log(Object.prototype.__proto__) // null

hasOwnProperty

而要知道一個屬性或方法是存在 instance 自己裡面還是在他的原型鏈上,可以用 hasOwnProperty 這個方法來判斷

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

console.log(p1.hasOwnProperty('sayHello')) // false
console.log(p1.__proto__.hasOwnProperty('sayHello')) // true

如此可以知道 sayHellop1 的原型鏈上的方法,而不是自己的方法。

我們也可以自己寫一段程式碼,來找到這個 sayHello 方法並執行他:

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

function call(obj, methodName) {
    var methodOwner = obj

    // 不斷往上找,直到找到真正擁有這個 method 的人,或 methodOwner 已經是 null(到最上層了)
    while(methodOwner && !methodOwner.hasOwnProperty(methodName)) {
        methodOwner = methodOwner.__proto__
    }

    // 找不到 method 時 methodOwner 會是 null,印出資訊並 return
    if (!methodOwner) return console.log('method not found')

    // 找到 method,用 apply 的方式來呼叫,this 的值才會正確
    methodOwner[methodName].apply(obj)
}

call(p1, 'sayHello') // allen: hello~
call(p1, 'sayByeBye') // method not found

現在可以思考看看,Person.__proto__ 會是什麼?

來人上程式碼

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

console.log(Person.__proto__ === Function.prototype) // true

其實 Person 就是 Function 的 instance 啦!所以 Person.__proto__ 就是 Function.prototype

instanceof

A instanceof B 可以判斷 A 不不是 B 的 instance。

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

console.log(p1 instanceof Person) // true
console.log(Person instanceof Object) // true
console.log(Person instanceof Function) // true
console.log(Person instanceof Array) // false

其實原理也很簡單,只要檢查 A 的原型鏈裡有沒有 B 的 prototype 就好了,像是這樣:

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

Person.prototype.sayHello = function() {
    console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

function instanceOf(A, B) {
    // 找到最上層了還是找不到
    if(!A) return false
    // 找不到的話,就往 A 的上層繼續找
    return A.__proto__ === B.prototype ? true : instanceOf(A.__proto__, B)
}

constructor

每個 prototype 還有一個屬性叫做 constructor,這個屬性會指向建構函式,也就是說 Person.prototype.constructor 就是 Person

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

Person.prototype.sayHello = function() {
  console.log(this.name + ': hello~')
}

var p1 = new Person('allen')

console.log(Person.prototype.constructor === Person) // true

參考資料

該來理解 JavaScript 的原型鍊了
new operator
Object.prototype.__proto__


#javascript







Related Posts

MTR_0928

MTR_0928

Git branch

Git branch

Git 與 GitHub

Git 與 GitHub


Comments