顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題
 [JS] 克服JS的奇怪部分 ch5 JS的物件導向與原型繼承

時間 2016-11-26 Sat. 12:55:29


Udemy課程: JavaScript全攻略:克服JS的奇怪部分
https://www.udemy.com/javascriptjs/learn/v4/overview
上完第五章的心得筆記

章節5 JavaScript 的物件導向與原型繼承

53. 觀念小叮嚀:古典和原型繼承(classical vs prototypal inheritance)

繼承(Inheritance): 一個物件取用另一個物件的屬性或方法

古典繼承: 在一般的程式語言像是 C#、JAVA 使用的物件繼承
有許多複雜的語法像是 private, protected, friend, interface 要搞懂
常會產生複雜的繼承樹狀結構,讓底層物件包含了一堆東西,使事情變的複雜

原型繼承: JS物件繼承方法,比較簡單易懂


54. 瞭解原型(Prototype)

在JS中所有的物件,包括函數,都會有個 proto 屬性
這個屬性指向了一個物件 proto

例如有個物件叫 obj 他有個屬性 prop1
obj的原型物件 proto,有個屬性 prop2

物件obj → prop1
        ↘
           proto → prop2

當使用 obj.prop1 時會取用 obj 的屬性 prop1 沒問題

而使用 obj.prop2 時,因為 obj 沒有 prop2 屬性
會到 obj 的原型物件 proto 去找,有的話就取用 proto 的屬性 prop2

只要用 obj.prop2 就可以取用 obj.proto.prop2
看起來好像 obj 有個 prop2 的屬性,但其實那是 proto 的屬性

同樣的,物件 proto 也有自己的原型物件,
若找不到某個屬性時就會再往上層的原型物件找,
這個尋找的路徑就叫原型鏈(Prototype Chain)


不同的物件可以分享一樣的原型

例如 obj2 的原型如果跟 obj 一樣時

物件obj → prop1
        ↘
物件obj2→ proto → prop2

此時使用 obj.prop2 和 obj2.prop2
就會取得同一個值,使用相同的記憶體位置


來看個使用原型的例子
var person = {
	
firstname: 'Default',
	
lastname: 'Default',
	
getFullName: function(){
	
	
return this.firstname + ' ' + this.lastname;
	
}
}

var john = {
	
firstname: 'John',
	
lastname: 'Doe'
}

// 這邊為了解說方便複寫了原型物件,實際上不要這麼做!
john.__proto__ = person;
console.log(john.getFullName()); // 顯示 John Doe
console.log(john.firstname); // 顯示 John

var jane = {
	
firstname: 'Jane'
}

jane.__proto__ = person;
console.log(jane.getFullName()); // 顯示 Jane Default
可以看到雖然 john 物件的成員函數沒有 getFullName
但他的原型物件有,所以一樣可以使用 john.getFullName()
而且此時 getFullName 中的 this 是指向呼叫的物件 john
不會指向原型物件 person

而另一個物件 jane 因為缺少了 lastname
在使用 getFullName 時,因為找不到 jane.lastname
會到原型物件 person 去找,所以顯示為 'Default'


55. 所有的東西都是物件(或純值)

JS中所有的東西都是物件,所以都有各自的原型
只有一種物件沒有原型,就是基本物件

在chrome的開發者工具上建立一個空物件,看看他的原型
[圖]

物件的原型就是一個基本物件
各種物件的原型鏈最後都會指向一個基本物件

基本物件的成員有這些
[圖]

所以所有的物件都有這些成員可以呼叫

建立一個空的函數,看看函數原型
[圖]

函數的原型物件多了這幾個成員: apply, arguments, bind, call, ...

前面提過每個函數都有這些隱藏成員可呼叫
其實這些是函數原型物件的成員

看一下函數原型的原型
[圖]

就是一個基本物件


56. Reflection 與 Extend

Reflection: 物件可以列出自己有哪些成員

利用Reflection可以實現一個有用的模式,叫做 extend
用來將一個物件的所有成員複製到另一個物件裡

延用之前的例子
var person = {
	
firstname: 'Default',
	
lastname: 'Default',
	
getFullName: function(){
	
	
return this.firstname + ' ' + this.lastname;
	
}
}

var john = {
	
firstname: 'John',
	
lastname: 'Doe'
}

// 這邊為了解說方便複寫了原型物件,實際上不要這麼做!
john.__proto__ = person;

// 使用 for in 可將物件的所有成員列出來
for(var prop in john){
	
console.log(prop + ': ' + john[prop]);
}
// 執行後顯示:
// firstname: John
// lastname: Doe
// getFullName: function (){
//
	
	
return this.firstname + ' ' + this.lastname;

//
	
}


// 加上 hasOwnProperty 判斷式,避免將原型的成員也列出來
for(var prop in john){
	
if(john.hasOwnProperty(prop)){
	
	
console.log(prop + ': ' + john[prop]);
	
}
}
// 執行後顯示:
// firstname: John
// lastname: Doe
使用迴圈 for(var prop in john){ }
會跑一遍物件 john 所有的成員(含原型的成員)
將每次迴圈時,成員的名稱存成變數 prop,
使用 john[prop] 就可以取得該成員的值

使用基本物件提供的 hasOwnProperty(),
可以檢查這個成員是不是自己的


例用物件可以列出自己所有成員的方法,
就可以實做 extend 功能,用來合併多個物件的所有成員

extend 在各種資源庫都有實做,所以這邊我們不自己寫
而是使用 underscore.js 提供的

例如有三個物件,各自有不同的成員
我們想把第二和第三個物件的所有成員都複製到第一個物件裡
var john = {
	
firstname: 'John',
	
lastname: 'Doe'
}

var jane = {
	
address: '111 Main St.',
	
getFormalFullName: function(){
	
	
return this.lastname + ', ' + this.firstname;
	
}
}

var jim = {
	
getFirstName: function(){
	
	
return firstname;
	
}
}

// 要先載入 underscore.js
_.extend(john, jane, jim);

// 使用 extend 後,物件 john 就雍有另外兩個物件的所有成員了
console.log(john.address); // 顯示 111 Main St.
console.log(john.getFormalFullName()); // 顯示 Doe, John
console.log(john.getFirstName()); // 顯示 John

可以到 underscore.js 的網站看看 extend 的實作方式
http://underscorejs.org/docs/underscore.html#section-103

有 extend 可以用的話,就不需要每次都使用原型鏈了


--
※ 作者: Knuckles 時間: 2016-11-26 12:55:29
※ 編輯: Knuckles 時間: 2016-11-26 12:58:56
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 872 
分享網址:
r)回覆 e)編輯 d)刪除 M)不收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