JavaScript面向對象OOM 2(JavaScript 創建對象的工廠模式和構造函數模式)

  在創建對象的時候,使用對象字面量和 new Object() 構造函數的方式創建一個對象是最簡單最方便的方式。但是凡是處於初級階段的事物都會不可避免的存在一個問題,沒有普適性,意思就是說我要爲世界上(程序中)的所有使用到的對象都使用一遍 var xxx = {} ,對於'懶惰'的程序員來講是不可以接受的。即便你能接受這種創建的方式,也無法保證將所有對象歸類這一哲學問題。

  由此,優秀的程序員們利用現有的規則,創造出了一種種優秀的解決方案 -- 這些優秀的解決方案統稱爲設計模式。

  在 JavaScript 中,設計模式由初級到高級的區別是他們的副作用的大小。依次可以分爲:

  • 工廠模式
  • 構造函數模式
  • 原型模式
  • others

  同時,使用設計模式也可以優雅的解決 JavaScript 在 ES6之前都是沒有類的尷尬問題。

工廠模式

  工廠模式,顧名思義就是創建對象的一個工廠,工廠可以創造一類具有相似結構和功能的對象。這個模式的誕生也<span style="color: red">基本</span>解決了:

  • 創建多個對象的時候,需要大量重複代碼

先看下在這種設計模式下,應該如何組織我們的代碼:

function createPerson(name, age) {
    var obj = new Object();
    
    obj.name = name ;
    obj.age  = age;
    obj.say  = function() { console.log(this.name)}
    
    return obj;
}

var p1 = createPerson('ZhangSan', 12);
p1.say()  //ZhangSan
  • 這種叫工廠模式的設計模式:是指使用統一的方法(函數,因爲 Js 沒有類)來描述對象創建的細節
  • 把這個對象封裝起來,每次使用類似的對象都使用這個工廠函數來創建。
  • 它抽象了創建一個對象的過程。

當然,處於初級階段的 工廠模式, 一定有它處於初級階段的道理:

優勢:

  • 工廠函數可以解決創建多個類似功能對象的問題。

缺點:

  • 工廠模式無法解決對象的識別問題: 不知道對象是什麼類型的。
  • 使用工廠函數創建的對象,只有開發者是知道它的類型的(通過工廠函數變量名),但是程序仍然認爲它是一個普通的對象。
  • 每個對象都是通過工廠造就的全新的對象。

構造函數模式

  在 JavaScript 的開發中,經常會聽說和使用的一個詞語叫做構造函數,這裏的構造函數就是出自構造函數模式這一種設計模式。在長時間的傳承中,文化或者其他的名詞都會變成一種泛稱,所以人們常說的構造函數,有的時候指的是構造函數模式,有的時候指的是構造函數模式創造的對象中的構造函數方法(實例的 constructor )。

  構造函數是用來創建特定的類型的對象的。比如Js原生提供的ObjectArray。都是構造函數模式創建的原生構造函數。

慣例,看下在這種設計模式下,該如何組織代碼:

function Person (name, age) {

    this.name = name;
    this.age = age;
    this.say = function() {
        console.log(this.age);
    }
    
}

var p1 = new Person('ZhangSan', 12);
var p2 = new Person('LiSi', 22);
p1.say() // 12
p2.say() // 22
  • 使用構建函數模式和工廠模式創建對象的區別:

    1. 沒有明顯的創建對象的過程: (new Object()的過程)
    2. 直接將參數賦值給了 this。(因爲 沒有創建明顯的對象,就需要用 this 進行賦值)
    3. 沒有 return 。
    4. 構造函數的首字母大寫,這是代碼風格上的變化(爲了和其他的 OOM 語言的代碼風格保持一致)
  • 創建一個由構造函數創建的對象,需要使用 new Person() 進行創建。
  • 使用構造函數創建對象經歷了以下四個過程:

    1. 創建一個新對象
    2. 構造函數的作用域交給新對象。this指向新對象
    3. 執行內部代碼,給新對象增加屬性和方法
    4. 返回新對象
  • 由構造函數創建的對象稱爲這個構造函數的<span style="color: red">實例</span>,在實例中會存在一個 constructor 屬性,這個屬性指向創造它的構造函數。(證明自己從哪裏來)

    p1.constructor == Person;  // true
    p2.constructor == Person;  // true
  • 每一個實例都是可以被檢測出來的,檢測對象是否屬於某一個類型,可以使用 instanceof XX

        p1 instanceof Person // true
        p1 instanceof Object // true

圖片描述

知道了構造函數模式創建實例的過程和方法,下面介紹一些使用構造函數方法中,一些不爲人知的祕密(高級知識點):

  • 使用構造函數的時候常規操作是:使用操作符new。但是也可以直接當做一個普通的函數使用。
// Person 在上個 Demo
    
var p3 = Person('Person3', 33);
p3; // undefined;
p3.say(); // undefined;
    
Person('Person4', 44);
window.say(); // 44;
    
  • 如果把構造函數當做普通函數使用了,就不能構造實例了,即使構造了實例,也都是 undefined
  • 直接使用 構造函數當做普通函數使用, 屬性和方法會被添加到全局中(window/global)。
  • 當然也可以使用 call把 this 指向其他對象。(不瞭解 call,可以先忽略。call 是改變 this 指針的方式之一)。

  瞭解了高級用法之後,細心的孩子已經發現了構造函數方法作爲 Level 2的設計模式,一定有哪裏不對。其實很簡單,在構造函數創建的過程中,很好的解決了工廠模式創建對象不知道類型的問題(不知道自己從哪裏來)。在構想上,實例的屬性和方法應該都是唯一指向的,理想情況是都指向構造函數。但是差強人意的地方出現了:

  • 屬性都很好的指向了 構造函數。 this.name = name
  • 方法又自己偷摸的創建了新的對象(函數也是對象)。這就不符合理想的情況了,因爲這個時候:
p1.say === p2.say   // false
  • 原理也很清楚啊:this.say = function() {} 就是 this.say = new Function() 啊。

這個時候,一個構造函數的補救措施出現了:

var Person = function(name, age) {
    this.name = name;
    this.age = age;
    this.say = sayFunc;
}

var sayFunc = function() {
    console.log(this.age)
}
  • 使用額外的函數,將構造函數內的方法指向外部,這樣就保證了實例方式的相同。

<u>俗話說的好,補救措施終究是補救措施</u>:

  • 在構造函數存在大量的方法的時候,外部會存在數量巨多的補救方法。
  • 補救方法在構造函數的外部,毫無封裝性可言。
  • 外部方法也是一個普通的函數,也可以被其他方法或構造函數執行。
  • 外部方法被添加到 全局(window/global)上,性能浪費。如果在 window 上調用,this 就指向了 window。造成 this 指向混亂(誰調用,this 指向誰)

所以,構造函數的方法仍然是一個不完美的方法。但是在開發速度上,構造函數的設計模式還是有很大優勢的。

而且當你需要你的方法在全局被使用的時候,構造函數模式是最適合的,這也是 Object, Array原生構造函數出現的原因。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章