---
# System prepended metadata

title: 原型鏈（Prototype Chain）
tags: [Lidemy-MTR05, Week16]

---

# 原型鏈（Prototype Chain）

在 [JavaScript 繼承機制](https://hackmd.io/@allenliao/H18vBKOnO) 中，prototype 扮演很重要的角色，然而什麼叫做 Prototype Chain 呢？

## prototype 與 \_\_proto\_\_

:::warning
現在更好的用法是 `Object.getPrototypeOf()`，雖然有些瀏覽器還是支援 `__proto__`，但應該盡量避免使用，詳情請見 [Object.prototype.\__proto__](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)
:::

先來看看這個例子：

```javascript
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 `p1` 跟 `p2`，還有一個放在 `Person.prototype` 裡面的方法 `sayHello`，這樣可以讓 `p1` 跟 `p2` 共用 `sayHello` 這個方法。

但是我們明明是把 `sayHello` 放在 `Person.prototype`，為什麼 `p1` 跟 `p2` 這樣就可以用了呢？

其實當我們 `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）

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

```javascript
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` 這個方法來判斷

```javascript
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
```

如此可以知道 `sayHello` 是 `p1` 的原型鏈上的方法，而不是自己的方法。

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

```javascript
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__` 會是什麼？

來人上程式碼

```javascript
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。

```javascript
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 就好了，像是這樣：

```javascript
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`。

```javascript
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 的原型鍊了](https://blog.huli.tw/2017/08/27/the-javascripts-prototype-chain/)
[new operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new)
[Object.prototype.\__proto__](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto)

###### tags: `Lidemy-MTR05`, `Week16`