JS 原型
轉載自【EC前端 - JavaScript原型】
原型是JavaScript最重要的概念。同時也是初級開發者最忌憚的內容,原因在於網上很少有關於它的合理描述。
但事實上,原型很簡單,你可以很輕松的掌握它的知識要點。
什么是原型
了解什么是原型之前,我們先看一個示例:
var obj = {};
obj.toString(); // "[object Object]"
上面的例子中,我們聲明了一個空對象,並沒有為它添加toString
屬性方法,但這個方法卻可以被成功調用並輸出。是不是覺得很神奇?
原因在於JavaScript的對象都有一個內置的[[Prototype]]
私有屬性,這個屬性指向另一個對象,我們稱這個對象為原對象的原型。當JS引擎訪問obj
的toString
屬性時,首先會去obj
對象查找,發現找不到,就沿着obj
的[prototype]
屬性去他的原型上查找。
通過Chrome開發者工具,我們可以看到這個obj
原型的真面目:

圖中我們可以看到,obj
的原型對象已經定義了一個toString
屬性。所以,空對象也可以使用toString()
這個屬性方法。
為什么需要原型
原型意義在於實現屬性的繼承。
想象一下:編寫一個JS腳本,創建1000個數組實例。如果在每個數組實例中單獨定義數組的操作方法,不僅代碼冗余,還會內存資源極大的浪費。有了原型,我們只需要把數組的操作方法定義在數組的原型上即可,實現了屬性的共享。
舉個例子:
我們希望JS的數字類型能提供一個方法判斷當前數字是否為奇數,沒有原型的情況下,你只能這么做:
var num = new Number(99);
num.isOdd = function(){return this % 100 !== 0};
num.isOdd(); // true
但是這樣是沒有意義的,因為isOdd()
方法定義在num
變量上面,其他數字類型並不能使用它:
var num2 = 100;
num2.isOdd(); // num2.isOdd is not a function
有了原型概念以后,由於所有數字類型都指向了同一個原型,我們可以把isOdd
方法定義在這個原型上,這樣,所有數字類型就都能調用到這個方法了:
var num1 = 99, num2 = 100, num3 = 0;
Number.prototype.isOdd = function(){return this % 100 !== 0};
num1.isOdd(); // true
num2.isOdd(); // false
num3.isOdd(); // false
學習后面的內容,你將明白:Number.prototype
指向數字類型的原型
原型鏈
原型並不是一個特別的存在,它也只是一個普通的對象而已。
換句話說,原型也可以擁有屬於它的原型。如果把對象的[[prototype]]
屬性想象成鏈條,就形成了一條原型鏈。
接下來,我們通過一個示例來看下JS引擎是如何通過原型鏈查找屬性的:
現在創建三個對象,並通過Object.setPrototypeOf()
方法將它們連結成一條原型鏈:
var son = {a: 1, b: 2},
parent = {b: 3, c: 4},
ancestor = {d: 5};
Object.setPrototypeOf(son, parent);
Object.setPrototypeOf(parent, ancestor);
son.a // 1
son.b // 2
son.c // 4
son.d // 5
這三個對象形成將形成一條原型鏈,JS引擎將從左往右有序地查找目標屬性:

如何設置和修改對象的原型
JavaScript分別通過 Object.setPrototypeOf() 和Object.getPrototypeOf() 兩個方法來設置和獲取對象的原型。
var parent = {type: 'parent'}, son = {type: 'son'};
Object.setPrototypeOf(son, parent);
Object.getPrototypeOf(son) === parent // true
內置對象實例的原型
JavaScript提供了一些內置對象(構造函數),比如Object, String, Array, Boolean等等,它們提供了prototype
屬性,指向實例的原型。因此,可以簡單地通過instance.constructor.prototype
來獲取
var obj = {}, str = '', arr = [], bl = true;
Object.getPrototypeOf(obj) === obj.constructor.prototype // true
Object.getPrototypeOf(str) === str.constructor.prototype // true
Object.getPrototypeOf(arr) === arr.constructor.prototype // true
Object.getPrototypeOf(bl) === bl.constructor.prototype // true
如果你不理解constructor
這個屬性,可以閱讀構造函數一節。
通過instance.constructor.prototype
這種方式獲取原型的方式並不是絕對可靠的。因為實例的constructor
屬性是可改變的(mutable)。一旦屬性,instance.constructor.prototype
便無法正確指向實例的原型。
var obj = new Object();
obj.constructor = Array;
Object.getPrototypeOf(obj) === obj.constructor.prototype // false
上面的例子中,我們隨意修改了obj的constructor
屬性,然后obj.constructor.prototype
便不再指向obj
的原型了。
另外,對於自定義構造函數而言,其constructor
也是可變的(內置構造函數的constructor
被配置為不可改變)
綜合來說,我們推薦使用 Object.getPrototypeOf() 方法獲取實例原型。