原型其實就是一個特殊的對象,在聲明函數的時候自動建立的。
<!-- more -->javascript
好比,咱們如今聲明一個構造函數 A ,除了會申請保存函數的內存空間,還會額外申請一個內存空間,用於存儲構造函數 A 的原型對象。全部函數中(Function.prototype.bind 除外)默認都有一個 prototype
的屬性,它保存了函數的原型對象的地址(引用)(也就是它指向了原型對象)。
而在原型對象中默認有一個 constructor
屬性存儲了構造函數的地址(引用)(也就是 constructor
指向了構造函數)。若是不理解上面所說的,那咱們看下面的圖:java
瀏覽器控制檯中:瀏覽器
_ _proto_ _
與 prototype
剛開始接觸原型的時候這兩個東西很容易就搞混了。
先記住如下兩點,就很容易就區分了:函數
prototype
是函數中才有的屬性__proto__
是全部對象都有的屬性
咱們已經知道了函數中的 prototype
屬性指向的是它的原型對象,那麼對象中的 __proto__
表明什麼?post
通常狀況下,對象中的 __proto__
屬性是指向它的構造函數的原型對象的,即和構造函數中的 prototype
屬性所指向的對象是同一個對象。性能
用一段簡單的代碼:學習
function A() {} var a = new A()
上圖看着不夠簡便,咱們簡化一下:this
還有一點,__proto__
不是一個規範屬性,ie(除了 ie10) 不支持。對應的標準屬性是[[Prototype]]
,可是這個屬性咱們無法直接訪問到。開發者儘可能不要用這種方式去訪問,由於操做不慎會改變這個對象的繼承原型鏈。
在使用 Object.create(參數)
方式建立對象時,對象的 __proto__
屬性指向的是傳入的參數。spa
因爲__proto__
是全部對象都具備的屬性,而__proto__
自己指向的 原型(函數.prototype)也是一個對象,它也有__proto__
屬性。因此這樣會造成由__proto__
將 對象和 原型連起來的鏈條。這就是 原型鏈。原型鏈的頂端是Object.prototype
(Object 是全部對象的祖宗) ,Object.prototype.__proto__
的值爲null
。
仍是看以前的代碼:.net
function A() {} var a = new A()
它的原型鏈以下:
構造函數 A 其實也是一個對象。全部函數都是由 Function
函數構造的。(聲明函數 function A() {}
等價於 var A = new Function()
) 。因此全部函數的 __proto__
指向的都是 Function.prototype
。更新上圖:
Function
也是一個函數,它的 __proto__
指向的也是 Functon.prototype
即 Funtion.__proto__ === Function.prototype
。繼更新上圖:
Object
一樣是一個函數,因此 Object.__proto__ === Function.prototype
到了這裏,咱們應該能夠看懂下面這張圖了:
當 JS 引擎查找對象屬性時,先查找對象自己是否存在該屬性,若是不存在,會在對象的__proto__
裏找,還找不到就會沿着原型鏈一直找到 原型鏈頂端(Object.prototype) 直到找到屬性爲止,最後在原型鏈頂端都沒找到就返回undefined
。
因爲上面的機制,原型的做用就很明顯了——共享屬性,節省內存空間。
function Animal() { this.name = '動物' this.eat = function() { console.log('在吃···') } } var a1 = new Animal() var a2 = new Animal() console.log(a1.eat === a2.eat) // false // 每一個對象的 eat 方法不是同一個,但方法類容同樣,浪費內存
使用 原型解決:
function Animal(name) { this.name = '動物' } Animal.prototype.eat = function() { console.log('吃') } var a1 = new Animal() var a2 = new Animal() console.log(a1.eat === a2.eat) //true // a1.eat 和 a2.eat 都同一個方法(Animal.prototype.eat)
原型很是適合封裝共享的方法。可是上面的代碼把構造函數和原型分開寫了。封裝不到位。使用動態類型模式解決。
function Animal() { this.name = '動物' /* 判斷 this.eat 是否是 函數類型, 若是不是,則表示是第一次建立對象或者調用 Animal 函數, 會將 eat 添加到原型中去。 若是是,則表示原型中存在了 eat 方法,不須要再添加。 */ if(typeof this.eat !== 'function') { Animal.prototype.eat = function() { console.log('吃') } } } var a = new Animal() a.eat()
原型基於以前的共享屬性和方法,是實現 JS 中繼承的基礎。
hasOwnProperty()
經過以前的學習,咱們知道了去訪問一個對象的屬性時,會在原型鏈上查找。因此咱們並不知道這個屬性來自哪裏。
hasOwnProperty()
方法返回一個布爾值,能夠判斷一個屬性是否來自對象自己。
function Animal() {} Animal.prototype.name = '動物' var a = new Animal() a.age = 3 console.log(a.hasOwnProperty('name')) // false console.log(a.hasOwnProperty('age') // true
in
操做符in
操做符用返回一個布爾值,用來判斷一個屬性可否在對象上找到。在對象的原型鏈上找到也返回true
。
function Animal() {} Animal.prototype.name = '動物' var a = new Animal() a.age = 3 console.log('name' in a) // true console.log('age' in a) // true console.log('sex' in a) // false
prototype
只存在於函數中__proto__
屬性,它指向對象的構造函數的原型__proto__
屬性,__proto__
將對象和原型鏈接起來,造成原型鏈Object.prototype
是原型鏈的頂端參考資料:
http://www.javashuo.com/article/p-fbwdxkws-cy.html
http://www.javashuo.com/article/p-yedyoeua-k.html
https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bed40d951882545f73004f6