javascript面向對象系列第二篇——建立對象的5種模式

前面的話

  如何建立對象,或者說如何更優雅的建立對象,一直是一個津津樂道的話題。本文將從最簡單的建立對象的方式入手,逐步介紹5種建立對象的模式html

 

對象字面量

  通常地,咱們建立一個對象會使用對象字面量的形式安全

  [注意]有三種方式來建立對象,包括new構造函數、對象直接量和Object.create()函數,詳細狀況移步至此函數

var person1 = {
    name: "bai",
    age : 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
    }
};

  若是咱們要建立大量的對象,則以下所示:this

var person1 = {
    name: "bai",
    age : 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
    }
};
var person2 = {
    name: "hu",
    age : 25,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
    }
};
/*
var person3 ...
*/

  雖然對象字面量能夠用來建立單個對象,但若是要建立多個對象,會產生大量的重複代碼spa

 

工廠模式

  爲了解決上述問題,人們開始使用工廠模式。該模式抽象了建立具體對象的過程,用函數來封裝以特定接口建立對象的細節prototype

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('bai',29,'software Engineer');
var person2 = createPerson('hu',25,'software Engineer');

  工廠模式雖然解決了建立多個類似對象的問題,但沒有解決對象識別的問題,由於使用該模式並無給出對象的類型code

 

構造函數模式

  能夠經過建立自定義的構造函數,來定義自定義對象類型的屬性和方法。建立自定義的構造函數意味着能夠將它的實例標識爲一種特定的類型,而這正是構造函數模式賽過工廠模式的地方。該模式沒有顯式地建立對象,直接將屬性和方法賦給了this對象,且沒有return語句htm

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.jog = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");

  使用構造函數的主要問題是每一個方法都要在每一個實例上從新建立一遍,建立多個完成相同任務的方法徹底沒有必要,浪費內存空間對象

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.jog = job;
    this.sayName = function(){
        alert(this.name);
    };
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
//具備相同做用的sayName()方法在person1和person2這兩個實例中卻佔用了不一樣的內存空間
console.log(person1.sayName === person2.sayName);//false

構造函數拓展模式blog

  在構造函數模式的基礎上,把方法定義轉移到構造函數外部,能夠解決方法被重複建立的問題

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.jog = job;
    this.sayName = sayName;
}
function sayName(){
    alert(this.name);
};
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
console.log(person1.sayName === person2.sayName);//true

  如今,新問題又來了。在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡。並且,若是對象須要定義不少方法,就要定義不少全局函數,嚴重污染全局空間,這個自定義的引用類型沒有封裝性可言了

寄生構造函數模式

  該模式的基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象。該模式是工廠模式和構造函數模式的結合

  寄生構造函數模式與構造函數模式有相同的問題,每一個方法都要在每一個實例上從新建立一遍,建立多個完成相同任務的方法徹底沒有必要,浪費內存空間

function Person(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    return o;
}
var person1 = new Person("bai",29,"software Engineer");
var person2 = new Person("hu",25,"software Engineer");
//具備相同做用的sayName()方法在person1和person2這兩個實例中卻佔用了不一樣的內存空間
console.log(person1.sayName === person2.sayName);//false

  還有一個問題是,使用該模式返回的對象與構造函數之間沒有關係。所以,使用instanceof運算符和prototype屬性都沒有意義。因此,該模式要儘可能避免使用

function Person(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        console.log(this.name);
    };
    return o;
}
var person1 = new Person("bai",29,"software Engineer");
console.log(person1 instanceof Person);//false
console.log(person1.__proto__ === Person.prototype);//false

穩妥構造函數模式

  所謂穩妥對象指沒有公共屬性,並且其方法也不引用this的對象。穩妥對象最適合在一些安全環境中(這些環境會禁止使用this和new)或者在防止數據被其餘應用程序改動時使用

  穩妥構造函數與寄生構造函數模式類似,但有兩點不一樣:一是新建立對象的實例方法不引用this;二是不使用new操做符調用構造函數

function Person(name,age,job){
    //建立要返回的對象
    var o = new Object();
    //能夠在這裏定義私有變量和函數
    //添加方法
    o.sayName = function(){
        console.log(name);
    };
    //返回對象
    return o;
}
//在穩妥模式建立的對象中,除了使用sayName()方法以外,沒有其餘方法訪問name的值
var friend = Person("bai",29,"Software Engineer");
friend.sayName();//"bai"

  與寄生構造函數模式類似,使用穩妥構造函數模式建立的對象與構造函數之間也沒有什麼關係,所以instanceof操做符對這種對象也沒有什麼意義

 

