javaScript創建對象

一、對象

ECMA-262把對象定義爲:無序屬性的集合,其屬性可以包含基本值,對象或者函數。所以js中對象就是一組鍵值對。

面向對象的語言中,都是通過類的來創建任意多個具有相同屬性和方法的對象實例的。但是js中沒有類的概念,接下來我先通過一個例子來闡述js中沒有“類”的概念蘊含的哲學。這點會讓初學者很困惑,但是也正因爲放下了“類”的概念,js對象纔有了其他編程語言沒有的活力。事實上js中對象的“類”是從無到有,又不斷演化,最終消失於無形之中。

舉例:小蝌蚪找媽媽的故事,小蝌蚪在其自身類型不斷演化的過程中,逐漸變成了和媽媽一樣的“類”。

代碼:

複製代碼

<!DOCTYPE html><html><meta charset="utf-8"><head>
    <title>小蝌蚪找媽媽</title></head><body><script>
    var life={};//光溜溜的生命
    for(life.age=1;life.age<=3;life.age++)
    {        switch (life.age)
        {            case 1:
                life.body="卵細胞";//增加body屬性                life.say=function(){
                 console.log(this.age+this.body);
                };//新建say方法
                break;            case 2:
                life.tail="尾巴";//增加tail屬性                life.gill="腮";//增加gail屬性                life.body="蝌蚪";
                life.say=function(){
                    console.log(this.age+this.body+'-'+this.tail+","+this.gill);
                };                break;            case 3:                delete life.tail;//刪除tail屬性
                delete life.gill;//刪除gill屬性                life.legs="四條腿";//增加legs屬性                life.lung="肺";//增加lung屬性                life.body="青蛙";
                life.say=function(){
                    console.log(this.age+this.body+"-"+this.legs+","+this.lung);
                };                break;
        }
        life.say();//調用say方法,每次邏輯都會發生動態改變    }</script></body></html>

複製代碼

效果:

(1)js程序一開始產生了一個生命對象life,life誕生時只是個光溜溜的生命對象,沒有任何屬性和方法。

(2)第一次生命進化,life對象有了身體屬性body,並有了一個say方法,看起來是一個“卵細胞”。

(3)第二次生命進化,它又長出了“尾巴”和“腮”,有了tail和gill屬性,顯示它是一個“蝌蚪”。

(4)第三次生命進化,它的tail和gill消失了,但又長出了“四條腿”和“肺”,有了legs和lung屬性,從而最終變成了“青蛙”。

所以說,對象的“類”是從無到有,又不斷演化,最終消失於無形中。

“類”確實可以幫助我們對世界分類,但是我們思想不能被“類”束縛,如果生命開始就被規定了固定的“類”,就無法演化,蝌蚪就變不成青蛙。所以js中沒有“類”,類已化爲無形與對象融爲一體。這樣也更加貼近現實世界,不是嗎?

每個對象都是基於一個引用類型創建的,這個引用類型可以是原生類型也可以是開發人員定義的類型。

js沒有類,js對象就是一組鍵值對,接下來看看js中9種創建對象的方式。

js創建對象的方法的產生是一個迭代的過程,因爲已有方法的缺陷催生出新的方法。首先是早期js程序員經常使用也是最簡單的方法——通過Objec構造函數創建對象。

二、通過Object構造函數創建對象

代碼:

複製代碼

<script>
  var person=new Object();
    person.nam="lxy";
    person.age="22";
    person.job="Software Engineer";
    person.sayName= function () {
        alert(this.nam);
    }
    person.sayName();</script>

複製代碼

優點:簡單

三、通過字面量創建對象

早期JS開發人員經常使用new Object()創建對象,幾年後對象字面量稱爲創建對象的首選模式。

代碼:

複製代碼

<script>
  var person={
    name:"lxy",
    age:22,
    job:"Software Engineer",

    sayName:function(){
        alert(this.name);
    }
  };
    person.sayName();</script>

複製代碼

要注意一點就是每聲明一個鍵值對後面標點是“,”。

這些屬性在創建時都帶有一些特徵值(characteristic),JavaScript通過這些特徵值來定義它們的行爲。

對象字面量相對於Object構造函數代碼量少了一點點。但是這2種方法通過一個接口創建很多對象,會產生大量重複代碼。Don't Repeat Yourself!我們需要對重複的代碼進行抽象。工廠模式就是在這種情況下出現的。

四、工廠模式

工廠模式是軟件工程領域一種廣爲人知的設計模式,這種模式抽象了創建具體對象的過程。

 通過類來創建多個實例必然可以減少代碼重複,但是ECMAScript中無法創建類,所以就用函數來封裝以特定接口創建對象的細節。

代碼:

複製代碼

<script>
    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 lxy=createPerson("lxy",22,"Software Engineer");    var strangerA=createPerson("strangerA",24,"Doctor");
    lxy.sayName();
    strangerA.sayName();</script>

複製代碼

工廠模式減少了重複代碼,但是不能夠識別對象,所有實例都是object類型的

這時構造函數模式就出現了。

五、構造函數模式

 像Object和Array這樣的原生構造函數,在運行時會自動出現在執行環境中。我們可以創建自定義構造函數,從而創建特定類型的對象。

代碼:

複製代碼

<script>
    function Person(name ,age,job){        this.name=name;        this.age=age;        this.job=job;        this.sayName=function(){
            alert(this.name);
        }
    }    var lxy=new Person("lxy",22,"Software Engineer");    var strangerA=new Person("strangerA",24,"Doctor");
    lxy.sayName();
    strangerA.sayName();</script>

複製代碼

構造函數中首字母大寫,而非構造函數首字母小寫作爲區別。

通過new操作符來創建Person實例,這樣創建的實例都有一個constractor(構造函數)屬性,該屬性指向Person。

    alert(lxy.constructor==Person);//true
    alert(strangerA.constructor==Person);//true

lxy和strangeA是Person的實例,同時也是Object的實例。因爲所有的對象都繼承自Object。

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

構造函數也是函數,所以語法上可以像普通函數一樣去用,但是可以用並不代表應該用,還是以構造函數的方式用更合理。

構造函數的問題是,同一構造函數的不同實例的相同方法是不一樣的

alert(lxy.sayName==strangerA.sayName());//false

這個問題很好理解,因爲js中函數就是對象,每定義一個函數,也就是實例化了一個對象。

從代碼的角度可能理解的更深刻:

this.sayName=function(){alert(this.name)};與

this.sayName=new Function(alert(this.name));是等價的。

所以使用構造函數創建對象,每個方法在每個實例上都要重新實現一遍,一是耗資源,二是創建兩個或者多個完成同樣任務的Function沒有必要,三是有this在,沒必要在代碼執行前就把函數綁定到特定對象上。

所以,有一種方法是說把函數定義轉移到構造函數外部,代碼如下:

複製代碼

<script>
    function Person(name ,age,job){        this.name=name;        this.age=age;        this.job=job;        this.sayName=sayName;
    }    function sayName(){
        alert(this.name);
    }    var lxy=new Person("lxy",22,"Software Engineer");    var strangerA=new Person("strangerA",24,"Doctor");
    lxy.sayName();
    strangerA.sayName();</script>

複製代碼

把sayName()函數的定義轉移到構造函數外部,成爲全局的函數,構造函數內部把sayName賦爲爲全局的sayName。這樣sayName是一個指向外部函數的指針,因此lxy和strangeA就共享了 全局的sayName函數。

alert(lxy.sayName==strangerA.sayName);//true

但是這會有更糟糕的問題:全局作用域的函數只能被某個對象調用,這名不副實啊,會造成對全局環境的污染;更糟糕的是構造函數有多少個方法,就要定義多少個全局函數, 那構造函數就絲毫沒有封裝性可言了。

但是這樣的想法是可貴的,爲原型模式做了鋪墊,構造函數創建對象問題的解決辦法是原型模式。

六、原型模式

 原型模式就是把構造函數中方法拿出來的基礎上,爲了避免對全局環境的污染,再做了一層封裝,但是畢竟是一種新的模式,它封裝的更徹底,而且也不是把所有的函數都封裝,而是恰到好處的把構造函數中公共的方法和屬性進行了封裝。

代碼:

複製代碼

<script type="text/javascript">function Person(){

}
Person.prototype.name="lxy";
Person.prototype.age=22;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}     
     var lxy=new Person();
     lxy.sayName();     var personA=new Person();
     personA.sayName();
     alert(lxy.sayName()==personA.sayName());//true</script>

複製代碼

使用原型的好處是可以讓所有的實例共享它所包含的屬性和方法。完美的解決了構造函數的問題。因爲原型是js中的一個核心內容,其信息量很大,所以另作介紹,有興趣可看《javascript原型Prototype》

原型也有它本身的問題,共享的屬性值如果是引用類型,一個實例對該屬性的修改會影響到其他實例。這正是原型模式很少單獨被使用的原因。

複製代碼

<script type="text/javascript">function Person(){

}
Person.prototype.name="lxy";
Person.prototype.age=22;
Person.prototype.job="Software Engineer";
Person.prototype.friends=["firend1","friend2"];
Person.prototype.sayName=function(){
    alert(this.name);
}     
     var lxy=new Person();     var personA=new Person();
     alert(lxy.friends);//friend1,friend2     alert(personA.friends);//friend1,friend2     alert(lxy.friends==personA.friends);//true     lxy.friends.push("friend3");
     alert(lxy.friends);//friend1,friend2,friend3     alert(personA.friends);//friend1,friend2,friend3     alert(lxy.friends==personA.friends);//true</script>

複製代碼

七、構造函數和原型混合模式

 創建自定義類型的最常見方式,就是組合使用構造函數模式和原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義共享的方法和屬性。結果,每個實例都有一份實例屬性的副本,同時又共享着對方法的引用,最大限度的節省了內存。另外,這種混合模式還支持向構造函數傳遞參數,可謂是集兩種模式之長。

 代碼:

複製代碼

<script type="text/javascript">function Person(name,age,job){this.name=name;this.age=age;this.job=job;this.friends=["firend1","friend2"];
}

