我來重新學習 javascript 的面向對象(part 1)

很多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`對象

做了以上的改變之後,整個創建對象的模式被改變了:

  1. 首先定義了一個 Food 的構造函數(其實就是之前的工廠函數createFood,但是現在升級了)
  2. 通過 new 來創建一個對象(現在的 Food 用 new 來先創建)
  3. 將構造函數的作用域賦值給新對象,將this指向這個新對象(將升級版的工廠送給這個用 new 創建的 food)
  4. 執行構造函數的中的代碼(升級版的工廠會自動將裏面的零件和機器放到新的 Food 上,相當於組裝放在了食物本身 身上)
  5. 不需要主動 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);
}();

這樣就會造成內存和時間和性能的浪費,明明不需要重新重建新的函數實例的。

其實在之前的工廠模式裏面,也存在這個問題,不過工廠模式更徹底,直接完全創建一個新對象,而構造函數模式的話只是方法會被重新創建。

那怎麼解決呢?會用到原型模式,下回分解。

參考內容

  1. 紅寶書,《javascript 高級程序設計第三版》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章