淺談 JS 建立對象的 8 種模式

1.Object 模式

var o1 = {};//字面量的表現形式
var o2 = new Object;
var o3 = new Object();
var o4 = new Object(null);
var o5 = new Object(undefined);
var o6 = Object.create(Object.prototype);//等價於 var o = {};//即以 Object.prototype 對象爲一個原型模板,新建一個以這個原型模板爲原型的對象
//上面6種都是同樣
//區別
var o7 = Object.create(null);//建立一個原型爲 null 的對象

2.工廠模式

//工廠方法1 經過一個方法來建立對象 利用 arguments 對象獲取參數設置屬性(參數不直觀,容易出現問題)
function createCar(){
    var oTemp = new Object();
    oTemp.name = arguments[0];//直接給對象添加屬性,每一個對象都有直接的屬性
    oTemp.age = arguments[1];
    oTemp.showName = function () {
        alert(this.name);
    };//每一個對象都有一個 showName 方法版本
    return oTemp;
}
createCar("tom").showName();//在 JS 中沒有傳遞的實參,實際形參值爲 undefined(這裏的 age 爲 undefined)
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判斷對象是否 Object 類或子類
//工廠方法2 經過傳參設置屬性(參數直觀明瞭)
function createCar(name,age){
    var oTemp = new Object();
    oTemp.name = name;//直接給對象添加屬性,每一個對象都有直接的屬性
    oTemp.age = age;
    oTemp.showName = function () {
        alert(this.name);
    };//每一個對象都有一個 showName 方法版本
    return oTemp;
}
createCar("tom").showName();
createCar("tim",80).showName();
alert(createCar("tom") instanceof Object);//true 判斷對象是否 Object 類或子類

3.構造器模式

//構造器方法1
function Car(sColor,iDoors){  //聲明爲構造器時須要將函數名首字母大寫
    this.color = sColor;      //構造器內直接聲明屬性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };//每一個 Car 對象都有本身的 showColor方法版本
    this.showDoor = function () {
        return this.doors;
    }
}

使用方法1的問題很明顯,沒辦法是 showDoor 方法重用,每次新建一個對象就要在堆裏新開闢一片內存空間.改進以下javascript

//構造器方法2
function showDoor(){      //定義一個全局的 Function 對象
    return this.doors;
}

function Car(sColor,iDoors){//構造器
    this.color = sColor;      //構造器內直接聲明屬性
    this.doors = iDoors;
    this.showColor = function(){
        return this.color;
    };
    this.showDoor = showDoor();//每一個 Car 對象共享同一個 showDoor 方法版本(方法有本身的做用域,不用擔憂變量被共享)
}

alert(new Car("red",2).showColor());//經過構造器建立一個對象並調用其對象方法

4.經過Function對象實現建立對象

咱們知道每聲明一個函數實際是建立了一個Function 實例 JS 函數.php

function function_name(param1,param2){alert(param1);}
//等價於
var function_name = new Function("param1","pram2","alert(param1);");
var Car2 = new Function("sColor","iDoors", "this.color = sColor;"+ "this.doors = iDoors;"+ "this.showColor = function(){ return this.color; }" );
alert(new Car2("blue",3).showColor());

5.prototype模式

  • 類經過 prototype 屬性添加的屬性與方法都是綁定在這個類的 prototype 域(實際爲一個 Prototype 對象)中,綁定到這個域中的屬性與方法只有一個版本,只會建立一次.java

  • 類的實例對象能夠直接像調用本身的屬性同樣調用該類的 prototype 域中的屬性與方法,類能夠經過調用 prototype 屬性來間接調用prototype 域內的屬性與方法.segmentfault

注意:經過類實例化出對象後對象內無 prototype 屬性,但對象可直接像訪問屬性同樣的訪問類的 prototype 域的內容,實例對象有個私有屬性proto,proto屬性內含有類的 prototype 域內的屬性與方法markdown

方法1
function Car3(){}//用空構造函數設置類名
Car3.prototype.color = "blue";//每一個對象都共享相同屬性
Car3.prototype.doors = 3;
Car3.prototype.drivers = new Array("Mike","John");
Car3.prototype.showColor = function(){
    alert(this.color);
};//每一個對象共享一個方法版本,省內存。

var car3_1 = new Car3();
var car3_2 = new Car3();

alert(car3_1.color);//blue
alert(car3_2.color);//blue
alert(Car3.prototype.color);//blue

car3_1.drivers.push("Bill");
alert(car3_1.drivers);//"Mike","John","Bill"
alert(car3_2.drivers);//"Mike","John","Bill"
alert(Car3.prototype.drivers);//"Mike","John","Bill"

//直接修改實例對象的屬性,解析器會先去找實例對象是否有這個屬性(不會去找實例對象的 _proto_ 屬性內的那些類的 prototype 屬性,而是直接查看這個實例是否有對應的屬性(與_proto_同級))
//若是沒有則直接給這個實例對象添加該屬性,但不會修改類的prototype域的同名屬性,既實例對象的_proto_屬性內的那些類 prototype 域屬性不會被修改
car3_1.color = "red";//car3_1對象內無名爲 color 的對象屬性,故將該屬性添加到該對象上

//解析器對實例對象讀取屬性值的時候會先查找該實例有無同名的直接屬性
//若是沒有,則查找__proto__屬性內保存的那些 當前類的 prototype 域的屬性
//有就返回,無則繼續查找是否有原型鏈中的對應的方法屬性
//有就返回,無則返回undefined
alert(car3_1.color);//red
alert(car3_2.color);//blue
alert(car3_2.color2);//undefined

//直接修改類的 prototype 域內的屬性,不會影響該類的實例對象的對象屬性,但會影響實例對象的_proto_屬性(_proto_屬性內存放的是類的 prototype 域的內容)
Car3.prototype.color = "black";
alert(car3_1.color);//red 該對象有同名的直接屬性,故不會去_proto_屬性內查找類的 prototype 域的屬性
alert(car3_2.color);//black 受影響

//直接修改實例對象的方法,解析器會先去找實例對象是否有這個方法(不會去找實例對象的 _proto_ 屬性內的那些類的 prototype 域的方法,而是直接查看這個實例是否有對應的方法(與_proto_同級))
//若是沒有則直接給這個實例對象添加該方法,但不會修改類的prototype域的同名方法,既實例對象的_proto_屬性內的那些類 prototype 域方法不會被修改
//car3_1對象內無名爲 showColor 的對象方法屬性,故將該方法屬性添加到該對象上
car3_1.showColor = function () {
    alert("new function");
}
//解析器對實例對象調用方法屬性的時候會先查找該實例有無同名的直接方法屬性
//若是沒有,則查找_proto_屬性內保存的那些 當前類的 prototype 域的方法屬性
//有就返回,無則繼續查找是否有原型鏈中的對應的方法屬性
//找到就返回,無則報錯

car3_1.showColor();//new function
car3_2.showColor();//blue
car3_1.abcd();//直接報錯

//直接修改類的 prototype 域內的方法屬性,不會影響該類的實例對象的方法屬性,但會影響實例對象的_proto_屬性(_proto_屬性內存放的是類的 prototype 域的內容)
Car3.prototype.showColor = function () {
    alert("second function");
}
car3_1.showColor();//new function 該對象有同名的方法屬性,故不會去_proto_屬性內查找類的 prototype 域的方法屬性
car3_2.showColor();//second function 受影響

能夠看出使用該方法雖說打打減小了內存的浪費,但依舊有問題,某個對象的屬性一旦改變,全部由該類實例化獲得的對象的proto內屬性值也會跟着變(實爲引用),改進以下函數

6.構造器方式與原型方式的混合模式

//每一個對象有專屬的屬性不會與其餘對象共享
function Car4(sColor,iDoors){
    this._color = sColor;//私有屬性變量名稱頭加下劃線標識
    this._doors = iDoors;
    this.drivers = new Array("Mike","John");//公有屬性標識
}
//全部對象共享一個方法版本,減小內存浪費
Car4.prototype.showColor = function () {
    alert(this._color);
};

var car4_1 = new Car4("red",4);
var car4_2 = new Car4("blue",3);

car4_1.drivers.push("Bill");

alert(car4_1.drivers);//"Mike","John","Bill"
alert(car4_2.drivers);//"Mike","John"

7.動態原型模式

function Car5(sColor,iDoors,iMpg){
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.drivers = new Array("Mike","John");

    //使用標誌(_initialized)來判斷是否已給原型賦予了任何方法,保證方法永遠只被建立並賦值一次
    if(typeof Car5._initialized == "undefined"){//由於這裏的標記是附加在類上,故若是後期直接對其進行修改,仍是有可能出現再次建立的狀況
        Car5.prototype.showColor = function () {//爲Car5添加一個存放在 prototype 域的方法
            alert(this.color);
        };
        Car5._initialized = true;//設置一個靜態屬性
    }
}
var car5_1 = new Car5("red",3,25);
var car5_2 = new Car5("red",3,25);

8.混合工廠模式

function Car6(){
    var oTempCar = new Object;
    oTempCar.color = "blue";
    oTempCar.doors = 4;
    oTempCar.showColor = function () {
        alert(this.color);
    };
    return oTempCar;
}
var car6 = new Car6();

因爲在 Car6()構造函數內部調用了 new 運算符,因此將忽略第二個 new 運算符(位於構造函數以外),
在構造函數內部建立的對象被傳遞迴變量car6,這種方式在對象方法的內部管理方面與經典方式(工廠方法)有着相同的問題.應儘可能避免ui

相關文章
相關標籤/搜索