---
# System prepended metadata

title: 物件與繼承

---

# 物件與繼承

物件導向程式設計(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)藉由繼承現有建構器來建立新的建構器