1、正統的類與繼承javascript
類是對象的定義,而對象是類的實例(Instance)。類不可直接使用,要想使用就必須在內存上生成該類的副本,這個副本就是對象。html
以Java爲例:前端
public class Group { } // 建立一個類java
Group a = new Group(); // 實例化一個對象程序員
經過繼承,子類能夠直接從父類得到其全部的屬性和方法,繼承的實現機制是"複製、拷貝"。瀏覽器
public class Child extends Parent { } // 建立一個子類,繼承父類的方法dom
2、原型與繼承 ide
和正統的面嚮對象語言不一樣,JavaScript中不存在正統意義的"類"。緣由是Brendan Eich在設計JavaScript的時候,不但願又從新設計一門面向對象的語言(由於已經有C++和Java了),而且他但願把JavaScript設計得更簡單,所以他借鑑了Java的語法,用構造函數代替類,因此JavaScript中的對象是經過構造函數建立的。函數
這並不奇怪,JavaScript自己就是一個借鑑多種語言,倉促交媾的產物。idea
嚴格的講,JavaScript中既不存在類,也不存在實例,儘管這些概念是如此的深刻人心,以至於誤用起來是那麼順其天然,但咱們仍是須要明白這一點。
沒有類,那麼JavaScript怎麼實現繼承呢? Brendan Eich爲構造函數設置了一個prototype屬性。這個屬性包含一個對象(即"原型對象"),全部實例對象須要共享的屬性和方法,都放在這個對象裏面;不須要共享的屬性和方法,就放在構造函數裏面。
與類繼承的"複製、拷貝"不一樣,原型繼承的機制是"引用、關聯"。JavaScript中全部的對象都是由構造函數生成的,每一個對象都共同繼承構造函數的原型對象中的屬性和方法。
在對象內部有兩種方式訪問它繼承的原型:
一、obj.constructor.prototype
二、obj.__proto__
第一種方法,源於每一個對象都擁有一個內置屬性constructor指向其構造函數;第二種方法源於每一個對象都有一個內部指針[[prototype]],直接指向其原型,這個內部指針是不可訪問的,可是有些瀏覽器暴露出一個__proto__屬性,用於直接訪問原型對象。
3、原型鏈與繼承
由於全部對象都是由構造函數生成的,也就是說全部對象,都必然繼承某個原型,而原型自己也是對象,它也會繼承別的原型,如此環環相扣就造成了原型鏈。
JavaScript的對象系統是一個相似族譜的樹狀結構,每個分支都經過原型鏈一脈相承。
基於原型鏈的繼承機制和做用域鏈的工做機制很是相似:當調用對象的某個屬性時,若是對象的自有屬性中不存在該屬性,那麼JavaScript引擎就會沿着該對象的原型鏈向上查找,直到找到第一個匹配的屬性名爲止。
原型鏈總有個盡頭吧,就好像DOM只有惟一的根元素同樣,全部原型鏈都匯聚到同一個源頭,那就是Object.prototype,它也是一個對象,也有[[prototype]]屬性,那麼Object.prototype.__proto__ === ?答案是null。
有人特別擅長髮揮,就根據這個大談「JavaScript原型設計的哲學思想」,什麼道生於無,一輩子二,二生三,三生萬物……這個真的想多了,一個花兩週時間搞出來的「KPI項目」,怎麼也扯不到哲學上去,這就是外行學前端的一個寫照。原型鏈系統中每一個對象都必須關聯到另外一個對象,而且不能循環引用,那麼原型鏈就只能無限延伸下去,這顯然是不可能的,所以只好把個不三不四的 null 拿來做爲原型鏈的終結點,這也在必定程度上解釋了 null 明明不是對象,可是它的數據類型倒是對象。
4、原型的關聯規則
每一個對象的[[prototype]]具體指向誰,或者說每一個對象的原型到底是誰,取決於對象的建立方式。
一、對象直接量的原型對象是Object.prototype;
var obj = { }; obj.__proto__ === Object.prototype // true
二、經過Object.create( )建立的對象,其原型是由第一個參數指定的對象;
對象直接量等價於Object.create(Object.prototype)
三、經過構造函數建立的對象,其原型即構造函數的原型對象。
內置的構造函數的prototype原型是預設好的,咱們能夠修改。內置構造函數的prototype原型並不都是普通的對象,例如:
typeof Function.prototype // "function"
Array.isArray( Array.prototype ) // true
但這不重要。
自定義的構造函數,它的原型默認是一個僅含constructor屬性的對象。
function f () {}; Object.getOwnPropertyNames(f.prototype) // "constructor"
比較特殊的是函數也有繼承的原型。注意,這裏很容易混淆「函數的繼承原型」與「函數的prototype原型」,它們是兩回事,函數不會從自身的prototype指向的原型對象中繼承任何屬性或方法,全部的函數都共同繼承一個原型對象,那就是Function.prototype。
Array.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
var f = function () { }; f.__proto__ === Function.prototype // true
爲何函數也有__proto__屬性?這不奇怪,由於函數也是對象(可調用的對象)。
5、從零構建JavaScript對象系統
根據以上規則,咱們試着從零開始構建JavaScript的對象系統。
首先,創造一個構造函數Object,爲JavaScript空空如也的對象世界播下第一顆種子。而後給它添一些屬性和方法。在Chrome控制檯輸入Object.getOwnPropertyNames(Object),能夠看到Object有二十五個屬性,其中就有prototype。
prototype幾乎就是JavaScript的繁育後代的生殖系統,因此它很關鍵,咱們須要傳給它一些公共屬性和方法:
Object.prototype = {/* properties */}
而後須要給Object定義私有屬性,雖然它是函數,但畢竟函數也是對象,因此可以定義屬性:
Object.create = function ( ) { }; Object.keys = function ( ) { } ……
一樣的咱們須要給Function設置原型,可是這個原型不是普通對象,而是函數,所以不能用對象直接量:
Function.prototype = {/* properties */} // wrong
Function.prototype = function () { } // right
至於爲何這麼作,規範裏只有規定,沒有解釋。
(參考:http://stackoverflow.com/questions/39698919/why-typeoffunction-prototype-is-function)
而後將Function.prototype.__proto__ 設置爲 Object.prototype。
如法炮製,因而咱們有了九大構造函數:Object、Function、Array、String、Number、Boolean、Date、Error、RegExp。
再來兩個單體內置對象:Math和JSON
var Math = new Object(); // 這裏採用new「實例化」了一個對象
Math.random = ……
Math.max = ……
……
如今原型對象之間的關聯有了,函數是否是也效仿着弄一個繼承呢,這樣就省得給每一個函數都定義相同的方法了,簡單粗暴,直接將函數的[[prototype]]都指向Function.prototype完事兒。
JavaScript內置的原型系統基本上就完成了,其它的構造函數和對象就留給程序員自定義設置吧。
參考:阮一峯《JavaScript繼承機制的設計思想》
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html