|
原文地址:http://www.cnblogs.com/Truly/archive/2007/07/24/830013.html 前言 OO(面向?qū)ο?概念的提出是軟件開發(fā)工程發(fā)展的一次革命,多年來我們借助它使得很多大型應(yīng)用程序得以順利實(shí)現(xiàn)。如果您還沒有掌握并使用OO進(jìn)行程序設(shè)計(jì)和開發(fā),那么您無疑還停留在軟件開發(fā)的石器時(shí)代。大多數(shù)編程語言,尤其是近年問世的一些語言,都很好的支持了面向?qū)ο?,您可能?duì)此了如執(zhí)掌,但是一些語言在OO方面卻無法與其它高級(jí)語言相比,在這些語言上進(jìn)行面向?qū)ο蟪绦蛟O(shè)計(jì)和開發(fā)會(huì)有些困難,例如本文要討論的JavaScript。JavaScript是一門古老的語言,但是隨著近期Web2.0 技術(shù)的熱捧,這門語言又重新煥發(fā)出青春的光輝,借助于JavaScript客戶端技術(shù),我們的Web體驗(yàn)變得豐富而又精彩,為了設(shè)計(jì)和開發(fā)更加完善、復(fù)雜的客戶端應(yīng)用,我們必須掌握J(rèn)avaScript上的OO方法,這正是本文要討論的。 前幾天閱讀了MSDN的《使用面向?qū)ο蟮募夹g(shù)創(chuàng)建高級(jí) Web 應(yīng)用程序》一文,覺得還有些東西有必要繼續(xù)探討補(bǔ)充一下,就有了本文。 目錄 開始 開始
對(duì)象的聲明
var MyObject = {};
function MyObject()
{
blabalbla...
}
var MyObject = function(){balbalba...};
對(duì)于后兩種方法,我們還可以增加參數(shù),這樣就類似于一個(gè)帶參數(shù)的構(gòu)造器了,例如:
function MyObject(msg)
{
alert(msg);
}
var o = new MyObject("Hello world");
var MyObject = function(msg)
{
alert(msg + " again");
};
var o = new MyObject("Hello world!");
甚至我們可以使用字符串來生聲明函數(shù),這使得我們的程序更加靈活:
/*
* Function可以有多個(gè)入口參數(shù),最后一個(gè)參數(shù)最為方法體。
*/
var MyObject = new Function("msg","alert(msg);");
var o = new MyObject("Hello world!");
成員的聲明 在JavaScript中,要聲明一個(gè)對(duì)象的成員也非常簡(jiǎn)單,但是跟其它的高級(jí)程序仍然略有不同,請(qǐng)看下面的示例:
var MyObject = {
FirstName:"Mary",
LastName:"Cook",
Age:18,
ShowFullName : function(){
alert(this.FirstName + ' ' + this.LastName);
}
}
MyObject.ShowFullName();
或者使用字符串來聲明:
var MyObject = {
"FirstName":"Mary",
"LastName":"Cook",
"Age":18,
"ShowFullName" : function(){
alert(this.FirstName + ' ' + this.LastName);
}
}
MyObject.ShowFullName();
用字符串的聲明方式有諸多好處,這也是JavaScript中表示對(duì)象的一種特殊方式,像近年JSON概念的提出,將這種特殊方式提示到了一個(gè)新的高度,更多JSON的介紹請(qǐng)參加我以前的大作《深入淺出JSON》。 而在實(shí)際的程序設(shè)計(jì)中,這種方式在JavaScript的面向?qū)ο蟪绦蛟O(shè)計(jì)中我們通常用來映射數(shù)據(jù)類型,定義類似高級(jí)語言中的結(jié)構(gòu),集合,實(shí)體等,還常常用作定義靜態(tài)幫助器類,無需構(gòu)造而可以直接訪問成員方法。例如上面代碼中的MyObject.ShowFullName();
var DateTime = { Now : new Date(), "Show" : function() { alert(new Date());} };
alert(DateTime.Now);
// 等價(jià)于:
alert(DateTime["Now"]);
DateTime.Show()
// 等價(jià)于:
DateTime["Show"]();
提到方法調(diào)用,這里有一些知識(shí)需要知道,在JavaScript中,所有的對(duì)象的基類是Object,基類通過prototype定義了很多成員和方法,例如toString, toLocaleString, valueOf等
var obj = { "toString" : function() { return "This is an object."; } };
alert(obj);
我們注意到當(dāng)alert的時(shí)候,toString()方法被調(diào)用了,事實(shí)上,當(dāng)javascript需要將一個(gè)對(duì)象轉(zhuǎn)換成字符串時(shí)就隱式調(diào)用這個(gè)對(duì)象的toString()方法,例如alert或者document.write或者字符串需要進(jìn)行+運(yùn)算等等。參加下面示例代碼:
Date.prototype.toString = function(){ alert('This is called');}
var dt= new Date(new Date());
Date.prototype.toString = function(){ alert('This is called');}
var dt= new Date() + 1;
通過這個(gè)例子我們驗(yàn)證了這一點(diǎn),即使一個(gè)對(duì)象作為入口參數(shù)也可能會(huì)調(diào)用其toString方法。除了這一點(diǎn)外,該示例同時(shí)演示了如何覆蓋基類中定義的方法。
全局變量和局部變量
在JavaScript中,在全局上下文中聲明的變量作為全局變量,而在對(duì)象或方法內(nèi)部聲明的對(duì)象則作為本地變量。請(qǐng)參見下面的代碼:
var global = 1;
function mm()
{
var global = 2; // 聲明本地變量
alert(this.global); // 等價(jià)于alert(global);
}
mm();
alert(this.global); // 等價(jià)于alert(global);
上面例子我們可以看出本地變量和全局變量即使同名也不會(huì)出現(xiàn)沖突。 在JavaScript中this關(guān)鍵字是比較重要的一個(gè)特點(diǎn),它會(huì)隨調(diào)用對(duì)象而發(fā)生改變,始終與當(dāng)前對(duì)象的上下文保持一致,這里一個(gè)例子讓我們演示this并且同時(shí)繼續(xù)深入研究toString,首先我們使用構(gòu)造器方式創(chuàng)建一個(gè)對(duì)象,代碼如下:
function obj(params){
toString = function() { return 'This is an object.'; }
}
alert(new obj());
你會(huì)發(fā)現(xiàn)當(dāng)運(yùn)行這段代碼的時(shí)候,瀏覽器將會(huì)拋出一個(gè)錯(cuò)誤。
function obj(params){
aMethod = function() { return 'This is global method.'; }
}
alert(new obj()); // 正常執(zhí)行
function obj(params){
this.toString = function() { return 'This is local method.'; }
}
alert(new obj()); // 正常執(zhí)行
第一個(gè)函數(shù)聲明雖沒有使用this關(guān)鍵字,這時(shí)如果初始化對(duì)象那么將聲明一個(gè)全局方法aMethod。第二個(gè)函數(shù)聲明則為對(duì)象定義了一個(gè)自己的toString()方法。 當(dāng)分析這兩個(gè)函數(shù)的時(shí)候,你會(huì)注意到JavaScript的另一個(gè)特性,解釋執(zhí)行,所以
function obj(params)
{
aMethod = function()
{
return 'This is global method.';
}
}
alert(aMethod()); // 次句會(huì)報(bào)錯(cuò)
function obj(params)
{
aMethod = function()
{
return 'This is global method.';
}
}
new obj(); // 實(shí)例化的時(shí)候,聲明了全局變量阿Method()方法
alert(aMethod()); // 正常執(zhí)行
通過上面的例子我們知道關(guān)鍵字this非常重要,如果使用不當(dāng),可能會(huì)造成全局函數(shù)的改變。有一點(diǎn)需要記住,絕不要調(diào)用包含“this”(卻沒有所屬對(duì)象)的函數(shù)。否則,將違反全局命名空間,因?yàn)樵谡{(diào)用這樣的函數(shù)時(shí),“this”將引用全局對(duì)象,而這必然會(huì)給您的應(yīng)用程序帶來災(zāi)難。 如下面的例子,當(dāng)對(duì)象沒有定義this指定的函數(shù)(isNaN)時(shí),那么可能覆蓋全局的同名函數(shù),看一些代碼示例: 正確使用this的例子:
alert(isNaN(1)); // 全局函數(shù)isNaN
function obj(params)
{
this.toString = function(){return 'This is an object.';};
this.isNaN = function()
{
return 'not anymore';
};
}
var o = new obj(); // 正確使用方式,調(diào)用構(gòu)造函數(shù)
alert(o.isNaN(1)); // 此時(shí)obj定義中的this指向o這個(gè)實(shí)例而不是全局上下文
alert(isNaN(1)); // 全局函數(shù)未發(fā)生變化
錯(cuò)誤的例子:
alert(isNaN(1)); // 全局函數(shù)isNaN
function obj(params)
{
isNaN = function()
{
return 'not anymore';
}
}
obj(); // 錯(cuò)誤的使用方式,this指向全局上下文,全局函數(shù)isNaN被覆蓋
alert(isNaN(1)); // 全局函數(shù)發(fā)生改變
同時(shí)我們還注意到有一些全局函數(shù)則無法覆蓋,例如toString() 下面我們看JavaScript的一個(gè)很好用的方法:call
關(guān)于call的解釋: call方法可以用來代替另一個(gè)對(duì)象調(diào)用一個(gè)方法。call方法可以將一個(gè)函數(shù)的對(duì)象上下文從初始化的上下文改變?yōu)橛蓆hisObj指定的新對(duì)象。 可以這樣來理解: 我們定義了一個(gè)函數(shù)abc:
function abc()
{
alert(this.member1);
}
var obj = {member1:"Hello world!",show:abc};
var obj2 = {member1:"Hello world again!",show:abc};
obj.show();
// 也可以使用
abc.call(obj);
abc.call(obj2);
修改后的另一個(gè)版本:
member1 = 'test';
function abc()
{
alert(this.member1);
}
var obj = { member1:"Hello world", show:abc};
var obj2 = { member1:"Hello world again", show:abc};
obj.show();
// 也可以使用
abc.call(obj);
abc.call(obj2);
abc(); // 此時(shí)abc中的this指向了當(dāng)前上下文
每個(gè)函數(shù)都有call方法,上面的過程中我們看到用另一個(gè)對(duì)象代替調(diào)用顯示方法,并且注意到this在對(duì)象上下文中的改變。 通過上面基礎(chǔ)知識(shí)的研究,讓我們?cè)傧蚯翱绯鲆徊?,使?strong>call的特性來實(shí)現(xiàn)類的繼承,參見下面的示例:
// 統(tǒng)一類構(gòu)造器
function MyClassInitor()
{
this.base();
if(!this.mm)
{
alert('未定義成員函數(shù):mm()');
}
return this;
}
// 定義一個(gè)基類
function baseClass()
{
if(!this.tt) // 判斷該成員是否被繼承類覆蓋
{
this.tt = '基類成員';
}
}
// 從基類繼承
var obj = { member1:"Hello world", base:baseClass, gg:function(){ alert('I am an GG');}};
var obj2 = { member1:"Hello world again", base:baseClass,mm:function(name){alert('I am MM '+name + '.');}, tt:"覆蓋基類的tt成員"};
var o = MyClassInitor.call(obj);
var o2 = MyClassInitor.call(obj2);
alert(o.tt);
alert(o2.tt);
o2.mm('Mary');
雖然跟高級(jí)編程語言的語法有點(diǎn)不同,但是你必須了解JavaScript的語法特點(diǎn)。通過這個(gè)例子,我們什么分析了this和call的配合,但是實(shí)際進(jìn)行類繼承設(shè)計(jì)時(shí)往往不會(huì)采用此方法進(jìn)行實(shí)現(xiàn),后面我們介紹Prototype時(shí)再做詳細(xì)介紹。
命名空間 前面我們了解完類、對(duì)象的聲明,下面看一下Javascript中命名空間的處理,大家知道,在高級(jí)編程語言中我們非常熟練的使用命名空間來避免變量或方法名的沖突,那么同樣我們也可以在JavaScript中使用命名空間來為我們的類和方法進(jìn)行界定。在JavaScript中命名空間的聲明與其它高級(jí)語言略有不同,下面是一個(gè)命名空間聲明的示例:
var System = {};
var System.Web = {};
通過這兩行代碼我們就有了System和System.Web兩個(gè)命名空間,回想一下前面我們介紹的知識(shí),你很快可以發(fā)現(xiàn),這是兩個(gè)對(duì)象聲明語句。在JavaScript中,我們正是使用對(duì)象來表示命名空間的。但是你必須清楚一點(diǎn),由于JavaScript的特性,在實(shí)際應(yīng)用中,我們不能這么簡(jiǎn)單的來處理命名空間,因?yàn)槁暶髡Z句可能同時(shí)出現(xiàn)在多個(gè)地方或者多個(gè)js文件中,我們知道,在JavaScript中,最后聲明的變量會(huì)覆蓋前面同名的變量,因此通常我們要加一些判斷代碼來防止重復(fù)聲明,例如:
if(typeof System == 'undefined') var System = {};
這樣即使這段代碼在程序中重復(fù)出現(xiàn)多次我們也可以保證System對(duì)象只聲明一次。關(guān)于這一點(diǎn),大家如果深入研究過AjaxPro和其它很多大型JavaScript框架,會(huì)發(fā)現(xiàn)當(dāng)配合后端應(yīng)用程序的時(shí)候,它是多么的有用。例如AjaxPro的類型注冊(cè),關(guān)于AjaxPro可參見我另一篇文章《AjaxPro框架剖析》。 |
|
|