# 物件與繼承
物件導向程式設計(OOP)分為層次有:
[Layer 1: 單一物件的物件導向(OOP)](#Layer-1:-單一物件的物件導向OOP)
[Layer 2: 物件的原型鍊(Prototype chains)](#Layer-2:-物件的原型鍊Prototype-chains)
[Layer 3: 建構器(Constructor)作為實體的工廠](#Layer-3-建構器Constructor作為實體的工廠)
[Layer 4: 衍生子類別(subclassing)藉由繼承現有建構器來建立新的建構器](#Layer-4-衍生子類別subclassing藉由繼承現有建構器來建立新的建構器)
## Layer 1: 單一物件的物件導向(OOP)
JS中所有物件都是mappings特性or屬性(property):
+ key : value
+ 字串 : 任何js的值、函式(methods方法)
特性又分成三種
1. 具名的資料特性 properties(named data properties)
- 最常見 key: value的mappings關係
2. 具名的存取器特性 accessors (named accessor properties)
- 調用時像是在讀取或寫入特性
- eg. setter, getter
3. 內部特性 (internal properties)
- js無法直接取用,可用間接方式存取
```javascript
const obj = {name: '123'};
console.log(Object.getPrototypeOf(obj));
```
## 物件字面值, 點號運算子
```javascript
const student = {
name: 'Allen',
describe: function(){
return `his name is ${this.name}`
},
};
//讀取
student.name; //Allen
//呼叫方法
student.describe(); //'his name is Allen'
//設定
student.name = 'John';
//刪除
//key, value皆會被刪除 不能刪除繼承而來的特性
delete student.name; //true
student.name // undefined
//帶入特性描述器
Object.defineProperty(student, 'name', {
value: 'Tom',
configurable: false
});
delete student.name; //false
```
## 特殊key值
+ 變數不能用的保留字 var, function...etc
+ 數字為key字時為字串,點號運算子只能存取key值為識別字
+ '任意字串'
```javascript
const obj = {
function: 1,
0.2 : 'hello',
'how are you': 'fine',
};
obj['0.2'];
obj['how are you'];
```
## 方框運算子 `[experssion]`
```javascript
const obj = {
0.2 : 'hello',
describe: function(){
return true;
},
};
'
//讀取
obj[0.1+0.1]; //數字強制轉字串 //'hello'
//呼叫方法
obj['describe'](); //'his name is Allen'
//設定
obj['color'] = 'red';
//刪除
delete obj['color']; // true
```
## 轉為物件 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
```javascript
const obj = {
firstName: 'Jane',
friends : ['Allen', 'John'],
loop: function(){
'use strict';
this.friends.forEach(
function(friend){
console.log(`${this.firstName}的朋友是${friend}`);
});
},
};
obj.loop();
//use strict this會是 undefined
// 寬鬆模式 this 指向全域window.firstName undefined
解法1. that = this
const obj = {
firstName: 'Jane',
friends : ['Allen', 'John'],
loop: function(){
'use strict';
let that = this;
this.friends.forEach(
function(friend){
console.log(`${that.firstName}的朋友是${friend}`);
});
},
};
解法2. bind()
const obj = {
firstName: 'Jane',
friends : ['Allen', 'John'],
loop: function(){
'use strict';
this.friends.forEach(
function(friend){
console.log(`${this.firstName}的朋友是${friend}`);
}.bind(this));
},
};
解法3. 指定callback的this值
const obj = {
firstName: 'Jane',
friends : ['Allen', 'John'],
loop: function(){
'use strict';
this.friends.forEach(
function(friend){
console.log(`${this.firstName}的朋友是${friend}`);
},this);
},
};
解法4. 箭頭函式
const obj = {
firstName: 'Jane',
friends : ['Allen', 'John'],
loop: function(){
'use strict';
this.friends.forEach((friend)=>{
console.log(`${this.firstName}的朋友是${friend}`);
});
},
};
'
```
## 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的屬性與方法
```javascript=
const a = {name: 'A'};
// a ---> Object.prototype ---> null
const b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.name) // A
//第二個引數可帶入描述器
const b = Object.create(a, {
realName: {value: 'B', writable: true}
});
console.log(b.name, b.realName); //A, B
```
### Object.setPrototypeOf()
可帶入兩個參數 第一個為接受繼承的物件 第二個為原型
以下例子結果與create一樣
```javascript=
const a = {name: 'A'};
const b = {};
Object.setPrototypeOf(b, a);
console.log(b.name) // A
```
### getPrototypeOf() 讀取一個原型
```javascript
Object.getPrototypeOf(b)=== a //true
```
### isPrototypeOf() 是否為另一個物件原型
```javascript
a.isPrototypeOf(b) //true
```
### `__proto__` 特殊特性
+ dunder proto (doubble underscore proto)
+ 非標準ECMAScript5規費
## 存取器 Accessors
### 取值器(getter) & 設值器(setter)
#### 用 物件字面值 定義存取器
```javascript=
let a = {
ary: [10, 20, 30],
get addData(){
return this.ary;
},
set addData(a){
this.ary.push(a);
},
};
a.addData = 40;
console.log(a); // [10, 20, 30, 40]
a.addData = 50;
console.log(a); // [10, 20, 30, 40, 50]
```
#### 用 特性描述器 定義存取器
```javascript=
let a = Object.create(
Object.prototype, {
ary: {
value: [10, 20, 30]
},
addData:{
get: function(){
return this.ary;
},
set: function(a){
this.ary.push(a);
},
}
}
);
a.addData = 40;
console.log(a); // [10, 20, 30, 40]
a.addData = 50;
console.log(a); // [10, 20, 30, 40, 50]
```
### 特性屬性(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)
```javascript=
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 42,
writable: false,
configurable: true
});
Object.getOwnPropertyDescriptor(obj, 'foo');
//{
// value: 42,
// writable: false,
// enumerable: false,
// configurable: true
// }
//指定
obj.foo = 'b';
console.log(obj.foo); //42
Object.getOwnPropertyDescriptor(obj, 'bar');
//undefined
Object.defineProperties(obj, {
foo: {
value: 10,
enumerable : true,
},
bar: {
value: 20,
writable: true,
configurable: true,
}
});
Object.getOwnPropertyDescriptors(obj);
// {
// "foo": {
// "value": 10,
// "writable": true,
// "enumerable": true,
// "configurable": true
// },
// "bar": {
// "value": 20,
// "writable": true,
// "enumerable": false,
// "configurable": true
// }
// }
```
## 特性的迭代與偵測
### 列出自有特性的key值
+ Object.getOwnPropertyNames(obj)
+ Object.keys(obj)
### 列出所有特性的key值
+ for-in 迴圈
### hasOwnProperty() 此物件本身是否有這個屬性 (原型的屬性不算)
```javascript
var a = {name: 'Zoe'};
var b = {sex: 'female'};
Object.setPrototypeOf(b, a);
console.log(b.hasOwnProperty('sex')); //true
console.log(b.hasOwnProperty('name')); //false
console.log(b.hasOwnProperty.call(a, 'name'));
//true
```
### key值 in obj 此物件是否有這個屬性 (包含原型的屬性)
```javascript
var a = {name: 'Zoe'};
var b = {sex: 'female'};
Object.setPrototypeOf(b, a);
console.log('name' in b); //true
console.log('sex' in b); //true
```
### 計算物件自有特性的數量Object.keys(obj).length
```javascript
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
```javascript
const obj = {};
Object.preventExtensions(obj);
obj.bar='b';
obj.bar //undefined
Object.defineProperty(obj, 'foo', {
value: 10
});
// Uncaught TypeError: Cannot define property foo,
// object is not extensible
```
#### 2. 密封 Object.seal(obj)
防止擴充,把 configurable 設成-> false (唯讀狀態)
只可變更已有的key值的value值
#### 檢查是否密封 Object.isSeal(obj) true/false
```javascript
const obj = {foo: 'a'};
Object.seal(obj);
obj.bar='b';
obj.bar //undefined
Object.defineProperty(obj, 'foo', {
value: 10
});
// 只可變更已有的key值的value值
```
#### 3. 凍結 Object.freeze(obj)
以上效果皆有並且無法寫入已有的key值的value值
#### 檢查是否凍結 Object.isFrozen(obj) true/false
## 陷阱
保護只是淺層(shallow)
Object.prototype 原型也是可變的
## Layer 3: 建構器(Constructor)作為實體的工廠
## Layer 4: 衍生子類別(subclassing)藉由繼承現有建構器來建立新的建構器