原型模式

  使用原型對象,可讓全部實例共享它的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中

function Person(){
    Person.prototype.name = "bai";
    Person.prototype.age = 29;
    Person.prototype.job = "software Engineer";
    Person.prototype.sayName = function(){
        console.log(this.name);
    }
}
var person1 = new Person();
person1.sayName();//"bai"
var person2 = new Person();
person2.sayName();//"bai"
alert(person1.sayName == person2.sayName);//true

更簡單的原型模式

  爲了減小沒必要要的輸入,也爲了從視覺上更好地封裝原型的功能,用一個包含全部屬性和方法的對象字面量來重寫整個原型對象

  可是,通過對象字面量的改寫後,constructor再也不指向Person了。由於此方法徹底重寫了默認的prototype對象,使得Person.prototype的自有屬性constructor屬性不存在,只有從原型鏈中找到Object.prototype中的constructor屬性

function Person(){};
Person.prototype = {
    name: "bai",
    age: 29,
    job: "software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//false
console.log(person1.constructor === Object);//true

  能夠顯式地設置原型對象的constructor屬性

function Person(){};
Person.prototype = {
    constructor:Person,
    name: "bai",
    age: 29,
    job: "software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false

  因爲默認狀況下,原生的constructor屬性是不可枚舉的,更妥善的解決方法是使用Object.defineProperty()方法,改變其屬性描述符中的枚舉性enumerable

function Person(){};
Person.prototype = {
    name: "bai",
    age: 29,
    job: "software Engineer",
    sayName : function(){
        console.log(this.name);
    }
};
Object.defineProperty(Person.prototype,'constructor',{
    enumerable: false,
    value: Person
});
var person1 = new Person();
person1.sayName();//"bai"
console.log(person1.constructor === Person);//true
console.log(person1.constructor === Object);//false

  原型模式問題在於引用類型值屬性會被全部的實例對象共享並修改,這也是不多有人單獨使用原型模式的緣由

function Person(){}
Person.prototype = {
    constructor: Person,
    name: "bai",
    age: 29,
    job: "Software Engineer",
    friend : ["shelby","Court"],
    sayName: function(){
        console.log(this.name);
    }
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends);//["shelby","Court","Van"];
alert(person2.friends);//["shelby","Court","Van"];
alert(person1.friends === person2.friends);//true

 

組合模式

  組合使用構造函數模式和原型模式是建立自定義類型的最多見方式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,這種組合模式還支持向構造函數傳遞參數。實例對象都有本身的一份實例屬性的副本,同時又共享對方法的引用,最大限度地節省了內存。該模式是目前使用最普遍、認同度最高的一種建立自定義對象的模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["shelby","Court"];
}
Person.prototype = {
    constructor: Person,
    sayName : function(){
        console.log(this.name);
    }    
}
var person1 = new Person("bai",29,"Software Engineer");
var person2 = new Person("hu",25,"Software Engineer");
person1.friends.push("Van");
alert(person1.friends);// ["shelby","Court","Van"];
alert(person2.friends);// ["shelby","Court"];
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true

動態原型模式

  動態原型模式將組合模式中分開使用的構造函數和原型對象都封裝到了構造函數中,而後經過檢查方法是否被建立,來決定是否初始化原型對象

  使用這種方法將分開的構造函數和原型對象合併到了一塊兒,使得代碼更加整齊,也減小了全局空間的污染

  [注意]若是原型對象中包含多個語句,只須要檢測其中一個語句便可

function Person(name,age,job){
    //屬性
    this.name = name;
    this.age = age;
    this.job = job;
    //方法
    if(typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            console.log(this.name);
        };
    }
}
var friend = new Person("bai",29,"Software Engineer");
friend.sayName();//'bai'

 

最後

  本文從使用對象字面量形式建立一個對象開始提及,建立多個對象會形成代碼冗餘;使用工廠模式能夠解決該問題,但存在對象識別的問題;接着介紹了構造函數模式,該模式解決了對象識別的問題,但存在關於方法的重複建立問題;接着介紹了原型模式,該模式的特色就在於共享,但引出了引用類型值屬性會被全部的實例對象共享並修改的問題;最後,提出了構造函數和原型組合模式,構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,這種組合模式還支持向構造函數傳遞參數,該模式是目前使用最普遍的一種模式

  此外,一些模式下面還有一些解決特殊需求的拓展模式

  以上

相關文章
相關標籤/搜索