閱讀目錄
前言
創建對象
工廠模式
構造函數模式
原型模式
組合使用構造函數模式和原型模式
結束語
前言
嚴格來講,JavaScript 並不是一門純面向對象的編程語言,他並沒有提供類,接口和抽象,以及訪問權限操作符的概念。沒辦法像C#、Java、C++那樣依托原有的特性容易的實現面向對象的特點。不過有劣勢也有會優勢,畢竟老天是公平的,ECMA小組在制定Javascript時,沒有提供這些特性,不過也提供了Javascript動態增減屬性的功能,我們可以利用這些功能模擬出真正面向對象語言中的部分面向對象功能。ECMA-262對對象的定義:“無序屬性的集合,其屬性可以包含基本值、對象或者函數。”這就等於說對象是一組沒有特定順序的值,對象的每個屬性或方法都有一個名字,而每個名字都會映射到一個值。其實可以想象成一個散列表,名值對之類的東西。Javascript的對象跟C#中的對象是一樣的,也是引用類型,(至於值類型和引用類型在內存中分配的情況可以參考我的另一篇文章:運行時的相互關系),這個類型可以是javascript定義的原型類型,也可以是開發人員自定義的類型。
創建對象
在javascript中創建對象最簡單的方式如:
var obj =new Object();
obj.author = "jwy";
obj.age = 23;
obj.display = function(){
document.write("author is " + this.author +",age is" + this.age);
};
這里創建了一個名為obj的對象,並添加了兩個屬性:author和age, 和一個display方法。雖然這種方式簡單,但是如果使用這種方式創建對象的話,會產生大量的重復代碼,每次創建一個對象,都要重新寫過這么多代碼。
工廠模式
工廠模式在java、C# 或其他語言中都是一種廣泛應用的設計模式,抽象了創建具體對象的過程,但是在javascript中沒有類的概念,所以我們可以這么模擬實現:
function createObj(author,age){
var o = new Object();
o.author = author;
o.age = age;
o.display = function(){
};
return o;
}
var o1 =createObj("Tom",22);
var o2 =createObj("Jack",23);
可以任意調用這個函數,每次都會返回一個包含制定屬性和方法的對象。這個方式解決了上面創建多個類似對象的問題,但是沒有解決識別一個對象類型的問題(識別對象類型在大部分地方都很有用)。
構造函數模式
在javascript中,函數只是一個變量名而已,是一個引用類型,通過new操作符可以用來創建特定類型的對象。
function Customer(name,age,tele){
this.name = name;
this.age = age;
this.tele = tele;
this.display = function(){
document.write(...);
}
}
var c1 = new Customer("Evada",2,87517522);
var c2 = new Customer("35",15,87517522);
在上面代碼中,看起來跟工廠模式的方法有點雷同,但還是有些許不同之處:
1、沒有顯式創建對象;
2、直接將方法和屬性賦給了this對象;
3、沒有return語句;
說明:this引用的是函數據以執行操作的對象,或者也可以說,this是函數在執行時所處的作用域。
如果是用來創建對象的,函數名一般遵循Pascal命名方式,如果是普通函數,或者說是對象的方法,則一般遵循Camel命名方式
要創建Customer的實例,就必須要使用new操作符,執行步驟如下:
1、 創建一個新對象;
2、 將構造函數的作用域賦給新對象(此時this就是當前的新對象了);
3、 執行構造函數中的代碼(添加屬性和方法)
4、 然后返回新對象,內存中表現就是在堆中給對象分配的地址,返回給在棧中定義的變量。此時該變量就引用了在內存中的對象了。
構造函數也是普通函數,任何函數只要通過new操作符調用,那它就可以作為構造函數,而任何函數,如果不通過new操作符調用,那它跟普通函數也不會有什么兩樣。
不足之處
每個方法都要在每個實例上重新創建一遍,例如,每個實例都有一個display的方法,但是這兩個方法不是同一個Function的實例,函數也是一個對象,這樣會在內存中重復創建很多相同的方法,所以可以像下面這樣解決這個問題:
function Customer(name,age,tele){
this.name = name;
this.age = age;
this.tele = tele;
this.display = display;
}
function display(){
document.write(...);
}
var c1 = new Customer("Evada",2,87517522);
var c2 = new Customer("35",15,87517522);
在上面代碼中,把display函數的定義移到了構造函數外面,然后在構造函數內部將display屬性設置成全局的display函數。這樣就只在內存中創建了一個display函數,但是問題又來了,這樣定義如果需要很多方法,那勢必要在全局定義很多函數,但是卻只能為某個構造函數所產生的各個實例所調用,這樣不僅代碼難看,而且毫無封裝性可言。
原型模式
在javascript中,每個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它是包含可以有特定類型的所有實例共享的屬性和方法。prototype就是通過調用構造函數而創建的那個對象的原型對象。好處:可以讓所有對象實例共享它所包含的屬性和方法。所以大可不必在構造函數中定義對象的屬性和方法,可以直接添加到原型中。
function Customer(){
}
Customer.prototype.name =’jwy’;
Customer.prototype.age = 23;
Customer.prototype.tele = ‘87517522’;
Customer.prototype.display = function(){
alert(this.name);
}
var c1 = new Customer();
c1.display(); //’jwy’
var c2 = new Customer();
c2.display();
alert(c1.display ==c2.display )//true
這里將方法和屬性都添加到了prototype對象中,構造函數中沒有代碼。但是新創建的對象還會具有相同的屬性和方法。而且這些屬性和方法是所有實例共享的,不像構造函數模式那樣,函數是一個實例一個實例特有的,雖然實現功能一樣。
原型不只在構造函數和創建對象中有重要作用,在javascript繼承方面也有舉足輕重的地位。
什么是原型
每個函數都有一個prototype屬性(也是一個對象),這個對象會自動獲得一個constructor屬性,這個constructor是一個指針,指向prototype屬性所屬函數。如:Customer.prototype.constructor à Customer ,用firebug 看下就清楚了。
默認情況下,原型屬性只會取得constructor屬性,其他實例方法是從Object繼承而來的,(點擊了解Object的方法),當實例化一個對象后,該對象內部將包含一個指針,指向構造函數的原型屬性,大部分實現中,這個指針的名字是__proto__,火狐開放了這個屬性,所以可以訪問,同樣可以通過firebug查看(用Chrome查看會更直觀),
這個__proto__是連接實例和構造函數的原型屬性,而不是實例和構造函數。下面用圖片展示一下實例和構造函數以及原型之間的關系,如圖:
Customer.prototype指向了原型對象,Customer.prototype.constructor指向了Customer。原型對象中不止包含constructor還包括后來添加的屬性。Customer每個實例都包含一個內部(__proto__)屬性,該屬性指向了Customer.prototype。如果調用方法和屬性,在構造函數中是沒有的,但是可以通過查找對象屬性的過程來實現。
在IE等無法訪問到內部__proto__屬性的瀏覽器中,可以通過isPrototypeOf方法來確定對象是否存在這種關系,也就是檢測實例是否指向構造函數的prototype屬性。如果是就返回true。
alert(Customer.prototype.isPrototypeOf(c1));//true
對象屬性搜索過程如下:
1、 給定屬性名稱,先從對象實例本身開始,如果在實例中找到了該屬性,則返回。
2、 沒有找到,則繼續搜索指針指向的原型對象,在原型中查找,如果找到則返回
如果實例屬性和原型屬性同名,則實例屬性會屏蔽原型屬性。
從Object中繼承過來的hasOwnProperty方法可以檢測一個屬性是否在實例中。只有在實例中才會返回true。
alert(c1.hasOwnProperty(‘name’));//false,name屬性在原型中
c1.name=”Scott”;//添加了實例屬性
alert(c1.hasOwnProperty(‘name’));//true,name屬性在實例中
我們還可以自己實現一個方法,用來檢測是屬性是在原型中的。
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
前半部分,確定屬性不在實例中,后半部分,確定屬性是實例所擁有的,則可推斷出屬性實在原型中的。
簡化原型語法
我們在給原型添加屬性和方法時每次都要寫Customer.prototype,多余代碼,很麻煩,更簡單的一個做法是,通過字面量重寫原型對象。
function Customer(){
}
Customer.prototype={
aame:’jwy’,
age:23,
tele:’87517522’,
display:function(){
document.write(…);
}
};
這里將原型設置為等於一個以對象字面量形式創建的新對象,結果是相同的,但是這里constructor不再指向Customer了,因為這里完全重寫了prototype對象,因此constructor屬性也就變成了新對象的constructor屬性(指向Object函數),不再指向Person函數。不過可以顯式設置constructor的值,使它重新指向Customer。
如果先聲明一個實例,再重寫prototype對象,結果就會跟所想的都不一樣,如下:
function Customer(){}
var c1=new Customer();
Customer.prototype={
Constructor:Customer,
name:’jwy’,
age:23,
…
};
c1.display();//error,undefine
這里先聲明一個實例,然后直接重寫了整個prototype對象,實例化實例時,實例有一個指向最初原型的指針,__proto__,但是后面重寫了整個原型,現在實例的__proto__還是依然指向最初的原型,后面重寫的原型所添加的屬性和方法,在當前實例中找不到,所以導致方法調用失敗。如圖所示:
原型對象存在的問題
原型模式少了傳參,所以每個實例都是相同的屬性值,但是如果屬性值是引用類型的,則每個實例不僅共享相同的屬性值,而且任意一個對象更改了,則其他對象也會收到更改后的值,因為引用類型的值是在堆中,屬性變量只是一個指針指向它而已。
function Customer(){}
var c1=new Customer();
Customer.prototype={
Constructor:Customer,
name:’jwy’,
age:23,
bank:[1,2,4],
…
};
var c1=new Customer();
var c2=new Customer();
c1.bank.push(33);
alert(c2.bank);//1,2,4,33
結合使用構造函數和原型模式
結合兩種模式,可以實現這樣的效果:構造函數定義實例屬性,原型模式定義實例共享的屬性和方法。
fnction Customer(name,age,tele){
this.name=name;
this.age=age;
this.tele=tele;
}
Customer.prototype={
constructor:Customer,
dsplay:function(){
document.write(…);
}
};
var c1=new Customer(‘jwy’,23,’87517522’);
var c2=new Customer(‘Scott,23,’1825081’);
這種模式,是目前應用得最廣泛的。
結束語
這里學習了javascript中對象的創建,通過分析各種不同的構建方法,權衡利弊。
如果覺得不錯的話,請點擊下推薦,(*^__^*) !!
轉載請注明出處:http://www.cnblogs.com/enshjiang/archive/2012/03/22/2411006.html