※ 本文同步發表於隨性筆記
本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML!
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章
No CSS! No HTML! No Browser!
Just need programming language
既然是要來物件導向,當然要先來學怎麼建立物件。本節帶你看看如何建立一個新的物件。
直接建立
obj0 = {
name: "World",
hello(){console.log(`Hello, ${this.name}`)},
}
obj0.hello();
工廠模式
可以直接建立物件,也可以透過工廠模式建立並初始化物件。
function FactoryNew(name){
/*
var new_obj = {
name: name,
hello(){console.log(`Hello, ${this.name}`)},
};
*/
var new_obj = Object.create(obj0)
new_obj.name = name
return new_obj;
}
var obj1 = FactoryNew("Daniel");
obj1.hello() // => Hello, Daniel
obj0.hello() // => Hello, World
單純使用工廠模式太過簡單了,既然都叫原形繼承了,就用原形設計模式(prototye design pattern來做吧!
※ 上面兩種寫法在此作用等價
有些物件若以標準的方式建立實例,或者是設定至某個狀態需要複雜的運算及昂貴的資源,則您可以考慮直接以某個物件作為原型,在 需要個別該物件時,複製原型並傳回 。1
雖然上面使用了Object.create
2來建立新物件,不過有些細節與原形設計模式還是不同,我以後會說到,這裡就姑且認為是複製物件內容到新物件裡去吧。
new Operator
在很早之前就有了new
操作方法3。透過new
,工廠模式可以更加簡單:
function FooConstructor(name){
this.name = name;
this.hello = function(){
console.log(`Hello, ${this.name}`);
}
}
var obj2 = new FooConstructor("Foo");
obj2.hello() // Hello, Foo
※ 注意!你當然可以是用Foo()
的命名方式,這樣new Foo(name)
會更明確好看,不過為了後續的內容,我加上了Contructor
。
new 發生了什麼事?
根據MDN是這麼寫的:
1. 創建一個空的簡單JavaScript對象(即{});
2. 鏈接該對象(即設置該對象的搆造函數)到另一個對象 ;
3. 將步驟1新創建的對象作為this的上下文 ;
4. 如果該函數沒有返回對象,則返回this。
看看第4點,是的我們剛剛沒有回傳值,當然你可以回傳個物件:
function Empty(name){
this.name = name
this.hello = function(){
console.log(`Hello, ${this.name}`);
}
return {}; // 故意回傳一個空物件
}
empty_obj = new Empty("World")
// empty_obj.hello() // error! no hello() method
console.log(empty_obj) // => {}
來加點料
這小節稍微有點進階,看不懂可以先跳過。
var obj3 = {};
obj3.constructor = FooConstructor;
obj3.constructor("Kitty");
obj3.hello(); // => Hello, Kitty
var obj4 = {};
obj4.__proto__ = FooConstructor.prototype;
obj4.constructor("K-on"); // 完了,暴露宅屬性
obj4.hello(); // => Hello, K-on
Object.is(obj3.constructor, obj4.constructor);
我明天才會提到原形鏈相關的概念。這裡只要先知道obj3
、obj4
、FooConstructor
都有關係就好。然後透過增加FooConstructor.prototype
的新方法,可以使所有都有新的方法:
FooConstructor.prototype.bye = function(){console.log(`Bye Bye, ${this.name}`)};
obj2.bye(); // => Bye Bye, Foo
// obj3.bye(); // error! 沒繼承到`FooConstructor`,不受影響
obj4.bye(); // => Bye Bye, K-on
new.target
FooConstructor
也是函式,可以直接呼叫,但可能有異想不到的結果:
var name; // just declare
console.log(name); // => underfined
FooConstructor("JavaScript");
// Oops! 怎麼被改到了??
console.log(name); // => JavaScript
欸欸!!為什麼name被改到了??
這是因為為特別確定this
的話,會指向globalThis
4物件。瀏覽器指window
;在Node.js環境下是global
。
所以最好的方式是透過new.target
5加以限制:
function FooConstructor(name){
if (!new.target) throw "FooConstructor() must be called with new";
this.name = name;
this.hello = function(){
console.log(`Hello, ${this.name}`);
}
}
// FooConstructor("JavaScript"); // error! FooConstructor() must be called with new
[工商時間] 俄羅斯方塊 網頁遊戲
在ES6都還沒出來時,為了練習JS裡物件導向的概念,從頭不搞框架自幹了網頁遊戲--俄羅斯方塊。賞臉去玩一下吧!(儘管是陳舊的code,但應該還是有些值得看一下)
參考資料
良葛格。Prototype 模式↩
[MDN]Object.create()↩
[MDN]new operator↩
[MDN]globalThis↩
[MDN]new.target↩