js 紀錄9 new 關鍵字發生哪些事? 新增 / 修改屬性 屬性描述器 (Property descriptor) constructor、prototype、proto和原型鏈 原型繼承(Prototype-based inheritance) 類別的繼承(Classical inheritance) – class 語法糖前身 類別宣告(class declaration)

JavaScript Object

  • 把情境(問題)描述為物件
    • 人 & 香蕉
  • 描述物件的屬性 & 方法
    • 人 - 屬性
      • 名子, 年齡, 有的物品
    • 人 - 方法
      • 打招呼, 走路, 接收物品
    • 香蕉 - 屬性
      • 名字, 價格, 味道
    • 香蕉 - 方法
      • 剝⽪, 攻擊人
  • 操作讓物件彼此互動
    • A 跟 B 是⼈,A 有⼀根香蕉
    • A.打招呼( )
    • A.給予( B , 香蕉)
      • B -> 香蕉.剝⽪( )
      • B.吃( 香蕉 )
  • 類別」可想像成是建構某特定物體的藍圖或模具,而「實體」就是按照這藍圖或模具製造出來的成品,並使用「建構子」來建立和初始化實體
  • 繼承」定義一個類別,其特性繼承於另外一個類別,稱它們為「父類別」與「子類別」,而子類別「繼承」了父類別的特性
  • 多型」是指子類別除了擁有自己的方法外,這個方法還能覆寫來特化父類別的同名方法,以賦予其更特殊的行為

new 關鍵字發生哪些事?

  • 建立一個新的物件
  • 將物件的 .__proto__ 指向 prototype,形成原型鏈
  • 將建構子的 this 指向 new 出來的新物件
  • 回傳這個物件

新增 / 修改屬性

有什麼特性

  • get & set 方法 - 模擬 Private 成員
    • 使用物件字面值的方式定義屬性
    • 透過這樣方式產生的物件,所有的屬性都是「公開成員」可隨意變動

title

  • get 屬性讀取,不允許修改
