JavaScript 中的原型原來是這樣的

什麼是原型

原型其實就是一個特殊的對象,在聲明函數的時候自動建立的。

<!-- more -->javascript

好比,咱們如今聲明一個構造函數 A ,除了會申請保存函數的內存空間,還會額外申請一個內存空間,用於存儲構造函數 A 的原型對象。全部函數中(Function.prototype.bind 除外)默認都有一個 prototype 的屬性,它保存了函數的原型對象的地址(引用)(也就是它指向了原型對象)。
而在原型對象中默認有一個 constructor 屬性存儲了構造函數的地址(引用)(也就是 constructor 指向了構造函數)。若是不理解上面所說的,那咱們看下面的圖:java

原型

瀏覽器控制檯中:瀏覽器

原型

_ _proto_ _prototype

剛開始接觸原型的時候這兩個東西很容易就搞混了。

先記住如下兩點,就很容易就區分了:函數

  • prototype 是函數中才有的屬性
  • __proto__ 是全部對象都有的屬性

 _ _proto_ _ 與 prototye

咱們已經知道了函數中的 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.prototypeFuntion.__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

相關文章
相關標籤/搜索