1. 程式人生 > >javascript面向對象系列——創建對象的5種模式

javascript面向對象系列——創建對象的5種模式

type屬性 cnblogs 對象共享 存在 最適 想是 HA 決定 ava

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

對象字面量

  一般地,我們創建一個對象會使用對象字面量的形式

  [註意]有三種方式來創建對象,包括new構造函數、對象直接量和Object.create()函數

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

  如果我們要創建大量的對象,則如下所示:

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 ...
*/

雖然對象字面量可以用來創建單個對象,但如果要創建多個對象,會產生大量的重復代碼

工廠模式

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

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‘);

工廠模式雖然解決了創建多個相似對象的問題,但沒有解決對象識別的問題,因為使用該模式並沒有給出對象的類型

構造函數模式

  可以通過創建自定義的構造函數,來定義自定義對象類型的屬性和方法。創建自定義的構造函數意味著可以將它的實例標識為一種特定的類型,而這正是構造函數模式勝過工廠模式的地方。該模式沒有顯式地創建對象,直接將屬性和方法賦給了this對象,且沒有return語句

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

構造函數拓展模式

  在構造函數模式的基礎上,把方法定義轉移到構造函數外部,可以解決方法被重復創建的問題

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‘

最後

  本文從使用對象字面量形式創建一個對象開始說起,創建多個對象會造成代碼冗余;使用工廠模式可以解決該問題,但存在對象識別的問題;接著介紹了構造函數模式,該模式解決了對象識別的問題,但存在關於方法的重復創建問題;接著介紹了原型模式,該模式的特點就在於共享,但引出了引用類型值屬性會被所有的實例對象共享並修改的問題;最後,提出了構造函數和原型組合模式,構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,這種組合模式還支持向構造函數傳遞參數,該模式是目前使用最廣泛的一種模式

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

javascript面向對象系列——創建對象的5種模式