[JavaScriptWeird]No.69 從 prototype 來看「原型鏈」

前言

我們用上一節的範例來解釋什麼是 原型 (prototype)原型鏈 (prototype chain),然後也不難發現 JavaScript 很多底層的觀念都是鏈狀的。

回顧上一節的 ES5 範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function dog(name){  
this.name = name;
}

dog.prototype.getName = function(){
return this.name;
}

dog.prototype.sayHello = function(){
console.log(this.name + ' say Hello');
}

var d = new dog('abc');
d.sayHello();
console.log(d.__proto__);

上一節的內容中,我們知道只要於 dog.prototype 上新增方法,這樣之後透過 new 產生的 dog 物件都具有 sayHello 方法。

以這個例子來說,當我們印出 d 的內容時,可以發現當中有個隱藏的屬性「.__proto__」,也發現寫在 dog.prototype 上的方法在這邊出現了。

甚至會發現怎麼點開了「.proto」裡面還有一個「.proto」?

「.proto

以這個例子白話的說,意思就是當在變數 d 指向的物件身上找不到對應的方法時,便從「.__proto__」內尋找有沒有對應的方法,聽起來是不是跟之前解釋的 ScopeChain 有點像呢。

1
2
... 省略 ...  
console.log(d.__proto__ === dog.prototype); // true

而使用三等號來比較的話,可以得知兩者是一樣的。

當呼叫 d.sayHello() 的過程

1
2
... 省略 ...  
d.sayHello();
  • 檢查變數 d 指向的物件屬性內有沒有 sayHello ? 這個例子沒有。
  • 接著找該物件的 __proto__ 屬性內有沒有 sayHello若有就停止不會繼續往後找下去。

以這個例子來說,在步驟二時就找到了,那如果沒有的情況呢?

還記得上面的一張圖嗎?

怎麼點開了「.__proto__」裡面還有一個「.__proto__」?

  • 如果還是找不到,便繼續往 .__proto__ 尋找,如 d.__proto__.__proto__

此時先抽離這個步驟,從這邊可以觀察出「.__proto__」是一層一層的,而越後面的「.__proto__」就越接近「底」的部分。

而本例中 dog 物件的「.__proto__」下一層就是原始物件的 prototype ,所以下面程式碼為 true

1
console.log(d.__proto__.__proto__ === Object.prototype); // true

如何知道是不是已經找到底層了呢?只要輸出為 null 代表上一層就是頂層了。

1
console.log(d.__proto__.__proto__.__proto__); // null

回到剛才的步驟

  • 如果還是找不到,便會繼續往 .__proto__ 尋找,直到找到為止
  • 如果找到底層了還是沒有,則跳出錯誤訊息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function dog(name){  
    this.name = name;
    }

    dog.prototype.getName = function(){
    return this.name;
    }

    var d = new dog('abc');
    d.sayHello();

整理一下對應的關聯

可以得知這個過程是一層一層逐漸地往下找的,而這個過程被稱之為原型鏈 (prototype chain),其實跟範圍鏈 (scope chain) 有點相似。

這邊也順手觀察一下 dog.__proto__ 是什麼

1
2
console.log(dog.__proto__); // ƒ () { \[native code\] }  
console.log(dog.__proto__ === Function.prototype); //true

因為 dog 本身就是一個函式,出現這樣子是符合預期的。

觀察至此,大部分的疑惑都解開,只剩下 new 了,接下來會提到 new 到底做了些什麼。

知道了這些可以做什麼?

可以進行一些比較有趣的事情,像是我們可以在 String 的原型上增加一個自己寫的方法,這樣之後只要型別屬於 String 就可以使用。

1
2
3
4
5
6
7
8
String.prototype.getFirst = function(){  
return this[0];
}

var a = '123';
console.log(a.__proto__ === String.prototype); // true
console.log(String.prototype);
console.log(a.getFirst()); // 1

0%