1
2
3
4
5
6
7
8
9
10
function Person( name, age, gender ){
this.getName = function(){
return name
}
this.getAge =function(){
return age
}
this.getGender = function(){
return gender
}
  • set 修改屬性更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  this.setName = function(name){
return this.setName = name
}

this.setGender = function(gender){
return this.setGender = gender
}

this.greeting = function(){
console.log('Hello! My name is ' + this.getName() + '.');
};
this.hello = function(person){
console.log('Hello' + ' '+person.name+' '+"I'm"+' ‘+this.getName())
}
this.old = function(person){
console.log('Hello' + ' '+"I'm"+" "+this.getName()+' '+"I'm"+' '+this.getAge()+' '+'old')
}
}

let person2 = new Person( 'John', 10, 'male');
person2.greeting(); // "Hello! My name is John."
console.log(person2.getAge()) // 10

屬性描述器 (Property descriptor)

是什麼

  • 自己設定的屬性 稱為 屬性的特徵,而設定這些屬性特徵的函式稱為 屬性描述器
  • 就算沒有屬性描述器,我們依然可以撰寫 JavaScript,但使用 屬性描述器 可以讓程式更為強健

有什麼特性

  • 改善過去 constructor 函式與 ES6 Class 語法仍然是「一對一」關係的語法糖,只能宣告成員方法,無法宣告成員屬性
  • 若要用 class 來模擬 private 透過 Object.defineProperty 加上 get、set 來處理
  • 會直接對一個物件定義屬性或是修改現有的屬性。執行後會回傳定義完的物件
  • 用來檢視屬性的特徵
    • 可否寫入(writable)
    • 可否配置(configurable)
    • 可否列舉(enumerable)
  • Object.defineProperty(obj, prop, descriptor)
    • obj
      • 要定義屬性的物件
    • prop
      • 要被定義或修改的屬性名字
    • descriptor
      • 要定義或修改物件敘述內容
  • Object.getOwnPropertyDescriptor()
    • 檢查物件屬性描述器的狀態
  • 屬性描述器(Property descriptor)要透過 ES5 所提供的 Object.defineProperty() 來使用
    • value: 屬性的值(唯一要設定的)
    • writable: 定義屬性是否可以改變,如果是 false 那就是唯讀屬性(預設:false)
    • enumerable: 定義物件內的屬性是否可以透過 for-in 語法來迭代(預設:false)
    • configurable: 定義屬性是否可以被刪除、或修改屬性內的 writable、enumerable 及 configurable 設定(預設:false)
    • get: 物件屬性的 getter function 回傳有效值 (預設:undefined)
    • set: 物件屬性的 setter function 決定如何處理數據 (預設:undefined)
    • 定義了 get 與 set 方法,表示要自行控制屬性的存取,那麼就不能再去定義 value 或 writable 的屬性描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var person = {}
Object.defineProperty(person,'name',{
value:'jimmy',
writable:false // 不可被寫入(自定義)
})

// 檢查物件屬性描述器的狀態
console.log(Object.getOwnPropertyDescriptor(person, 'name’))
// {value: "jimmy", writable: false, enumerable: false, configurable: false}

console.log(person.name) // jimmy
person.name='jj' // writable:true or false
console.log(person.name) // jj or jimmy

/*----------------------------------
function Archiver() {
var temperature = null;
var archive = [];

Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
console.log(this)
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
console.log(archive)
}
});
this.getArchive = function() { return archive; };
}

let arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

constructor、prototype、proto和原型鏈

Function, 對象(object)

  • 對象由函數創建,函數都是 Function 實例對象

title

  • Function 函數是 Person 函數的構造函數
  • Function 函數同時是自己的構造函數
  • Function 函數同樣是 Object 對象的構造函數
1
2
3
4
function Person(){} 
console.log(Person.constructor) // ƒ Function() { [native code] }
console.log(Function.constructor) // ƒ Function() { [native code] }
console.log(Object.constructor) // ƒ Function() { [native code] }

建構式 (Constructor)

有什麼特性

  • 有 function 就有 constructor
  • 只能使用 function,不能使用箭頭函式

流程

  • 產生一個新物件的 constructor 屬性設為 Person ,這個 Function 物件建立了一個 Person 建構式 (constructor) ,透過 new 關鍵字來建立各種實例物件
    • 這個物件繼承 Person.prototype (Function 物件)
    • person1 與 person2 是 Person 的實例對象,他們的 constructor 指指向創建它們的 Person 函數
    • Person 是函數,同時也是 Function 的實例對象,它的 constructor 指向創建它的 Function 函數
    • Function 函數,它是 JS 的內建對象,它的構造函數是它自己,所以內部 constructor 指向自己

title

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name,age){
this.name = name
this.age = age
this.greeting = function(){
console.log('Hello! My name is ' + this.name + '.');
};
this.hello = function(Person){
console.log('Hello' + ' '+Person.name+' '+"I'm"+' '+this.name+' '+this.age)
}
}

let person1 = new Person('jimmy',30)
person1.greeting() // "Hello! My name is jimmy."

let person2 = new Person( 'John', 10);
person2.greeting(); // "Hello! My name is John."
person1.hello(person2) // "Hello John I'm jimmy 30"

prototype (原型對象)

  • JS 並沒有對所建立的函式區分建構函式與一般的函式,所以只要是函式,就一定會有 prototype 屬性(除了語言內建的函式不會有這個屬性)
  • prototype 存檔點一樣裡面放著許多屬性&方法(共用)
  • function 自帶 prototype 存檔點
  • 實例物件沒有 prototype(只會繼承上一個的 prototype 方法),因為實例物件沒有 function

缺點1

  • constructor 生成的實例對象,有一個缺點就是無法共享屬性和方法(佔記憶體空間)
  • 因為沒有 class,所以它的繼承方法是透過 「原型」(prototype) 來進行實作
1
2
3
4
5
6
7
8
// 設定相同方法 但不等於一樣並且佔據記憶體空間
person1.atk = function() {
console.log('攻擊')
}
person2.atk = function() {
console.log('攻擊')
}
console.log(person1.atk === person2.atk) // false

改善

  • 透過「原型」繼承可以讓本來沒有某個屬性的物件去存取其他物件的屬性(可共享)
  • 所有實例物件需要共享的屬性和方法,都放在 prototype 裡面
  • 那些不需要共享的屬性和方法,就放在 constructor 裡面

title

1
2
3
4
5
6
7
// 設定共用方法在 prototype 裡面
Person.prototype.atk = function(){
return this.name+' '+'攻擊'
}
console.log(person1.atk()) // '攻擊'
console.log(person2.atk()) // "攻擊"
console.log(person1.atk === person2.atk) // true

缺點2

  • new Person( ) 出來的多個實例中如果都有 constructor 屬性,並且都指向創建自己的構造函數,所以它們都各自佔據記憶體空間

改善

  • constructor 可以被當成一個共享屬性存放在 prototype 中,作用也依然是指向自己的 constructor
  • 也就是默認 constructor 是被當做共享屬性放在它們的原型對像 prototype 中

title

缺點3

  • 如果是共享屬性 constructor,那將兩個實例其中一個屬性改了,為什麼第二個實例沒同步?
    • 因為 person1.constructor = Function 改的並不是原型對像上的共享屬性 constructor,而是給實例 person1 加了一個 constructor 屬性
    • 可隨意更改新增實例的 constructor 屬性,但無法通過一個 實例.constructor 找回創建自己的構造函數(之間沒有箭頭鏈接)

title

1
2
3
4
5
6
7
8
9
10
11
function Person() {}
var person1 = new Person()
var person2 = new Person()
console.log(person1.constructor) // ƒ Person() {}
console.log(person2.constructor) // ƒ Person() {}

person1.constructor = Function
console.log(person1.constructor) // ƒ Function() { [native code] }
console.log(person2.constructor) // ƒ Person() {}
console.log(person1) // Person {constructor: ƒ}
console.log(person2) // ƒ Person() {}

改善

_proto_

  • 讓實例物件找到自己的原形對象,每個物件都有一個 _proto_ 內部屬性,幫助物件間指向它繼承而來的原型 prototype 物件
  • _proto_ 這個內部屬性,它是一個存取器(accessor)屬性,意思是用 getter 和 setter 函式合成出來的屬性
  • 用 new 的話必須要有 constructor ,所以 constructor 內的屬性會共享給實例

title

1
2
3
4
5
6
7
8
9
10
11
12
// 創建自己的構造函數
console.log(person1.__proto__.constructor) // ƒ Person() {}
console.log(person1.constructor) // ƒ Person() {}
console.log(person1.constructor === person1.__proto__.constructor) // true

// 創建自己的構造函數內部的 prototype(原型對象)
console.log(person1.__proto__) // {atk: ƒ, constructor: ƒ}
console.log(person1.__proto__.__proto__)
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

console.log(person1.__proto__.__proto__.constructor) // ƒ Object()
console.log(person1.__proto__.__proto__.__proto__) // null

最後完整圖

  • 實例對象._proto_.constructor = 創建自己的 constructor
  • 實例對象._proto_ = 創建自己的 prototype(原型對象共用的東西都在裡面)
  • 實例對象.prototype._proto_.constructor = 原型對象
    • Object 不會像 Function 一樣指向自己的 protoype ,如果指向那就會進入死回圈

title

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
console.log(person1.constructor)  // ƒ Person() {}
console.log(Person.constructor) // ƒ Function() { [native code] }
console.log(Function.constructor) // ƒ Function() { [native code] }
console.log(Object.constructor) // ƒ Function() { [native code] }

console.log(person1.__proto__.constructor) // ƒ Person() {}
console.log(Person.__proto__.constructor) // ƒ Function() { [native code] }
console.log(Function.__proto__.constructor) // ƒ Function() { [native code] }
console.log(Object.__proto__.constructor) // ƒ Function() { [native code] }

console.log(Person.__proto__.constructor === Person.constructor) // true


console.log(person1.prototype) // undefined
console.log(Person.prototype.__proto__.constructor) // ƒ Object() { [native code]
console.log(Function.prototype.__proto__.constructor) // ƒ Object() { [native code] }
console.log(Object.prototype.constructor) // ƒ Object() { [native code] }

console.log(person1.__proto__.__proto__.constructor) // ƒ Object() { [native code] }
console.log(Person.__proto__.__proto__.constructor) // ƒ Object() { [native code] }
console.log(Function.__proto__.__proto__.constructor) // ƒ Object() { [native code] }
console.log(Object.__proto__.__proto__.constructor) // ƒ Object() { [native code] }

console.log(Person.prototype.__proto__.constructor === Person.__proto__.__proto__.constructor)
// true

原型鏈 (prototype chain)

  • 由 _proto_ 指向連接起來的結構,稱之為原型鏈(prototype chain),也就是原型繼承的整個連接結構(紅色箭頭)
  • person -> function -> object -> nul

title

1
2
3
4
5
6
7
8
9
10
11
console.log(Person.constructor)             // ƒ Function() { [native code] }
console.log(Person.__proto__) // ƒ () { [native code] }
console.log(Person.__proto__.constructor) // ƒ Function() { [native code] }

console.log(Person.__proto__.__proto__)
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

console.log(Person.__proto__.__proto__.constructor)
// ƒ Object() { [native code] }

console.log(Person.__proto__.__proto__.__proto__) // null

缺點

  • prototype 的 constructor 很容易被更改
  • 所有所謂繼承下來的屬性全都是共享屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function GrandFather() {
this.name = 'GrandFather'
}
function Father() {
this.age = 40
}

Father.prototype = new GrandFather() // Father 函數改變自己的 prototype 指向

function Son() {}
Son.prototype = new Father() // Son 函數改變自己的 prototype 指向
console.log(Son.prototype.constructor) // ƒ GrandFather()

var son = new Son()
console.log(son.name) // GrandFather
console.log(son.age) // 40

改善

  • 重新指向回自己的 prototype.constructor
1
2
3
4
5
6
Son.prototype.constructor = Son
console.log(Son.prototype.constructor) // ƒ Son() {}

let son2 = new Son()
console.log(son2.name) // GrandFather
console.log(son2.age) // 40

名詞整理

含義 作用
constructor 建立實例對象的構造函數 容易被更改、放在 prototype 中當共享屬性
prototype 對象的原型對象 存放共享方法 / 屬性、節省記憶體、只要是 function 都有、實例對象沒有這屬性
_proto_ 指向自己的原型對象、構成原型鍊、每個對象都有一個 _proto_

原型繼承(Prototype-based inheritance)

基礎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function Energy(name,age){
this.name = name
this.age = age
}

// 設定共同方法
Energy.prototype.atk = function(){
return this.name+' '+'攻擊'
}

// 實例 1
let Hulk = new Energy('Hulk',50)

// Hulk 自己的方法
Hulk.call = function () {
return this.name + ' 打電話'
}
console.log(Hulk.call()) // Hulk 打電話
console.log(Hulk.atk()) // Hulk 攻擊
console.log(Hulk.constructor) // function Energy
console.log(Hulk.prototype) // undefined
console.log(Hulk.__proto__) // {atk: ƒ, constructor: ƒ}
console.log(Hulk.__proto__.constructor) // function Energy
console.log(Hulk.__proto__.__proto__.constructor) // ƒ Object() { [native code] }
console.log(Hulk.__proto__.__proto__.__proto__) // null


// 實例 2
let Iron = new Energy('Iron',53)

// Iron 自己的方法
Iron.call = function () {
return this.name + ' 打電話'
}
console.log(Iron.call()) // Iron 打電話
console.log(Iron.atk()) // Iron 攻擊

// 判斷 constructor function 內外方法是否共用
console.log(Hulk.call === Iron.call) // flase
console.log(Hulk.atk === Iron.atk) // true

// 判斷某個物件是否存在於另一個物件的原型鏈中
console.log(Energy.prototype.isPrototypeOf(Hulk)); // true

繼承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Energy(name,age){
this.name = name
this.age = age
}

function Batman(){}
console.log(Batman.prototype) // {constructor: ƒ} Batman 未掛載到 Energy

// Batman 函數改變自己的 prototype 指向(繼承)
Batman.prototype = new Energy('Batman',65)

// Batman 自己的 method
Batman.speak = function(){
return this.name + ' 打電話'
}
console.log(Batman.speak()) // Batman 打電話
console.log(Batman.prototype) // Energy{name: "Batman", age: 65} 已掛載
console.log(Batman.prototype.constructor) // ƒ Energy(name,age)

不指向回自己

  • 在 prototype.constructor 底下建立實例,constructor 會指向繼承的 prototype,而原型鍊就不是在 Batman 下
  • micky2 -> Energy -> Energy -> Object -> null
1
2
3
4
5
6
7
8
9
10
11
12
13
let micky2 = new Batman()

console.log(micky2.name) // Batman
console.log(micky2.atk()) // Batman 攻擊
console.log(micky2.constructor) // ƒ Energy(name,age)
console.log(micky2.prototype) // undefined
console.log(micky2.__proto__) // Energy{name: "Batman", age: 65}
console.log(micky2.__proto__.constructor) // ƒ Energy(name,age)
console.log(micky2.__proto__.__proto__.constructor) // ƒ Energy(name,age)
console.log(micky2.__proto__.__proto__.__proto__.constructor) // ƒ Object()
console.log(micky2.__proto__.__proto__.__proto__.__proto__) // null
console.log(Energy.prototype.isPrototypeOf(micky2)); // true
console.log(Batman.prototype.isPrototypeOf(micky2)); // true

指向回自己

  • 後在 prototype.constructor 底下建立實例,constructor 一樣指向繼承的 prototype,而原型鍊會在 Batman 下
  • micky -> Batman -> Energy -> Object -> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 指向回自己
Batman.prototype.constructor = Batman

console.log(Batman.prototype) // Energy{name: "Batman", age: 65, constructor: ƒ}
console.log(Batman.prototype.constructor) // ƒ Batman(){}

let micky = new Batman()

console.log(micky.name) // Batman
console.log(micky.atk()) // Batman 攻擊
console.log(micky.prototype) // undefined
console.log(micky.constructor) // ƒ Batman(){}
console.log(micky.__proto__) // Energy{name: "Batman", age: 65, constructor: ƒ}
console.log(micky.__proto__.constructor) // ƒ Batman(){}
console.log(micky.__proto__.__proto__.constructor) // ƒ Energy(name,age)
console.log(micky.__proto__.__proto__.__proto__.constructor) // ƒ Object()
console.log(micky.__proto__.__proto__.__proto__.__proto__) // null
console.log(Energy.prototype.isPrototypeOf(micky)); // true
console.log(Batman.prototype.isPrototypeOf(micky)); // true

類別的繼承(Classical inheritance) - class 語法糖前身

有什麼特性

  • Object.create 沒有 constructor ,需要重新定義
  • 模仿了傳統物件導向語言的類別方法,而達到繼承的功能

判斷

  • 判斷某個物件是否存在於另一個物件
  • 傳入對象將作為新建對象的原型

Object.create(prototype, descriptors)

  • 有什麼特性
    • 定義一個物件當作原型物件
    • 原型物件建立另一個新的物件(過程可以加入其他的屬性)
    • 不會執行建構函式,繼承 prototype 的方法&屬性
  • 參數
    • prototype
      • 必需,要用作原型的對象,可以為 null(停止 prototype chain)
    • descriptors
      • 屬性描述器
        • 可選,數據屬性包含 value,writable,enumerable,configurable 特性,未指定最後三個特性,則默認為 false
        • 檢索或設置該值, 訪問器屬性包含 set 和/或 get 指定原型且可選擇性地指定屬性

isPrototypeOf

  • prototype.isPrototypeOf(object)
  • 判斷某個物件是否存在於另一個物件的原型鏈結中

instanceof

  • object instanceof constructor
  • 物件 instanceof 函式,回傳 boloon
  • 檢查物件是否為指定的建構子所建立的實體
  • instanceof 測試實例和原型鏈中出現過的 constructor function,如果存在就會返回 true

Object.keys

  • 回傳一個 array,陣列中的各元素為直屬於 obj ,對應可列舉屬性名的字串

組合繼承流程

  • 建立子類別,增加子類別的屬性/方法
  • 選擇要繼承的父類別 prototype(方法自動繼承)
  • 將子類別建立好在掛載到要的父類別 prototype 上
1
2
3
4
5
6
7
8
9
// 父類別
function Energy(name,age){
this.name = name
this.age = age
}

Energy.prototype.atk = function(){
return this.name+' '+'攻擊'
}

兩種新增屬性的方式

  • call(thisAg,arg1,argN…) 強制指定參數帶入父類別屬性 定義新的建構子(SuperEnergy)
1
2
3
4
function SuperEnergy(type,name,age,status,level){
Energy.call(this,type,name,age,status) // 指向新的物件
this.level = level // 子類別自己可新增屬性
}
  • 直接新增子類別自己的屬性
1
2
3
4
5
6
7
function SuperEnergy(name,type,status,level,age){
this.name = name
this.type = type
this.status = status
this.level = level
this.age = age
}

使用共同方法

  • 將 SuperEnergy.prototype 建立在 Object.create(Energy)無法使用共同方法
  • 所以要建立在 Object.create(Energy.prototype)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 掛載到父類別 prototype 上
SuperEnergy.prototype = Object.create(Energy.prototype)

// 指向回自己
SuperEnergy.prototype.constructor = SuperEnergy

// 物件實例步驟
// 可定義物件自己的屬性&方法從 SuperEnergy 產生實例 hulk

let hulk = new SuperEnergy('hulk','sencise','pace','large',45)
hulk.skill = function(name){
return 'GAA'+' '+"I'm"+' '+this.name
}

console.log(hulk.skill()) // GAA I'm hulk GAA I'm Energy
console.log(hulk.name) // hulk Energy
console.log(hulk.atk()) // hulk 攻擊
console.log(hulk.age) // 45

原型鍊

  • hulk -> SuperEnergy -> Energy -> Object -> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
console.log(hulk.constructor)                      // ƒ SuperEnergy(name,type,status,level)
console.log(hulk.prototype) // undefined
console.log(hulk.__proto__) // Energy {constructor: ƒ}
console.log(hulk.__proto__.constructor) // ƒ SuperEnergy(name,type,status,level)
console.log(hulk.__proto__.__proto__) // {atk: ƒ, constructor: ƒ}
console.log(hulk.__proto__.__proto__.constructor) // ƒ Energy(name,age)
console.log(hulk.__proto__.__proto__.__proto__)
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(hulk.__proto__.__proto__.__proto__.constructor) // ƒ Object()
console.log(hulk.__proto__.__proto__.__proto__.__proto__) // null

// 檢查物件是否為指定的建構子所建立的實體
console.log(hulk instanceof SuperEnergy) // true
console.log(hulk instanceof Energy) // true
console.log(SuperEnergy instanceof Energy) // false
console.log(hulk.__proto__ === SuperEnergy.prototype) // true
console.log(SuperEnergy.__proto__=== Energy.__proto__) // true

// 可用的屬性
console.log(Object.keys(hulk)) // ["name", "type", "status", "level", "skill"]

// 更改父類別的方法
Energy.prototype.atk = function(){
return this.name+' '+'先不攻擊'
}
console.log(hulk.atk()) // "hulk 先不攻擊"

物件繼承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let Alien = {
type:'evialEnergy',
name: 'bloodSpider',
age:'none',
status:'monster',
speak:function(name){
return "I'm"+' '+this.name
}
}

let bloodSpider = Object.create(Alien)
console.log(bloodSpider.status) // "monster"
console.log(bloodSpider.speak()) // "I'm bloodSpider”
console.log(Alien.constructor) // ƒ Object() { [native code] }
console.log(Alien.prototype) // undefined
console.log(Alien.__proto__) // function Object()
console.log(Alien.__proto__.__proto__) // null

建立物件的語法

  • 物件字面定義,相等於 new Object()
    • const newObject = { }
  • 使用 Object.create 方法
    • const newObject = Object.create( proto )
    • const newObject = Object.create( proto.prototype )
  • ES6 類別定義,或是建構函式
    • const newObject = new ConstructorFunc ( )
    • const newObject = new ClassName ( )

類別宣告(class declaration)

  • class 並非如其他物件導向語言般在宣告時期靜態的複製定義,而只是物件間的連結
  • 執行時期變更父類別的方法或屬性,子類別與其實體都會受到影響
  • class 語法無法宣告屬性只能宣告方法,因此若想宣告屬性以追蹤共用狀態,就只能回歸到 prototype
  • 為了優化效能,super 是在宣告時期靜態綁定的,在某些狀況下,綁定會失敗,因此必須手動綁定
  • class 語法只能指定方法,不能設定屬性,這避免開發者誤將屬性(狀態或資料)放在類別中造成的共用問題

class

  • 相當於建立共享方法,宣告自己的屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Energy{
// 宣告自己的屬性
constructor(name,age){
this.name = name
this.age = age
}
// 共享方法
atk(){
return this.name+' '+'攻擊'
}
speak(){
return 'I`am ' + this.name
}
}

let Iron = new Energy('Iron',32)

console.log(Iron.name) // Iron
console.log(Iron.atk()) // Iron 攻擊
console.log(Iron.speak()) // I`am Iron
console.log(Iron.constructor) // class Energy
console.log(Iron.__proto__.constructor) // class Energy
console.log(Iron.__proto__.__proto__.constructor) // function Object()
console.log(Iron.__proto__.__proto__.__proto__) // null

類別繼承 - extends

  • 把子類別掛載到父類別
    • 相當於設定 prototype(_proto_)
  • 原型鍊
    • Energy -> Function -> Object -> null
  • 呼叫父類別 - super
    • 在子類別中,呼叫父類別的方法或屬性來使用
    • 子類必須在 constructor 方法中調用 super 方法,否則新建實例時會報錯
      • 這是因為子類自己的 this 對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然後再對其進行加工,加上子類自己的實例屬性和方法,如果不調用 super 方法,子類就得不到 this 對象
    • 靜態綁定 - 也就是父類別的方法或屬性不會隨不同子類別變化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Energy{
constructor(name,age){
this.name = name
this.age = age
}
atk(){
return this.name+' '+'攻擊'
}
speak(){
return 'I`am ' + this.name
}
}

/* 這兩句相當於下面的寫法
* SuperEnergy.prototype = Object.create(Energy.prototype)
* SuperEnergy.prototype.constructor = SuperEnergy
*/

class SuperEnergy extends Energy {

// 表示屬性來自父類別共用
constructor(name,age,status){
super(name,age)
this.status = status
}
angry(){
return this.name+' '+ this.status
}

// 更改父類別方法名子並直接把相同方法掛載到 SuperEnergy 下
superGa(){
return super.speak()
}
}

console.log(SuperEnergy.prototype) // Energy {constructor: ƒ, speak: ƒ}
console.log(SuperEnergy.constructor) // ƒ Function()

console.log(SuperEnergy.__proto__) // class Energy
console.log(SuperEnergy.__proto__.constructor) // ƒ Function()
console.log(SuperEnergy.__proto__.__proto__) // ƒ () { [native code] }
console.log(SuperEnergy.__proto__.__proto__.constructor)
// function Function() { [native code] }
console.log(SuperEnergy.__proto__.__proto__.__proto__.constructor)
// function Object() { [native code] }
console.log(SuperEnergy.__proto__.__proto__.__proto__.__proto__) // null

建立繼承實例

  • 原型鍊
    • SuperEnergy -> Energy -> Object -> null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
let hulk = new SuperEnergy('hulk',45,'crazy')

// 實例設定自己的方法
hulk.skill = function(){
return 'GAA'+' '+this.name +' '+ "smash"
}

console.log(hulk.name) // hulk
console.log(hulk.status) // crazy
console.log(hulk.age) // 45
console.log(hulk.atk()) // hulk 攻擊
console.log(hulk.angry()) // hulk crazy
console.log(hulk.skill()) // GAA hulk smash
console.log(hulk.speak()) // I`am hulk
console.log(hulk.superGa()) // I`am hulk


console.log(hulk.prototype) // undefined
console.log(hulk.constructor) // class SuperEnergy extends Energy

console.log(hulk.__proto__) // Energy {constructor: ƒ, speak: ƒ}
console.log(hulk.__proto__.constructor) // class SuperEnergy extends Energy
console.log(hulk.__proto__.__proto__) // {constructor: ƒ, atk: ƒ}
console.log(hulk.__proto__.__proto__.constructor) // class Energy
console.log(hulk.__proto__.__proto__.__proto__)
// {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(hulk.__proto__.__proto__.__proto__.constructor) // ƒ Object()
console.log(hulk.__proto__.__proto__.__proto__.__proto__) // null


// 子更動父的方法
SuperEnergy.prototype.atk = function(){
return this.name+' '+'不要攻擊'
}

console.log(hulk.atk()) // hulk 不要攻擊