什么是JavaScript原型


JS 原型

轉載自【EC前端 - JavaScript原型

原型是JavaScript最重要的概念。同時也是初級開發者最忌憚的內容,原因在於網上很少有關於它的合理描述。

但事實上,原型很簡單,你可以很輕松的掌握它的知識要點。

什么是原型

了解什么是原型之前,我們先看一個示例:

var obj = {};
obj.toString(); // "[object Object]"

上面的例子中,我們聲明了一個空對象,並沒有為它添加toString屬性方法,但這個方法卻可以被成功調用並輸出。是不是覺得很神奇?

原因在於JavaScript的對象都有一個內置的[[Prototype]]私有屬性,這個屬性指向另一個對象,我們稱這個對象為原對象的原型。當JS引擎訪問objtoString屬性時,首先會去obj對象查找,發現找不到,就沿着obj[prototype]屬性去他的原型上查找。

通過Chrome開發者工具,我們可以看到這個obj原型的真面目:

JavaScript空對象的原型

圖中我們可以看到,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引擎將從左往右有序地查找目標屬性:

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() 方法獲取實例原型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM