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