程式設計方法一般有兩種:面向對象程式設計(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在每個對象中都支援 一個
。不過希望大家能明白一點:這個連接配接存在于執行個體和構造函數原型對象之間,而不是存在于執行個體與構造函數之間。
接下來我們看一個例子:
//定義構造函數
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,而引用屬性他是存在于原型對象中,當為其添加元素時,将影響所有的執行個體對象。
我們用一組圖來直覺的解釋一下:
執行個體化後
改變非引用屬性
改變引用屬性
一般來說,我們希望執行個體擁有自己的全部私有屬性,包括引用屬性,是以我們很是單獨使用原型模式來建立對象。
方案六:組合構造模式
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).