這裏,咱們列出原型的幾個概念,以下:javascript
__proto__
只要建立了一個函數,就會爲該函數建立一個prototype
屬性,指向該函數的原型對象。實例對象是不會擁有該屬性的。
默認狀況下,該原型對象
也會得到一個constructor
屬性,該屬性包含一個指針,指向prototype
屬性所在的函數。css
Person.prototype.constructor===Person
__proto__
javascript中,不能夠訪問的內部屬性都是用[[propertyName]]
這種形式來表示的,好比還有枚舉屬性[[Enumberable]]。java
[[prototype]]
屬性只能是對象能夠擁有的屬性。好比實例化的對象以及原型對象
,而不是構造函數。這個屬性指向擁有其屬性的對象的構造函數的原型對象。注意,此處的構造函數
指的是使用new
方式的構造函數。並不由於更改了原型對象上的constructor
屬性而改變。chrome
__proto__
是個啥呢,就是對[[propertyName]]
的實現。也就是說,你能夠在支持該實現的瀏覽器下(FF,chrome,safari),去訪問對象的構造函數的原型對象。好比:segmentfault
var Person=function(name){ this.name=name; }; var p1=new Person(); p1.__proto__===Person.prototype;//true Person.prototype={}; var p2=new Person(); p2.__proto__===Object.prototype;//false
固然,__proto__
只是瀏覽器的私有實現,目前ECMAScript標準實現方法是Object.getPrototypeOf(object)
。瀏覽器
var Person=function(name){ this.name=name; }; var p1=new Person(); Object.getPrototypeOf(p1)===Person.prototype;//true Person.prototype={}; var p2=new Person(); Object.getPrototypeOf(p2)===Object.prototype;//false
另一種判斷實例對象和其原型對象存在指向關係(由實例的[[prototype]]指向其構造函數的原型對象)的方法是:isPrototypeOf
。好比:函數
Person.prototype.isPrototypeOf(p1);//true
因爲原型對象
也是一個對象,因此,它天然而然也擁有[[prototype]]
屬性。性能
Javascript
並無類繼承模型,而是使用原型對象 prototype
進行原型式繼承。
儘管人們常常將此看作是 Javascript
的一個缺點,然而事實上,原型式繼承比傳統的類繼承模型要更增強大。舉個例子,在原型式繼承頂端構建一個類模型很簡單,然而反過來則是個困可貴多的任務。Javascript
是惟一一個被普遍運用的原型式繼承的語言,因此理解兩種繼承方式的差別是須要時間的。ui
第一個主要差別就是 Javascript
使用原型鏈來繼承:this
function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {}
設置 Bar
的 prototype
爲 Foo
的對象實例:
Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World';
確保 Bar
的構造函數爲自己,並新建一個 Bar
對象實例:
Bar.prototype.constructor = Bar; var test = new Bar();
下面咱們來看下整個原型鏈的組成:
test [instance of Bar] Bar.prototype [instance of Foo] { foo: 'Hello World' } Foo.prototype { method: ... } Object.prototype { toString: ... /* etc. */ }
在上面的例子中,對象 test
將會同時繼承 Bar.prototype
和 Foo.prototype
。所以它能夠訪問定義在 Foo
中的函數 method
。固然,它也能夠訪問屬性 value
。須要提到的是,當 new Bar()
時並不會建立一個新的 Foo
實例,而是重用它原型對象自帶的 Foo
實例。一樣,全部的 Bar
實例都共享同一個 value
屬性。咱們舉例說明:
test1 = new Bar(); test2 = new Bar(); Bar.prototype.value = 41; test1.value //41 test2.value//41
當訪問一個對象的屬性時,Javascript
會從對象自己開始往上遍歷整個原型鏈,直到找到對應屬性爲止。若是此時到達了原型鏈的頂部,也就是上例中的 Object.prototype
,仍然未發現須要查找的屬性,那麼 Javascript
就會返回 undefined
值。
儘管原型對象的屬性被 Javascript
用來構建原型鏈,咱們仍然能夠值賦給它。可是原始值複製給 prototype
是無效的,如:
function Foo() {} Foo.prototype = 1; // no effect
這裏講個本篇的題外話,介紹下什麼是原始值:
在 Javascript
中,變量能夠存放兩種類型的值,分別是原始值和引用值。
1.原始值
(primitive value)
:
原始值是固定而簡單的值,是存放在棧stack
中的簡單數據段,也就是說,它們的值直接存儲在變量訪問的位置。
原始類型有如下五種型:Undefined, Null, Boolean, Number, String
。
2.引用值
(reference value)
:
引用值則是比較大的對象,存放在堆heap
中的對象,也就是說,存儲在變量處的值是一個指針pointer
,指向存儲對象的內存處。全部引用類型都集成自Object
。
若是須要查找的屬性位於原型鏈的上端,那麼查找過程對於性能而言無疑會帶來負面影響。當在性能要求必要嚴格的場景中這將是須要重點考慮得因素。此外,試圖查找一個不存在屬性時將會遍歷整個原型鏈。
一樣,當遍歷一個對象的屬性時,全部在原型鏈上的屬性都將被訪問。
理解原型式繼承是寫較爲複雜的 Javascript
代碼的前提,同時要注意代碼中原型鏈的高度。當面臨性能瓶頸時要學會將原型鏈拆分開來。此外,要將原型對象 prototype
和原型 __proto__
區分開來,這裏主要討論原型對象 prototype
就不闡述關於原型 __proto__
的問題了。
圖片來自基友 kzloser
1.總共三類對象(藍色大框)
2.實例對象(經過new XX() 所獲得的實例),跟原型鏈相關的只有 __proto__
屬性,指向其對應的原型對象 *.prototype
。
3.構造函數對象分原生和自定義兩類。跟原型鏈相關的有 __proto__
屬性,除此以外還有 prototype
屬性。它們的 __proto__
屬性都是指向 Function.prototype
這個原型對象的。prototype
也是指向對應的原型對象。
4.原型對象除了同樣擁有 __proto__
外,也擁有獨有的屬性 constructor
。它的__proto__
指向的都是 Object.prototype
,除了 Object.prototype
自己,它本身是指向 null
。而 constructor
屬性指向它們對應的構造函數對象。
5.原型鏈是基於 __proto__
的。實例只能經過其對應原型對象的 constructor
才能訪問到對應的構造函數對象。構造函數只能經過其對應的 prototype
來訪問相應的原型對象。
原型與原型鏈是javascript裏面最最核心的內容,若是不能理解它們之間的存在關係的話,那麼咱們是不能理解這門語言的。
在JS中,主要有兩種建立對象的方法, 分別是對象字面量以及調用構造函數
//對象字面量 var obj1 = {} //調用構造函數 var obj2 = new Object()
其實上述兩種建立對象的方法,本質上是同樣的,都是JS引擎調用對象的構造函數來新建出一個對象。構造函數自己也是一個普通的JS函數
下面咱們來看一個例子
//建立構造函數 function Person(name){ this.name = name } //每一個構造函數JS引擎都會自動添加一個prototype屬性,咱們稱之爲原型,這是一個對象 //每一個由構造函數建立的對象都會共享prototype上面的屬性與方法 console.log(typeof Person.prototype) // 'object' //咱們爲Person.prototype添加sayName方法 Person.prototype.sayName = function(){ console.log(this.name) } //建立實例 var person1 = new Person('Messi') var person2 = new Person('Suarez') person1.sayName() // 'Messi' person2.sayName() // 'Suarez' person1.sayName === person2.sayName //true
咱們藉助上面的例子來理解構造函數-原型-實例,三者之間的關係,主要有幾個基本概念
構造函數默認會有一個protoype
屬性指向它的原型
構造函數的原型會有一個consctructor
的屬性指向構造函數自己, 即
Person.prototype.constructor === Person
每個new
出來的實例都有一個隱式的__proto__
屬性,指向它們的構造函數的原型,即
person1.__proto__ === Person.prototype person1.__proto__.constructor === Person
瞭解了這些基本概念以後,咱們再來看看javascript的一些原生構造函數的關係網,看下列的圖
按照咱們上面的理解, Oject自己是一個構造函數,它也是一個對象,那麼
Object.__proto__ === Function.prototype
爲了方便咱們記住上圖,還有幾個須要咱們知道的特殊概念:
Function
的原型屬性與Function
的原型指向同一個對象. 即
Function.__proto__ == Function.prototype
Object.prototype.__proto__ === null
typeof Function.prototype === 'function'