0%

20200125 Professional Javascript 4th Ch8

current progress: class

Factory Pattern

1
2
3
4
5
6
7
8
9
function createPerson(name, age, job){
return {
name,
age,
sayName(){
console.log(this.name)
}
}
}

cons: not DRY

Constructor Pattern

1
2
3
4
5
6
7
function Person(name, age){
this.name = name
this.age = age
this.sayName = function(){
console.log(this.name)
}
}

Object

Prototype

有 class 的概念的話, 基本上某物件產生後, 所屬的類別就是固定不變
在 JS 沒有 class 的概念, 但是可以使用 prototype chain 透過 prototype 及 constructor 的關係來做判斷: 某物件是否屬於某個類別 (constructor)
因為不是內部的機制, 所以會有正常的使用情形, 也有可能發生預期之外的使用情形, 如果團隊的人員彼此之間沒有對這件事有共識的話

名詞定義

Constructor: 用來建立新物件的函式

判斷 prototype 的方式

instanceof
isPrototypeOf

1
2
3
4
5
6
// use Person as constructor
function Person(){}
const p1 = new Person()

p1 instanceof Person // true
Person.prototype.isPrototypeOf(p1)

Dynamic nature of prototype

在使用 constructor 函式建立物件的時候, 會將此物件的 [[PROTOTYPE]] 指到 constructor.prototype

但是之後如果對 constructor.prototype 指到另外一個物件, 則原先使用這個 constructor 建立的物件, 他的 [[PROTOTYPE]] 不會被指到新的 prototype 物件

1
2
3
4
5
6
7
8
9
function Person(){}
const p1 = new Person()

// prototype of constructor is changed
Person.prototype = {
greet(){}
}

p1.greet() // error

Prototype Patern

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
function Person(){}
Person.prototype.name = "A"
Person.prototype.age = 10

const p1 = new Person() // with keyword new, whether () exist is fine


p1.__proto__ === Person.prototype // true

Person.prototype.isPrototypeOf(p1) // true
Object.getPrototypeOf(p1) // Person.prototype
```

```js
function Person3(){}
Person3.prototype = {
name: "A",
age: 10
}
// constructor is Object

function Person4(){}
Person4.prototype = {
constructor: Person4,
name: "A",
age: 10
}
//constructor is Person4 now, but its enumberable is set to true by default

function Person5(){}
Person5.prototype = {
name: "A",
age: 10
}
Object.defineProperty(Person5.prototype, "constructor",{
enumerable: false,
value: Person5
})

Problems with Prototype

shared nature

如果物件上沒有該屬性, 會往 prototype chain 上尋找
有找到的話就會使用 prototype chain 上面的資料
如果沒有小心使用的話, 會產生預期之外的結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){}
Person.prototype.name = "Common"
Person.prototype.friends = []
Person.prototype.greet = function(){console.log(this.name)}

const p1 = new Person()
const p2 = new Person()
p1.name = "Jhon"
p1.greet() // Jhon
p2.greet() // Common

// 問題產生: 感覺是修改 p1 的資料, 但是 p2 也受到影響了
// 因為改到了共用的資料
p1.friends.push("May")
console.log(p2.friends) // [ "May" ]

Prototypal Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Douglas version
function object(o){
function F(){}
F.prototype = 0
return new F()
}

let p1 = {
name: "A",
friends: ["Bob"]
}
let p2 = object(p1)
p2.friends.push("Cindy")

console.log(p1.friends) // ["Bob", "Cindy"]

Object.create(param1, param2)

param1: 要用來作為 prototype 的物件
param2: 格式和 Object.defineProperty 參數一樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = {
name: "A",
friends: ["Bob"]
}

// Object.create
let p2 = Object.create(p1)
console.log(p2.friends) // ["Bob", "Cindy"]

let p3 = Object.create(p1, {
name: {
value: "P3"
}
})
console.log(p3.name) // "P3"

Parasistic Inheritance (Crockford)

1
2
3
4
5
6
7
function createAnother(original){
const clone = Object.create(original)
clone.sayHi = function(){
console.log({"H1")
}
return clone
}

這跟 Constructor Pattern 好像, 差異如下:
這個方式: 可以傳一個物件進來, 以此為基底產生一個新物件, 再對新物件寫資料上去
Constructor Pattern: 資料方法都是在 constructor 函式當中完成, 不需要傳物件進來; 並且寫資料到 this 上

同樣的問題都是代碼無法被復用

Combination Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
function Super(){
this.name="Super"
this.age = 10
}

function Sub(){
this.name = "Sub"
}
Sub.prototype = new Super()
Sub.prototype.constructor = Sub
Sub.prototype.sayHi = function(){console.log("Hi")}

const s1 = new Sub()

Parasitic Combination Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function inheritPrototype(subType, superType){
const prototype = Object.create(superType.prototype)
prototype.constructor = subType
subType.prototype = prototype
}

function Super(){}
function Sub(){}

inheritPrototype(Sub, Super)

Sub.prototype.sayHi = function(){
console.log("Hi")
}

const s1 = new Sub()
s1.sayHi() // Hi

優點:
(相較 Combination inheritance )
只會呼叫 Super 一次
不會存多餘的資料在 Sub.prototype 這邊

自問自答

Q: 為何需要透過 constructor 函式? 這和直接用 {} 建立物件有何區別?

A: 因為這樣才能透過 JS 內建的機制去將 [[PROTOTYPE]] 指到 constructor.prototype
雖然目前主要的 JS Runtime 都有將 [[PROTOTYPE]] 透過 __proto__ 讓外部能夠進行讀取或者設定, 但是這樣子可能還是比較非正規, 因為還是有可能有的環境沒有實作 __proto__ 的機制, 那麼在這種環境下就會有問題, 只是我們可以透過
__proto__ 去了解內部運作的方式

Q: instanceof 是拿甚麼在判斷的呢?

在書本 p.306 有一句話: You likely come to understand that the instanceof operator checks an instance's prototype chain against a constructor function

看下面的例子可能會覺得怪怪的

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

function Sub(){
this.name = "Sub"
}
Sub.prototype = new Super()
// Sub.prototype.constructor = Sub

const s1 = new Sub()
console.log(s1 instanceof Sub) // true
console.log(s1 instanceof Super) // true
console.log(Sub.prototype.constructor) // Super
console.log(s1.constructor) // Super

Sub.prototype.constructor = Sub
原本想說這一行沒有的話, s1 instanceof Sub 會是 false, 但看起來判斷正確, 這樣子是那裏出現問題?

A: 因為 Sub.prototype.constructor 因為之前修改 prototype 的關係, 已變成 Super

Class