淺談javascript的原型及原型鏈

 

淺談javascript的原型及原型鏈

 

這裏,咱們列出原型的幾個概念,以下:javascript

  • prototype屬性
  • [[prototype]]
  • __proto__

prototype屬性

只要建立了一個函數,就會爲該函數建立一個prototype屬性,指向該函數的原型對象。實例對象是不會擁有該屬性的。
默認狀況下,該原型對象也會得到一個constructor屬性,該屬性包含一個指針,指向prototype屬性所在的函數。css

Person.prototype.constructor===Person 

[[prototype]]和__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的一些原生構造函數的關係網,看下列的圖


引自stackoverflow

按照咱們上面的理解, Oject自己是一個構造函數,它也是一個對象,那麼

Object.__proto__ === Function.prototype

爲了方便咱們記住上圖,還有幾個須要咱們知道的特殊概念:

  • Function的原型屬性與Function的原型指向同一個對象. 即

    Function.__proto__ == Function.prototype
  • Object.prototype.__proto__ === null

  • typeof Function.prototype === 'function'

相關文章
相關標籤/搜索