原型是JavaScript最重要的概念。同時也是初級開發者最忌憚的內容,緣由在於網上不多有關於它的合理描述。javascript
但事實上,原型很簡單,你能夠很輕鬆的掌握它的知識要點。html
瞭解什麼是原型以前,咱們先看一個示例:前端
var obj = {}; obj.toString(); // "[object Object]"
上面的例子中,咱們聲明瞭一個空對象,並無爲它添加toString
屬性方法,但這個方法卻能夠被成功調用並輸出。是否是以爲很神奇?java
緣由在於JavaScript的對象都有一個內置的[[Prototype]]
私有屬性,這個屬性指向另外一個對象,咱們稱這個對象爲原對象的原型。當JS引擎訪問obj
的toString
屬性時,首先會去obj
對象查找,發現找不到,就沿着obj
的[prototype]
屬性去他的原型上查找。數組
經過Chrome開發者工具,咱們能夠看到這個obj
原型的真面目:函數
<div class="__diagram"> <img src="../__assets/prototype_01.jpg" alt="JavaScript空對象的原型" /> </div>工具
圖中咱們能夠看到,obj
的原型對象已經定義了一個toString
屬性。因此,空對象也可使用toString()
這個屬性方法。學習
原型意義在於實現屬性的繼承。this
想象一下:編寫一個JS腳本,建立1000個數組實例。若是在每一個數組實例中單獨定義數組的操做方法,不只代碼冗餘,還會內存資源極大的浪費。有了原型,咱們只須要把數組的操做方法定義在數組的原型上便可,實現了屬性的共享。spa
舉個例子:
咱們但願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引擎將從左往右有序地查找目標屬性:
<div class="__diagram"> <img src="../__assets/prototype_02.jpg" alt="JS在原型鏈中查找屬性" /> </div>
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() 方法獲取實例原型。