Person.prototype={
    constructor:Person,
    sayName:function(){
    alert(this.name);
}
}     
     var lxy=new Person("lxy",22,"Software Engineer");     var personA=new Person("personA",25,"Doctor");
     alert(lxy.friends);//friend1,friend2     alert(personA.friends);//friend1,friend2     alert(lxy.friends==personA.friends);//false     lxy.friends.push("friend3");
     alert(lxy.friends);//friend1,friend2,friend3     alert(personA.friends);//friend1,friend2</script>

複製代碼

實例屬性在構造函數中定義,而共享屬性constructor和共享方法sayName()在原型中定義。而修改一個實例的friends不會影響其他實例的friends,因爲它們引用不同數組,根本沒關係。

 這種構造函數與原型混合模式,是目前使用最廣泛、認同度最高的一種創建自定義類型的方法。可以說,這是用來定義引用類型的一種默認模式。其實原型就是爲構造函數服務的,配合它來創建對象,想要只通過原型一勞永逸的創建對象是不可取的,因爲它只管創建共享的屬性和方法,剩下的就交給構造函數來完成。

八、動態原型模式

 個人覺得構造函數和原型混合模式已經可以完美的完成任務了。但是動態原型模式的提出是因爲混合模式中用了構造函數對象居然還沒創建成功,還需要再操作原型,這在其他OO語言開發人員看來很彆扭。所以把所有信息都封裝到構造函數中,即通過構造函數必要時初始化原型,在構造函數中同時使用了構造函數和原型,這就成了動態原型模式。真正用的時候要通過檢查某個應該存在的方法是否有效,來決定是否需要初始化原型。

複製代碼

<script type="text/javascript">function Person(name,age,job){this.name=name;this.age=age;this.job=job;this.friends=["firend1","friend2"];if(typeof this.sayName!="function"){
    alert("初始化原型");//只執行一次    Person.prototype.sayName=function(){
        alert(this.name);
    }
}
}     
     var lxy=new Person("lxy",22,"Software Engineer");
     lxy.sayName();     var personA=new Person("personA",25,"Doctor");
     personA.sayName();</script>

複製代碼

使用動態原型時,不能使用對象字面量重寫原型。如果在已經創建了實例的情況下重寫原型,那麼就會切斷現有實例與新原型之間的聯繫

九、寄生的構造函數模式

 在前面幾種模式都不適用的情況下,適用寄生(parasitic)構造函數模式。寄生模式其實就是把工廠模式封裝在構造函數模式裏,即創建一個函數,該函數的作用僅僅是封裝創建對象的代碼,然後再返回新創建的對象,從表面看,這個函數又很像典型的構造函數。

代碼:

複製代碼

<script type="text/javascript">function Person(name,age,job){    var o=new Object();
    o.name=name;
    o.age=age;
    o.sayName=function(){
        alert(this.name);
    }    return o;
}var lxy=new Person("lxy",22,"Software Engineer");
lxy.sayName();</script>

複製代碼

除了適用new操作符使得該函數成爲構造函數外,這個模式和工廠模式一模一樣。

返回的對象和構造函數或者構造的原型屬性之間沒有任何關係,所以不能用instanceof,這種方式創建的對象和在構造函數外面創建的對象沒什麼兩樣。

十、穩妥的構造函數模式

穩妥的構造函數模式用顯式聲明的方法來訪問屬性。

和這種模式相關的有一個概念:穩妥對象。穩妥對象,指沒有公共對象,而且其方法也不引用this的對象。

穩妥對象最適合在一些安全的環境中(這些環境中會禁用this和new),或者防止數據被其他應用程序(如Mashup)改動時使用

穩妥構造函數遵循與寄生構造函數類似的模式,但有兩點不同:一是新創建對象的實例方法不引用this,而是不使用new操作符調用構造函數。

代碼:

複製代碼

<script type="text/javascript">function Person(name,age,job){    //創建要返回的對象
    var o=new Object();    //可以在這裏定義私有變量和函數
    
    //添加方法    o.sayName=function(){
        alert(name);
    }    return o;
}var lxy=new Person("lxy",22,"Software Engineer");
lxy.sayName();
alert(lxy.name);//undefinedalert(lxy.age);//undefined</script>

複製代碼

以這種模式創建的對象中,lxy是一個穩妥對象,除了使用sayName()方法外,沒有其他方法訪問name的值,age,job類似。

即使有其他代碼會給這個對象添加方法或數據成員,但也不可能有別的方法訪問傳入構造函數中的原始數據

與寄生構造函數模式類似,使用穩妥構造函數模式創建的對象與構造函數之間也沒有什麼關係,因此不能用instanceof操作符。

 

各種創建方法問題總結:

工廠模式:沒法知道一對象的類型。

構造函數:多個實例之間共享方法。

原型:屬性值是引用類型時,一個實例對該屬性的修改會影響到其他實例。

組合使用構造函數模式和原型模式:【推薦】構造函數模式用於定義實例屬性,每個實例都有一份實例屬性的副本;而原型模式用於定義共享的方法和屬性,每個實例同時又共享着對方法的引用

來源:愛創課堂

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