1. 程式人生 > >js中建立物件的幾種方式

js中建立物件的幾種方式

前言

不管是哪門語言,千變萬化不離其宗,深入理解其本質,方能應用自如。對應到js,閉包,原型,函式,物件等是需要花費大功夫思考、理解的。本文穿插了js原型和函式的相關知識,討論了批量建立物件的幾種方式以及它們的優缺點。

正文

說起建立物件,最容易想到的便是通過物件字面量方式直接定義一個物件吧,但這種方式只能建立少量,單獨且相互間無聯絡的物件。若要批量建立物件,該如何?

工廠模式

工廠模式非常直觀,將建立物件的過程抽象為一個函式,用函式封裝以特定介面建立物件的細節。如下所示:

function createStudent(name,sex,grade){                                                         
    var
o = new Object(); o.name = name; o.sex = sex; o.grade = grade; o.sayName = function(){ console.log(this.name); } return o; } var s1 = createStudent('Claiyre','famale',1);

通俗地講,工廠模式就是將建立物件的語句放在一個函式裡,通過傳入引數來建立特定物件,最後返回建立的物件。
工廠模式雖然可以建立多個相似的物件,但卻不能解決物件標識的問題,即怎樣知道一個物件的型別

。建構函式模式應運而生。

建構函式模式

建構函式模式是java語言建立物件的通用方式。兩種語言用建構函式建立物件的方式略有不同,注意區別。
在JavaScript中沒有類的概念,函式即為一等公民,因此,不必顯式宣告某個類,直接建立建構函式即可,類的方法和屬性在建構函式中(或原型物件上)處理。建構函式模式的示例程式碼如下:

function Student(name,sex,grade){                                                   
    this.name = name;
    this.sex = sex;
    this
.grade = grade; this.sayName = function(){ console.log(this.name); } } var s2 = new Student('孫悟空''male',2);

細心的朋友一定發現了建構函式的函式名首字母是大寫的,而普通函式首字母則是小寫,這是眾多OO語言約定俗成的規定,雖然大多數情況下不大寫也不會報錯,但是為了程式碼的規範性和可讀性,還是應該將建構函式的首字母大寫,與普通函式區別開。
與工廠模式相比,用構造模式建立物件有以下幾點不同:

  • 沒有顯示地建立物件
  • 直接將屬性和方法賦給this物件
  • 沒有return語句

此外,還應注意到要建立Student的例項,必須要使用new操作符,建立的例項物件將有一個constructor(構造器)屬性,指向Person建構函式。呼叫建構函式建立物件經過了以下幾個過程:

  • 建立一個新物件
  • 將建構函式的作用域賦給新物件(因此this就指向了這個新物件)
  • 執行建構函式中的程式碼
  • 返回新物件(不需要顯式返回)

建構函式雖好用,但也不是沒有缺點。使用建構函式的主要問題是:每個方法都要在每個例項上建立一遍。在ECMAScript中,函式即物件,因此每定義一個函式,也就是例項化了一個物件。下面的例子證明了這個缺點。

var s3 = new Student('唐僧','male',3);
var s4 = new Student('白骨精','female',4);
s3.sayName();
s4.sayName();
console.log(s3.sayName == s4.sayName);

執行結果:

也就是說通過建構函式例項化的多個物件的方法,是多個不同的方法,但它們內部的程式碼以及實現的功能是相同的,這就造成了一定的資源浪費。
幸運的是,這個問題可以用原型模式來解決。

原型模式

js中,每個函式都有一個prototype屬性,它是一個指標,指向一個物件,叫做原型物件,原型物件包含了可以由特定型別的所有例項物件共享的屬性和方法。此外,這個物件有一個與生自來的屬性constructor,指向建立物件的構造方法。
使用原型模式可以讓所有的例項共享原型物件中的屬性和方法,也就是說,不必再建構函式中定義物件例項的資訊。用程式碼表示如下:

function Student_1(){

}
Student_1.prototype.name = 'Claiyre';
Student_1.prototype.sex = 'female';
Student_1.prototype.class = 5;
Student_1.prototype.sayName = function (){
    console.log(this.name);
}

var s5 = new Student_1();
s5.sayName();    //Claiyre
var s6 = new Student_1();
s6.sayName();    //Claiyre

一張圖勝過千言萬語,下圖清楚地闡釋了各個物件和原型物件間的關係:

瞭解過原型後,可以繼續在例項物件上增添屬性或方法:

s6.name = 'John';
s6.sayName();       //John

當要讀取某個物件的屬性時,都會執行一次搜尋,搜尋首先從物件例項本身開始,如果在例項中找到了這個屬性,則搜尋結束,返回例項屬性的值;若例項上沒有找到,則繼續向物件的原型物件延伸,搜尋物件的原型物件,若在原型物件上找到了,則返回原型上相應屬性的值,若沒有找到,則返回undefined。因此,例項物件屬性會覆蓋原型物件上的同名屬性,所以上面第二行程式碼輸出的是John。
- Object.getPrototypeOf(object)方法返回引數物件的原型物件。
- Object.keys(object)方法返回物件上課列舉的例項屬性。
原型中的所有屬性都是被所有例項所共享的,這種共享對於函式來說非常合適,對於包含基本值的屬性也說的過去(例項屬性會覆蓋原型同名屬性),但對於那些包含引用型別的屬性,可有大麻煩了

Student_1.prototype.friends = ['aa','bb'];

console.log('s6的朋友' + s6.friends);
s5.friends.push('cc');
console.log('s5的朋友' + s5.friends);
console.log('s6的朋友' + s6.friends);

執行結果:

問題來了,我們只想改變s5的朋友列表,但由於原型模式的共享本質,s6的朋友列表也隨之改變了。
因此,很少單獨使用原型模式。

組合使用建構函式和原型模式

建構函式模式用於定義例項屬性,原型模式則用於定義方法和共享的屬性。這種混合模式不僅支援向建構函式傳入引數,還最大限度地節約了記憶體,可謂是集兩模式之長。示例程式碼如下:

function Student(name,sex,grade){                                                   
    this.name = name;
    this.sex = sex;
    this.grade = grade;
}

Student.prototype.sayName = function(){
        console.log(this.name);
}
Student.prototype.school = 'Joooh school';

其他模式

除了以上幾種常見的模式外,批量建立物件的方式還有

  • 動態原型模式:僅在第一次呼叫建構函式時,將方法賦給原型物件的相應屬性,其他示例的處理方式同構造函式模式
  • 寄生建構函式模式:僅僅封裝建立物件的程式碼,然後再返回新建立的物件,仍使用new操作符呼叫
  • 穩妥建構函式模式:沒有公共屬性,只有私有變數和方法,以及一些get/set方法,用以處理私有變數。

結語

每種模式都有各自的優缺點,具體要使用哪種,還需結合實際場景,深入理解,靈活運用。