js創建對象的幾種方式(工廠模式、構造函數模式、原型模式)


1.工廠模式

  考慮到在 ECMAScript 中無法創建類,開發人員就發明了一種函數,用函數來封裝以特定接口創建對象的細節,如下面的例子所示:

  function createPerson(name,age,job){

    var o = new Object();

    o.name = name;

    o.age = age;

    o.job = job;

    o.sayName = function(){

      alert(this.name);

    }

    return o;

  }

  var person1 = createPerson('Grey',27,'Doctor');

  函數 createPerson()能夠根據接受的參數來構建一個包含所有必要信息的 Person 對象。可以無數次地調用這個函數,而每次它都會返回一個包含三個屬性一個方法的對象。工廠模式雖然解決了創建\多個相似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。 

  主要好處就是可以消除對象間的耦合,通過使用工程方法而不是new關鍵字。將所有實例化的代碼集中在一個位置防止代碼重復。

  工廠模式解決了重復實例化的問題 ,但還有一個問題,那就是識別問題,因為根本無法 搞清楚他們到底是哪個對象的實例。

2.構造函數模式

  ECMAScript中的構造函數可用來創建特定類型的對象,像Array和Object這樣的原生構造函數,在運行時會自動出現在執行環境中。此外,也可以創建自定義的構造函數,從而定義自定義對象的屬性和方法。使用構造函數的方法,既解決了重復實例化的問題,又解決了對象識別的問題。例如,可以使用構造函數模式將前面的例子重寫如下:

  function Person(name,age,job){

    this.name = name;

    this.age = age;

    this.job = job;

    this.sayName = function(){

      alert(this.name);

    }

  }

  var person1 = new Person("Nicholas", 29, "Software Engineer"); 

  var person2 = new Person('Grey',27,'Doctor');

  Person()中的代碼除了與 createPerson()中相同的部分外,還存在以下不同之處:

     沒有顯式地創建對象;
     直接將屬性和方法賦給了 this 對象;
     沒有 return 語句。

  按照慣例,構造函數始終都應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。

  要創建 Person 的新實例,必須使用 new 操作符。以這種方式調用構造函數實際上會經歷以下 4個步驟:

    (1) 創建一個新對象;
    (2) 將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象);
    (3) 執行構造函數中的代碼(為這個新對象添加屬性);
    (4) 返回新對象。

  在前面例子的最后,person1 和 person2 分別保存着 Person 的一個不同的實例。這兩個對象都有一個 constructor(構造函數)屬性,該屬性指向 Person,如下所示。

    alert(person1.constructor == Person); //true
    alert(person2.constructor == Person); //true

  對象的 constructor 屬性最初是用來標識對象類型的。但是,提到檢測對象類型,還是 instanceof 操作符要更可靠一些。我們在這個例子中創建的所有對象既是 Object 的實例,同時也是 Person的實例,這一點通過 instanceof 操作符可以得到驗證。

    alert(person1 instanceof Object); //true
    alert(person1 instanceof Person); //true
    alert(person2 instanceof Object); //true
    alert(person2 instanceof Person); //true

  創建自定義的構造函數意味着將來可以將它的實例標識為一種特定的類型;而這正是構造函數模式勝過工廠模式的地方。

  在這個例子中,person1 和 person2 之所以同時是 Object 的實例,是因為所有對象均繼承自 Object.

  前面例子中定義的 Person()函數可以通過下列任何一種方式來調用。

    // 當作構造函數使用
    var person = new Person("Nicholas", 29, "Software Engineer");
    person.sayName(); //"Nicholas"
    // 作為普通函數調用
    Person("Greg", 27, "Doctor"); // 添加到 window
    window.sayName(); //"Greg"
    // 在另一個對象的作用域中調用
    var o = new Object();
    Person.call(o, "Kristen", 25, "Nurse");
    o.sayName(); //"Kristen"

  這個例子中的前兩行代碼展示了構造函數的典型用法,即使用 new 操作符來創建一個新對象。

  接下來的兩行代碼展示了不使用new操作符調用Person()會出現什么結果:屬性和方法都被添加給window對象了。

  當在全局作用域中調用一個函數時,this 對象總是指向 Global 對象(在瀏覽器中就是 window 對象)。

  因此,在調用完函數之后,可以通過 window 對象來調用 sayName()方法,並且還返回了"Greg"。最后,也可以使用 call()(或者 apply())在某個特殊對象的作用域中調用Person()函數。這里是在對象o 的作用域中調用的,因此調用后o 就擁有了所有屬性和sayName()方法。

 3.原型模式

  我們創建的每個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。

  如果按字面意思來理解,那么prototype就是通過調用構造函數而創建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法。換句話說,不必在構造函數中定義對象實例的信息,而是可以將這些信息直接添加到原型對象中。例如:

  function Person(){

  }

  Person.prototype.name = 'Nicholas';

  Person.prototype.age = 29;

  Person.prototype.job = 'Software Engineer';

  Person.prototype.sayName = function(){

    alert(this.name);

  }

  var person1 = new Person();

  person1.sayName();//"Nicholas"

  

  var person2 = new Person(); 

  person2.sayName(); //"Nicholas" 

  alert(person1.sayName == person2.sayName); //true

  在此,我們將 sayName()方法和所有屬性直接添加到了 Person 的 prototype 屬性中,構造函數變成了空函數。

  即使如此,也仍然可以通過調用構造函數來創建新對象,而且新對象還會具有相同的屬性和方法。但與構造函數模式不同的是,新對象的這些屬性和方法是由所有實例共享的。換句話說,person1 和 person2 訪問的都是同一組屬性和同一個 sayName()函數。要理解原型模式的工作原理,必須先理解 ECMAScript 中原型對象的性質。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM