物件與繼承 -2
物件導向程式設計(OOP)分為層次有:
Layer 1: 單一物件的物件導向(OOP)
Layer 2: 物件的原型鍊(Prototype chains)
Layer 3: 建構器(Constructor)作為實體的工廠
Layer 4: 衍生子類別(subclassing)藉由繼承現有建構器來建立新的建構器
Layer 1: 單一物件的物件導向(OOP)
JS中所有物件都是mappings特性or屬性(property):
- key : value
- 字串 : 任何js的值、函式(methods方法)
特性又分成三種
- 具名的資料特性 properties(named data properties)
- 最常見 key: value的mappings關係
- 具名的存取器特性 accessors (named accessor properties)
- 調用時像是在讀取或寫入特性
- eg. setter, getter
- 內部特性 (internal properties)
- js無法直接取用,可用間接方式存取
1 | const obj = {name: '123'}; |
物件字面值, 點號運算子
1 | const student = { |
特殊key值
- 變數不能用的保留字 var, function…etc
- 數字為key字時為字串,點號運算子只能存取key值為識別字
- ‘任意字串’
1
2
3
4
5
6
7
8
9const obj = {
function: 1,
0.2 : 'hello',
'how are you': 'fine',
};
obj['0.2'];
obj['how are you'];
方框運算子 [experssion]
1 | const obj = { |
轉為物件 Object()
參數值 | 結果 |
---|---|
不帶參數 | {} |
undefined | {} |
null | {} |
Boolean值bool | new Boolean(bool) |
數值 num | new Number(num) |
字串值str | new String(str) |
物件 | obj(不變) |
this 函式中隱藏的參數
寬鬆模式sloppy mode: this指向為全域物件window
嚴格模式strict mode: undefined
函式中 this 是函式被調用時的那個物件(receiver)
call(), apply(), bind()
常見陷阱 nested function
1 | const obj = { |
Layer 2: 物件的原型鍊(Prototype chains)
JavaScript 是一個以原型為基礎 (Prototype-based)、多範型的、動態語言。支援物件導向(Object-oriented programming, OOP)、指令式以及宣告式 (如函數式程式設計)。
物件導向程式設計 OOP
是將 軟體 想像成由一群物件交互合作所組成,而非以往以函數 (Function) 或簡單的指令集交互合作所組成。在物件導向的架構中,每個物件都具有接收訊息,處理資料以及發送訊息給其他物件的能力。每個物件都可視為獨一無二的個體,他們扮演不同的角色並有不同的能力及責任。物件導向程式設計強調模組化,使得程式碼變的較容易開發和理解。
類別 (Class) 和 物件 (Object)
類別 (Class)
類別是用來定義物件的屬性 (properties) 和方法 (methods)的藍圖。
物件 (Object)
物件為一個類別的實體 (Instance),包含屬性 (properties)與方法 (methods)的資料結構。
上述有提到Javascript是以原型為基礎 (Prototype-based)的語言,不用先設計藍圖(類別)就可以建立物件,是無類別的 (Classless)。
那JS沒有類別要如何用原型基礎來實現物件導向的概念呢?JS的物件透過原型(Prototype)相互繼承各自功能,形成原型鍊(Prototype Chain)。建立物件時,會用一個函式function也就是建構器 (Constructor)來定義物件的藍圖,類似類別的概念。
ES6有個class的新語法,只是個語法糖,讓建構器 (Constructor)的寫法更簡潔易懂,更近似於其他物件導向語言C++、JAVA定義類別的方式,但JavaScript仍然是基於原型的語言。
Object.create() 建立繼承給定原形的新物件
用(物件a)作為原型來建立新的物件b
新物件b繼承了物件a的屬性與方法
1 | const a = {name: 'A'}; |
Object.setPrototypeOf()
可帶入兩個參數 第一個為接受繼承的物件 第二個為原型
以下例子結果與create一樣
1 | const a = {name: 'A'}; |
getPrototypeOf() 讀取一個原型
1 | Object.getPrototypeOf(b)=== a //true |
isPrototypeOf() 是否為另一個物件原型
1 | a.isPrototypeOf(b) //true |
__proto__
特殊特性
- dunder proto (doubble underscore proto)
- 非標準ECMAScript5規費
存取器 Accessors
取值器(getter) & 設值器(setter)
用 物件字面值 定義存取器
1 | let a = { |
用 特性描述器 定義存取器
1 | let a = Object.create( |
特性屬性(property attributes)
key值 | 預設值 | |
---|---|---|
一般特性有以下屬性 | ||
value | undefined | 特性的值 |
writable | false | 是否可以被更改 |
存取器有以下屬性 | ||
get | undefined | 取值器 |
set | undefined | 存值器 |
所有特性都有以下屬性 | ||
enumerable | false | 設定特性是否不可列舉 |
configurable | false | 定義特性是否可以被刪除、或修改特性內的 writable、enumerable 及 configurable 設定。例外: length |
特性描述器 or 屬性描述器(property descriptor)
定義屬性
Object.defineProperty(obj, propKey, propDesc)
Object.defineProperties(obj, propDesc)
取得屬性
Object.getOwnPropertyDescriptor(obj, propKey)
Object.getOwnPropertyDescriptors(obj)
1 | const obj = {}; |
特性的迭代與偵測
列出自有特性的key值
- Object.getOwnPropertyNames(obj)
- Object.keys(obj)
列出所有特性的key值
- for-in 迴圈
hasOwnProperty() 此物件本身是否有這個屬性 (原型的屬性不算)
1 | var a = {name: 'Zoe'}; |
key值 in obj 此物件是否有這個屬性 (包含原型的屬性)
1 | var a = {name: 'Zoe'}; |
計算物件自有特性的數量Object.keys(obj).length
1 | Object.keys(b).length //1 |
可列舉不可列舉
||自有可列舉特性|原型可列舉特性|不可列舉特性 |
| ——– | ——– | ——– |——– |——– |
for…in| O| O| X|
Object.keys| O| X| X|
Object.getOwnPropertyNames|O|X|O|
保護物件: (弱 -> 強)
1. 防止擴充 Object.preventExtensions(obj)
檢查是否可擴充 Object.isExtensible(obj) true/false
1 | const obj = {}; |
2. 密封 Object.seal(obj)
防止擴充,把 configurable 設成-> false (唯讀狀態)
只可變更已有的key值的value值
檢查是否密封 Object.isSeal(obj) true/false
1 | const obj = {foo: 'a'}; |
3. 凍結 Object.freeze(obj)
以上效果皆有並且無法寫入已有的key值的value值
檢查是否凍結 Object.isFrozen(obj) true/false
陷阱
保護只是淺層(shallow)
Object.prototype 原型也是可變的
–