跳至主要内容

JavaScript - 原型鍊

基本介紹

JavaScript 不像 Java 或是其他物件導向程式語言,JavaScript 是沒有 class 的 (ES6 的 class 也只是語法糖 ),儘管沒有 class 卻還是可以設計類似機制來達成差不多的功能

在開始之前可以先閱讀一下這兩篇文章 从设计初衷解释 JavaScript 原型链Javascript 继承机制的设计思想

JavaScript 繼承

兩篇文章皆有提到 JavaScript 當初設計時為非常簡易的腳本語言,而 JavaScript 有又有繼承的需求,但是開發者不打算引入 class 的概念,於是將 new 引入而在 JavaScript 中, new 之後並不是像 C++ 或是 Java 會調用 clss 中的 constructor ,而是直接調用 constructor 。

舉個 🌰 在 Java 中從 class 產生 instance 的話:

Point p = new Point();

而在 JavaScript 直接連接 constructor:

// constructor
function Person(name, age) {
this.name = name;
this.age = age;
}

var nick = new Person('nick', 18);
var peter = new Person('peter', 20);

也可以在 Person 中加入 method

function Person(name, age) {
this.name = name;
this.age = age;
this.log = function () {
console.log('Hello ' + this.name);
};
}

但是這樣做時,每個 instance 都會有各自的 this.log ,但是這個 this.log 都在做一樣的事情在每個 instance 都占用各自的空間。

為了避免這樣佔用空間就可以將這個 method 抽出變成每個 instance 都可以共用,於是我們將這個 mehthod 放入 prototype

function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.log = function () {
console.log('Hello ' + this.name);
};

var nick = new Person('nick', 18);
var peter = new Person('peter', 20);

console.log(nick.log() === peter.log());
//true

原理

在上面的範例來講 nick.log() JavaScript 是怎麼找到這個 function 的?

nick 本身是沒有 log 這個 function ,而根據 JavaScript 機制, nick 是 Person 的 instance 所以當 nick 找不到時,會從 Person.prototype 尋找, 而把 nick 與 Person.prototype 連接的方式 就是 __proto__

function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.log = function () {
console.log('Hello ' + this.name);
};

var nick = new Person('nick', 18);
console.log(nick.__proto__ === Person.prototype);
//true

nick 的 __proto__ 指向 Person.prototype,而Person.prototype.__proto__ 又會指向 Object.prototype ,故此當 nick 找不到 log 這個 function 時就會靠 __proto__ 往上找,一直找到 __proto__ 為 null 時,這時就到了最上層。

而這一串透過 __proto__ 所串起來的鍊就叫做原型鍊,透過原型鍊就可以達到類似繼承的功能,做到呼叫自己 parent 的 method 。

hasOwnProperty

另外如果想知道一個屬性是存在於 instance 上或是在原型鍊當中,可以使用 hasOwnProperty

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
};

var nick = new Person('nick', 18);
console.log(nick.hasOwnProperty('log')); // false
console.log(nick.__proto__.hasOwnProperty('log')); // true

Instanceof

判斷 A 是不是 B 的 instance:

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
};

var nick = new Person('nick', 18);

console.log(nick instanceof Person); // true
console.log(nick instanceof Object); // true
console.log(nick instanceof Array); // false

另外有幾個有趣現象先不研究,先筆記一下:

// 這兩個互為彼此的 instance
console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true

// Function 的 __proto__ 會指向 Function.prototype
// 而 Function.prototype 的 __proto__ 會指向 Object.prototype
console.log(Function.__proto__ === Function.prototype); // true
console.log(Function.__proto__.__proto__ === Object.prototype); //true

// Object 的 __proto__ 會指向 Function.prototype
console.log(Object.__proto__ === Function.prototype); // true

Reference

proto和 prototype 来深入理解 JS 对象和原型链