很多job 的描述都說要求精通 javascript 面向對象編程,但是根據一般的套路,寫精通其實就是熟練,寫熟練其實就是一般,寫一般其實就是懵逼!
雖然話說如此,但是我們還是要熟練使用 javascript 面向對象編程的,畢竟這是js社會高能人才的其中一個標準,這裏我就用一個鮮活的例子來說明和理解我們應該如何使用javascript 面向對象的方式來編程。
一、野蠻方式構建對象
剛開始最初,我們創建對象的方式是這樣的:
// 。。。。每次都要寫上面的一大段代碼,只是爲了創建一個 food var food = new Object(); food.name = "蘋果"; food.sayName = function() { console.log("我是" + this.name); };
但是這樣創建起來很麻煩,寫的代碼也是很長,如果要創建好多對象,例如我製造了10000個食物,就要寫10000次這一大段代碼了,所以後來聰明的工程師改爲了這樣寫:
// 起碼比之前的少了幾行,也整潔了一些 var food = { name: "蘋果", sayName: function() { console.log("我是" + this.name); } };
起碼代碼少了一些,但是還是沒辦法很好解決我要寫100000段代碼的問題,所以再後來的人們就開始使用一些高級玩意來解決這個問題。
二、使用工廠模式構建對象
通過抽象出創建具體對象的過程,用函數來進行封裝,換句話來說,就是抽象了一個 food 的工廠,然後通過對這個工廠傳入不同的材料,來生成不同的食物。
function createFood(name) { var o = new Object(); o.name = name; o.sayName = function() { console.log("我是" + this.name); }; return o; } var food1 = createFood("蘋果"); var food2 = createFood("蘋果");
這裏可以看到food1,food2 就是這樣被製造出來的,然後只需要少量的代碼(預先定義好一個生產工廠函數),就可以完成大量的事情,徹底解決了問題,實現了多快好爽的新局面。但是用了一段時間之後,隨之而來發現一個新問題,當食物多起來的時候,老闆貌似不知道哪些食物是屬於那些分類的(假設老闆是 zz),那怎麼辦呢?
// 都統一返回是[Function: Object],沒辦法用區分識別(賣個關子,你不用管那個constructor) console.log(food1.constructor) // 返回[Function: Object] console.log(food2.constructor) // 返回[Function: Object]
三、使用構造函數模式來區分自己人
經過一番智慧交流之後,聰明的人們想出了一個方法,使用一個在對象裏面的 constructor
函數來識別那些不一樣的對象,類似使用部門工牌來標記這個人是是屬於哪個部門的。
function Food(name) { this.name = name; this.sayName = function() { console.log("我是" + this.name); }; } var food1 = new Food("蘋果"); var food2 = new Food("蘋果"); // 假設這裏有一個其他的食物,可能是冒充的 var food3 = new otherFood("蘋果");
因爲要實現類似工牌的方式來識別,所以在創建food的工廠裏做一些調整:
- 沒有顯式的創建對象,例如:
var o = new Object();
- 直接將屬性和方法付給了
this
對象 - 沒有
return
語句 - 函數使用了大寫字母開頭(這裏只是爲了區分這個函數的特別,按照慣例,大寫字母開頭的,一般都是 class 或者構造函數)
- 使用了 new 來創建
Food
`對象
做了以上的改變之後,整個創建對象的模式被改變了:
- 首先定義了一個
Food
的構造函數(其實就是之前的工廠函數createFood,但是現在升級了) - 通過
new
來創建一個對象(現在的 Food 用 new 來先創建) - 將構造函數的作用域賦值給新對象,將
this
指向這個新對象(將升級版的工廠送給這個用new
創建的 food) - 執行構造函數的中的代碼(升級版的工廠會自動將裏面的零件和機器放到新的 Food 上,相當於組裝放在了食物本身 身上)
- 不需要主動
return
,自動返回新對象(升級版的工廠會自動返回構造好的 food 對象)
通過這種方式,我們製造出來的食物都會有一個 constructor
爲 Food 的標記來標識,如果看到不是的話,那肯定就不是我們製造的。
console.log(food1.constructor) // 返回[Function: Food] console.log(food2.constructor) // 返回[Function: Food] console.log(food3.constructor) // 返回[Function: OtherFood] // 檢驗的方式有兩種 console.log(food1.constructor == Food) // 返回 true console.log(food2.constructor == Food) // 返回 true console.log(food3.constructor == Food) // 返回 false ,這個不是我們製造的食物 console.log(food1 instanceof Food) // 返回 true console.log(food3 instanceof Food) // 返回 false,這個不是我們製造的食物
可以看到,使用了新技術(constructor
模式技術)之後,在沒有增加工作量的情況下,解決了令人頭痛的問題,簡直是完美,不過過了一段時間之後,發現好像還是有些瑕疵,使用構造函數constructor
模式的時候,函數裏面的每個方法都會在每個實例上重新創建一遍,那麼最明顯的地方是:
console.log(food1.sayName == food2.sayName); // 返回 false
因爲使用new
來創建實例,new
的話還會把構造函數裏面的方法也一起創建,因爲方法也是函數,而函數的實例化也會被new
觸發:
// 省略了其他部分,只關注方法部分 this.sayName = function() { console.log("我是" + this.name); }; this.sayName = new function() { console.log("我是" + this.name); }();
這樣就會造成內存和時間和性能的浪費,明明不需要重新重建新的函數實例的。
其實在之前的工廠模式裏面,也存在這個問題,不過工廠模式更徹底,直接完全創建一個新對象,而構造函數模式的話只是方法會被重新創建。
那怎麼解決呢?會用到原型模式,下回分解。
參考內容
- 紅寶書,《javascript 高級程序設計第三版》