天天看點

javascript面向對象程式設計之建立對象

程式設計方法一般有兩種:面向對象程式設計(OOP)和過程化程式設計,他們兩者之間一個重要的差別就是OOP有類的概念,通過類可以任意建立多個具有相同屬性和方法的對象。而ECMAScript沒有類的概念,我們隻能通過他自身的一些特性去實作類似于java等面向對象語言的功能,正因為如此,就決定了javascript中的對象與基于類的語言中的對象有所不同。

今天,我要和大家分享的是如何基于js去建立一個對象。

方案一:利用Object()構造函數來建立一個對象

var person = new Object();  //關鍵字new可以省略
person.name = "尼古拉斯-大米";
person.age = ;
           

方案二:使用字面量方式建立Object

var person = {
    name: "尼古拉斯-大米",
    age: 
}
           

雖然通過以上兩種方式可以建立一個對象,但是他們均存在一個緻命的弊端:使用同一個接口來建立同不同的對象時,會産生大量的重複代碼,為了解決這一方式,我們可以使用大家所熟知的工廠模式來解決這一缺點。

方案三:工廠模式

function createComputer(brand,price) {
    var computer = new Object();
     computer.brand = brand;    //電腦品牌
     computer.price = price;   //電腦價格
     //擷取電腦資訊
     computer.getInfo = function() {
         return this.brand + "的價格是" + this.price;
     }
     return computer;
 }
 //執行個體化電腦對象
 var computer1 = createComputer("dell",);
 var computer2 = createComputer("mac",);
 //輸出dell的價格是5000
 console.log(computer1.getInfo()); 
 //輸出mac的價格是8000
 console.log(computer2.getInfo());
           

工廠模式雖然解決了多個相似對象重複執行個體化的問題,但是卻引入了另外一個問題:無法知道該對象的類型,随着js的發展,新的模式出現 了!

方案四:構造函數模式

function Computer(brand, price) {
     this.brand = brand;    //電腦品牌
     this.price = price;    //電腦價格
     //擷取電腦資訊
     this.getInfo = function () {
         return this.brand + "的價格是:" + this.price;
     }
 }
 //執行個體化電腦對象
 var computer1 = new Computer("dell",);
 var computer2 = new Computer("mac",);
 //輸出dell的價格是5000
 console.log(computer1.getInfo()); 
 //輸出mac的價格是8000
 console.log(computer2.getInfo());
 //輸出true ,可以清楚的知道computer1 是Computer的執行個體
 console.log(computer1 instanceof Computer);
           

通過代碼的對比,我們可以發現他與工廠模式的不同之處

  • 構造函數方法沒有顯示的建立對象
  • 直接将屬性和方法指派給this對象
  • 沒有renturn語句

另外構造函數模式還有一些規範如下:

  • 函數名和執行個體化構造名相同且大寫
  • 通過構造函數建立對象,必須使用new運算符

構造函數模式解決了重複執行個體化和對象識别問題,可以人們的需求是無限的 ,是以又出現了另外一個問題:每執行個體化一次,就會建立一個getInfo Function執行個體,但是他們實作的任務确完全相似,給代碼解析帶來了負擔,為了解決這個問題,我們可以做如下修改:

function Computer(brand, price) {
   this.brand = brand;    //電腦品牌
    this.price = price;    //電腦價格
    //擷取電腦資訊
     this.getInfo = getInfo;
}
function getInfo() {
    return this.brand + "的價格是:" + this.price;
}
           

将getInfo() 提到對象外做成全局之後解決了剛才的問題,看起來似乎很完美,但是卻忽略了一個問題,那就是:如果需要定義多個屬性方法,那麼就相應的要定義多少個全局函數,而且該方法除了被執行個體化後的對象調用之外,還可以在全局作用域内調用,隻是其中的this指向的是window而不是執行個體化後的對象,我們可以試着往深了思考,這樣的對象,還有什麼封裝性可言。

為了解決上述問題,另外一種方式就應運而生了。

方案五:原型模式

為了友善大家的了解,我們先來回顧一下有關原型的相關知識。

無論在什麼情況下,隻要我們建立了一個函數,那麼就會根據一組特定的規則為該函數建立一個prototype屬性,這個屬性指向函數的原型對象。在預設情況下,所有的原型對象都會自動的獲得一個constructor(構造函數)屬性,這個屬性包含一個指向構造函數的指針。同時當調用構造函數建立一個新執行個體後,改執行個體内部将包含一個指針,改指針指向構造函數的原型對象。ECMA-262中管這個指針叫做[[Prototype]],雖然無法通路改屬性,但是firefox,chrome和safari在每個對象中都支援 一個

javascript面向對象程式設計之建立對象

。不過希望大家能明白一點:這個連接配接存在于執行個體和構造函數原型對象之間,而不是存在于執行個體與構造函數之間。

接下來我們看一個例子:

//定義構造函數
function Computer(){};
//添加原型屬性和原型方法
Computer.prototype.brand = "mac";
Computer.prototype.model = ["mf840","mf839"];
Computer.prototype.getInfo = function(){
    return this.brand + "==>" + this.model;
}
//執行個體化電腦對象
var computer1 = new Computer();
var computer2 = new Computer();
console.log(computer1.getInfo()); //輸出:mac==>mf840,mf839
console.log(computer2.getInfo()); //輸出:mac==>mf840,mf839
//改變非引用屬性
computer1.brand = "dell";
console.log(computer1.getInfo()); //輸出:dell==>mf840,mf839
console.log(computer2.getInfo()); //輸出:mac==>mf840,mf839
computer1.model.push("mf841");
//改變引用屬性
console.log(computer1.getInfo()); //輸出:dell==>mf840,mf839,mf841
console.log(computer2.getInfo()); //輸出:mac==>mf840,mf839,mf841
           

從輸出結果我們可以發現當為computer1添加非引用屬性brand時,他并不影響原型對象中的prototype的值,是以computer2繼承了原型對象中的brand值,仍為mac,而引用屬性他是存在于原型對象中,當為其添加元素時,将影響所有的執行個體對象。

我們用一組圖來直覺的解釋一下:

執行個體化後

javascript面向對象程式設計之建立對象

改變非引用屬性

javascript面向對象程式設計之建立對象

改變引用屬性

javascript面向對象程式設計之建立對象

一般來說,我們希望執行個體擁有自己的全部私有屬性,包括引用屬性,是以我們很是單獨使用原型模式來建立對象。

方案六:組合構造模式

function Computer(brand,model){
    this.brand = brand;
    this.model = model;
}
Computer.prototype.getInfo = function(){
    return this.brand + "==>" + this.model;
}
//執行個體化電腦對象
var computer1 = new Computer("dell",["mf840","mf839"]);
var computer2 = new Computer("mac",["mf840","mf839"]);
console.log(computer1.getInfo()); //輸出:dell==>mf840,mf839
console.log(computer2.getInfo()); //輸出:mac==>mf840,mf839
computer1.model.push("mf841");
console.log(computer1.getInfo()); //輸出:dell==>mf840,mf839,mf841
console.log(computer2.getInfo()); //輸出:mac==>mf840,mf839
           

該模式中構造函數用于定義執行個體屬性,而原型模式用于定義方法和共享屬性。目前,這種方式是使用最為廣泛的方式。

方案七:動态原型模式

對于其他OO語言的開發人員來說,獨立的構造函數和原型,感覺有點兒詭異,為了解決這個問題,我們可以采用動态原型模式,把所有資訊都封裝在構造函數中。

function Computer(brand,model){
     this.brand = brand;
     this.model = model;
     //方法
     if(typeof this.getInfo != "function") {
         Computer.prototype.getInfo = function(){
             return this.brand + "==>" + this.model;
         }
     }
 }
 //執行個體化電腦對象
 var computer = new Computer("dell",["mf840","mf839"]);
 console.log(computer.getInfo()); //輸出:dell==>mf840,mf839
           

方案八:寄生構造函數模式

function SpecialArray(brand,price) {
    //建立數組
    var values = new Array();
    //添加值
    values.push.apply(values,arguments);
    //添加方法
    values.toPipedString = function() {
        return this.join("|");
    }
    //傳回數組
    return values;
}
var colors = new SpecialArray("red","blue");
console.log(colors.toPipedString()); //輸出:red|blue
           

這個模式可以在特殊的情況下,用來為對象建立構造函數,比如上例中:建立一個具有額外特殊方法的數組,由于不建議直接修改Array的構造函數原型對象,是以可以使用寄生構造函數模式。

方案九:穩妥構造函數模式

function createComputer(brand,price) {
    var computer = new Object();
    computer.brand = brand;    //電腦品牌
    //定義私有變量
    var privatePrice = price;
    computer.getPrice = function() {
        return privatePrice;
    }
    return computer;
}
var computer = createComputer("dell",);
console.log(computer.getPrice());  //輸出5000
console.log(computer.privatePrice); //undefined
           

以這種方式建立的對象中,除了使用getPrice()方法之外,沒有其他辦法通路privatePrice 的值。穩妥構造函數提供的這種安全性,使得他非常适合在某些安全執行環境使用(例如:ADsafe和Caja).

繼續閱讀