JavaScript原型對象

原型
內容引導:
1使用 prototype 原型對象解決構造函數的問題
2分析 構造函數、prototype 原型對象、實例對象 三者之間的關係
3屬性成員搜索原則:原型鏈
4實例對象讀寫原型對象中的成員
5原型對象的簡寫形式
6原生對象的原型
  + Object
  + Array
  + String
  + ...
7原型對象的問題
8構造的函數和原型對象使用建議
1 更好的解決方案: `prototype`
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` 對象,因此就提高了運行效率。
2 構造函數、實例、原型三者之間的關係


任何函數都具有一個 `prototype` 屬性,該屬性是一個對象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
  console.log('hi!')
}
構造函數的 `prototype` 對象默認都有一個 `constructor` 屬性,指向 `prototype` 對象所在函數。
console.log(F.constructor === F) // => true
通過構造函數得到的實例對象內部會包含一個指向構造函數的 `prototype` 對象的指針 `__proto__`。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
<p class="tip">
  `__proto__` 是非標準屬性。
</p>
實例對象可以直接訪問原型對象成員。
instance.sayHi() // => hi!
總結:
1 任何函數都具有一個 `prototype` 屬性,該屬性是一個對象
2 構造函數的 `prototype` 對象默認都有一個 `constructor` 屬性,指向 `prototype` 對象所在函數
3 通過構造函數得到的實例對象內部會包含一個指向構造函數的 `prototype` 對象的指針 `__proto__`
4 所有實例都直接或間接繼承了原型對象的成員
3 屬性成員的搜索原則:原型鏈
瞭解了 **構造函數-實例-原型對象** 三者之間的關係後,接下來我們來解釋一下爲什麼實例對象可以訪問原型對象中的成員。
每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性
1 搜索首先從對象實例本身開始
2 如果在實例中找到了具有給定名字的屬性,則返回該屬性的值
3 如果沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具有給定名字的屬性
4 如果在原型對象中找到了這個屬性,則返回該屬性的值
也就是說,在我們調用 `person1.sayName()` 的時候,會先後執行兩次搜索:
5 首先,解析器會問:“實例 person1 有 sayName 屬性嗎?”答:“沒有。
6 ”然後,它繼續搜索,再問:“ person1 的原型有 sayName 屬性嗎?”答:“有。
7 ”於是,它就讀取那個保存在原型對象中的函數。
8 當我們調用 person2.sayName() 時,將會重現相同的搜索過程,得到相同的結果。
而這正是多個對象實例共享原型所保存的屬性和方法的基本原理。
總結:
1 先在自己身上找,找到即返回
2 自己身上找不到,則沿着原型鏈向上查找,找到即返回
3 如果一直到原型鏈的末端還沒有找到,則返回 `undefined`
4 實例對象讀寫原型對象成員
讀取:
1 先在自己身上找,找到即返回
2 自己身上找不到,則沿着原型鏈向上查找,找到即返回
3 如果一直到原型鏈的末端還沒有找到,則返回 `undefined`
值類型成員寫入(`實例對象.值類型成員 = xx`):
4 當實例期望重寫原型對象中的某個普通數據成員時實際上會把該成員添加到自己身上
5 也就是說該行爲實際上會屏蔽掉對原型對象成員的訪問
5引用類型成員寫入(`實例對象.引用類型成員 = xx`):
- 同上
6複雜類型修改(`實例對象.成員.xx = xx`):
1 同樣會先在自己身上找該成員,如果自己身上找到則直接修改
2 如果自己身上找不到,則沿着原型鏈繼續查找,如果找到則修改
3 如果一直到原型鏈的末端還沒有找到該成員,則報錯(`實例對象.undefined.xx = xx`)
7 更簡單的原型語法
我們注意到,前面例子中每添加一個屬性和方法就要敲一遍 `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 + '歲了')
  }
}
8 原生對象的原型
<p class="tip">
  所有函數都有 prototype 屬性對象。
</p>
Object.prototype
Function.prototype
Array.prototype
String.prototype
Number.prototype
Date.prototype
- ...
練習:爲數組對象和字符串對象擴展原型方法。
9 原型對象的問題
- 共享數組
- 共享對象
如果真的希望可以被實例對象之間共享和修改這些共享數據那就不是問題。但是如果不希望實例之間共享和修改這些共享數據則就是問題。
一個更好的建議是,最好不要讓實例之間互相共享這些數組或者對象成員,一旦修改的話會導致數據的走向很不明確而且難以維護。
10 原型對象使用建議
1私有成員(一般就是非函數成員)放到構造函數中
2共享成員(一般就是函數)放到原型對象中
3如果重置了 `prototype` 記得修正 `constructor` 的指向

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章