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 function Person ( ) {}const p1 = new Person()p1 instanceof Person 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()Person.prototype = { greet(){} } p1.greet()
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() p1.__proto__ === Person.prototype Person.prototype.isPrototypeOf(p1) Object .getPrototypeOf(p1) `` ` ` `` jsfunction Person3 ( ) {}Person3.prototype = { name: "A" , age: 10 } 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() p2.greet() p1.friends.push("May" ) console .log(p2.friends)
Prototypal Inheritance 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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)
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" ] } let p2 = Object .create(p1)console .log(p2.friends) let p3 = Object .create(p1, { name: { value: "P3" } }) console .log(p3.name)
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()
優點: (相較 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() const s1 = new Sub()console .log(s1 instanceof Sub) console .log(s1 instanceof Super) console .log(Sub.prototype.constructor) console .log(s1.constructor)
Sub.prototype.constructor = Sub
原本想說這一行沒有的話, s1 instanceof Sub
會是 false, 但看起來判斷正確, 這樣子是那裏出現問題?
A: 因為 Sub.prototype.constructor
因為之前修改 prototype 的關係, 已變成 Super
Class