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