JavaScript 規定,每個構造函數都有一個 prototype
屬性,指向另外一個對象。這個對象的全部屬性和方法,都會被構造函數的所擁有。數組
這也就意味着,咱們能夠把全部對象實例須要共享的屬性和方法直接定義在 prototype
對象上。函數
function Person (name, age) { this.name = name this.age = age } console.log(Person.prototype) Person.prototype.type = 'human' Person.prototype.sayName = function () { console.log(this.name) } var p1 = new Person(...) var p2 = new Person(...) console.log(p1.sayName === p2.sayName) // => true
這時全部實例的 type
屬性和 sayName()
方法,其實都是同一個內存地址,指向 prototype
對象,所以就提升了運行效率。this
構造函數、實例、原型三者之間的關係
任何函數都具備一個 prototype
屬性,該屬性是一個對象。spa
function F () {} console.log(F.prototype) // => object F.prototype.sayHi = function () { console.log('hi!') }
構造函數的 prototype
對象默認都有一個 constructor
屬性,指向 prototype
對象所在函數。prototype
console.log(F.prototype.constructor === F) // => true
經過構造函數獲得的實例對象內部會包含一個指向構造函數的 prototype
對象的指針 __proto__
。指針
var instance = new F() console.log(instance.__proto__ === F.prototype) // => true
<p class="tip"> __proto__
是非標準屬性。</p>code
實例對象能夠直接訪問原型對象成員。對象
instance.sayHi() // => hi!
總結:blog
-
任何函數都具備一個
prototype
屬性,該屬性是一個對象繼承 -
構造函數的
prototype
對象默認都有一個constructor
屬性,指向prototype
對象所在函數 -
經過構造函數獲得的實例對象內部會包含一個指向構造函數的
prototype
對象的指針__proto__
-
全部實例都直接或間接繼承了原型對象的成員
屬性成員的搜索原則:原型鏈
瞭解了 構造函數-實例-原型對象 三者之間的關係後,接下來咱們來解釋一下爲何實例對象能夠訪問原型對象中的成員。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性
-
搜索首先從對象實例自己開始
-
若是在實例中找到了具備給定名字的屬性,則返回該屬性的值
-
若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性
-
若是在原型對象中找到了這個屬性,則返回該屬性的值
也就是說,在咱們調用 person1.sayName()
的時候,會前後執行兩次搜索:
-
首先,解析器會問:「實例 person1 有 sayName 屬性嗎?」答:「沒有。
-
」而後,它繼續搜索,再問:「 person1 的原型有 sayName 屬性嗎?」答:「有。
-
」因而,它就讀取那個保存在原型對象中的函數。
-
當咱們調用 person2.sayName() 時,將會重現相同的搜索過程,獲得相同的結果。
而這正是多個對象實例共享原型所保存的屬性和方法的基本原理。
總結:
-
先在本身身上找,找到即返回
-
本身身上找不到,則沿着原型鏈向上查找,找到即返回
-
若是一直到原型鏈的末端尚未找到,則返回
undefined
實例對象讀寫原型對象成員
讀取:
-
先在本身身上找,找到即返回
-
本身身上找不到,則沿着原型鏈向上查找,找到即返回
-
若是一直到原型鏈的末端尚未找到,則返回
undefined
值類型成員寫入(實例對象.值類型成員 = xx
):
-
當實例指望重寫原型對象中的某個普通數據成員時實際上會把該成員添加到本身身上
-
也就是說該行爲實際上會屏蔽掉對原型對象成員的訪問
引用類型成員寫入(實例對象.引用類型成員 = xx
):
-
同上
複雜類型修改(實例對象.成員.xx = xx
):
-
一樣會先在本身身上找該成員,若是本身身上找到則直接修改
-
若是本身身上找不到,則沿着原型鏈繼續查找,若是找到則修改
-
若是一直到原型鏈的末端尚未找到該成員,則報錯(
實例對象.undefined.xx = xx
)
圖例:
更簡單的原型語法
咱們注意到,前面例子中每添加一個屬性和方法就要敲一遍 Person.prototype
。爲減小沒必要要的輸入,更常見的作法是用一個包含全部屬性和方法的對象字面量來重寫整個原型對象:
function Person (name, age) { this.name = name this.age = age } Person.prototype = { type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '歲了') } }
在該示例中,咱們將 Person.prototype
重置到了一個新的對象。這樣作的好處就是爲 Person.prototype
添加成員簡單了,可是也會帶來一個問題,那就是原型對象丟失了 constructor
成員。
因此,咱們爲了保持 constructor
的指向正確,建議的寫法是:
function Person (name, age) { this.name = name this.age = age } Person.prototype = { constructor: Person, // => 手動將 constructor 指向正確的構造函數 type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '歲了') } }
原生對象的原型
<p class="tip"> 全部函數都有 prototype 屬性對象。</p>
-
Object.prototype
-
Function.prototype
-
Array.prototype
-
String.prototype
-
Number.prototype
-
Date.prototype
-
...
練習:爲數組對象和字符串對象擴展原型方法。
原型對象使用建議
-
私有成員(通常就是非函數成員)放到構造函數中
-
共享成員(通常就是函數)放到原型對象中
-
若是重置了
prototype
記得修正constructor
的指向