2.6 接口和"鴨子類型"
在軟件開發中,有很多時候我們希望指定某種行爲而不提供具體的實現。例如,在Shape 對象被正方形、圓形等對象子類化(subclassed)
的情況下,我們知道將無法得到一個不是某種特定類型的形狀。Shape 對象的基本概念是對於通用屬性的方便的抽象,而沒有真實世界中的等同物。
C++的虛類或者Java
的接口爲我們在代碼中定義這些概念提供了必要的機制。我們經常說接口在不同的軟件組件之間定義了一個契約。有了這個契約, Shape
處理庫的作者不必去考慮特定的實現,新的Shape 實現的作者不必去考慮任何庫代碼的內部實現或者任何同一接口的現有實現。
接口提供了好的概念分離,並且支撐了很多的設計模式。如果在Ajax 中使用設計模式,我們希理使用接口。JavaScript 沒有正式的接口概念,那麼我們如何來做呢?
最簡單的方法是非正式地定義契約,井且在接口的每一端簡單地依賴於開發者,明白他們正在做什麼。Dave Thomas
給這個方法起了一個迷人的名字"鴨子類型" (duck typing)
一一一如果它走起來像鴨子,叫起來也像鴨子,那麼它就是一隻鴨子。對於Shape 接口是類似的,如果它能夠計算面積和周長,那麼它就是-一種形狀。
假設我們希望將兩個形狀的面積加在一起。在Java 中,我們可以編寫:
public double addArea5(Shape s1,Shape s2){
return s1.getArea() + s2.getArea();
}
方法簽名明確禁止我們傳遞進除了形狀以外的任何其他東西。於是在方法體內,我們知道我們將會遵守這個契約。在JavaScript 中,方法的參數沒有類型,因此我們沒有這樣的保證:
function addArea5(s1,s2) {
return s1.getArea() + s2.getArea();
}
如果任何一個對象沒有附加的函數getArea (),我們將會得到-個JavaScript 錯誤。我們可以在調用它之前檢查函數是否存在:
function hasArea(obj){
return obj && obj.getArea && obj.getArea instanceof Function;
}
並且修改函數來使用這個檢查:
function addAreas(s1 , s2){
var total=null:
if (hasArea(s1) && hasArea(s2) {
total=s1.getArea()+s2.getArea() ;
}
return total;
}
實際上,使用JavaScript 反射,我們可以編寫一個通用的函數來檢查對象是否有一個特定名稱的函數:
function implements(obj , funcName) {
return obj && obj[funcName] && obj[funcName] instanceof Function;
}
或者,我們還可以將它附加到Object類的原型上:
Object.prototype.implements=function(funcName) {
return this && this[funcName] && this[funcName] instanceof Function;
}
這允許我們使用名稱來檢查特定的函數:
function hasArea(obj) {
return obj.implements("getArea");
}
甚至還可以測試對象是否遵守了一個完整的接口:
function isShape(obj) {
return obj.implements("getArea") && obj.implements("getPerimeter");
}
這給我們帶來了一定程度的安全性,雖然仍然不如我們在Java 中獲得的一樣多。例如,一個惡意對象(rogue object)
可以將getArea ()實現爲返回一個字符串而不是一個數字值。除非調用它,否則我們無法知道JavaScript
函數的返回值類型,因爲JavaScript
函數沒有預先定義的類型(我們甚至可以編寫一個函數,在每週工作日返回數字,而在週末返回字符串)。編寫一套簡單的測試函數來檢查返回類型是很容易的,例
如:
function isNum(arg) {
return parseFloat(arg) !=NaN;
}
NaN是"非數" (not a number) 的簡寫,是處理數字格式錯誤的一個特殊的JavaScript
變量。如果字符串以數字部分開始,這個函數實際上也會返回true. parseFloat() 和它的堂兄弟parseInt
()在可能的地方會竭盡全力抽取可以識別的數字。parseFloat ("64hectares") 將會得到64 ,而不是NaN。
我們可以更進一步來增強addAreas ()函數:
function addAreas(s1,s2) (
var total=null;
if (hasArea(s1) && hasArea(s2)) {
var a1=s1.getArea();
var a2=s2.getArea();
if (isNum(a1) && isNum(a2)) {
total=parseFloat(a1)+parseFloat(a2);
}
}
return total;
}
我在兩個參數上調用parseFloat(),以便正確地去掉字符串中的非數字部分。如果s1 返回值32 , s2 返回值64
hectares ,那麼addreas ()將會返回96 。如果我不使用parseFloat ,就將得到容易被誤解的值3264
hectares!
概括來說,鴨子類型使事情保持簡單,但是要求你相信開發團隊能夠明白所有的細節。鴨子類型在Ruby
社區中很流行,他們通常是一羣非常聰明的傢伙。隨着一個人從單個作者或者小型聯繫緊密的團隊轉移到大型的(包括分散的小團隊)項目中,這種信任會不可避免
地削弱。如果你希望在鴨子類型之上爲代碼添加一些檢查和平衡,本節爲你展示了應該從哪裏開始。
我們已經從對象的角度考察了這種語言。現在讓我們深入一些來考察那些散落在各處的函數,看看它們實際上是什麼。
3、方法和函數
在前面幾節和本文章的其餘部分我們定義了函數,並且調用它們。一名Java 或者C#程序員可能會假設它們類似於方法,是通過看起來有點古怪的語法來定義的。在本節中,我們將對函數進行更多的剖析,看看可以對它們做些什麼。
3.1 函數是一等公民
函數有點像Java 的方法。調用時,也有參數和返回值,但是有一個關鍵的區別: Java
方法天生捆綁在定義它的類上,不能與類脫離開而存在; JavaScript
函數是自由浮動的實體,自身就可以作爲正常對象(靜態的Java方法位於這兩者之間一一它們並沒有捆綁在任何對象實例上,但是仍然捆綁在類的定義上)。
一名兢兢業業的C系列語言程序員可能會認爲"啊,那麼它看起來像是C++ 中的一個函數指針。" 確實是這樣,但是這還不是函數的全部。
在JavaScript 中, Function
是咱一個內建的對象類型。就像期待的那樣,它包含可執行的代碼,可以調用,但是它也是Object 類的子孫,並且可以做JavaScript
對象可以做的任何事情,例如使用名稱來保存屬性。Function 對象上很可能(井且非常普遍)附加其他的Function 對象作爲它的方法。
我們已經看到如何獲得Function 對象的引用。更爲通常的是,我們希望在單獨一行中引用一個函數並且調用它,例如:
var result = MyObject.doSomething(x , y , z)
然而, Function 是第一等的對象,它也可以通過call ()方法(以及它的近親apply()方法)來執行:
var result = MyObject.doSomething.call(MyOtherObject , x ,y , z)
或者甚至是:
var result = MyObject['doSomething'] .call(MyOtherObject , x ,y , z)
Function.call()的第一個參數是在調用期間作爲函數上下文使用的對象,隨後的參數作爲函數調用的參數。apply(
)的工作方式略微不同,其中第二個參數是一個傳遞給函數調用的參數數組,允許以編輯方式調用那些參數列表長度不確定的函數,這帶來了巨大的靈活性。
這裏值得指出的是,JavaScript 函數的參數列表的長度不固定。使用比聲明更多或者更少的參數來調用戶一個Java
或者C#方法將會產生編譯期錯誤。而JavaScript僅僅忽略任何額外的參數,並且給缺少的參數賦值undefined。一個特別智能的函數可以通過
arguments屬性查詢它自己的參數列表,並且爲缺少的值分配明智的默認值,拋出一個異常或者採取任何其他的補救措施。這個特徵可以通過將獲取和設置
方法組合在單個函數中來演示,例如:
function area(value) {
if (value) {
this.area=value;
}
return this.area;
}
如果簡單地調用area() ,那麼value
是未定義的,所以沒有發生賦值,函數作爲getter方法來使用。如果傳入了一個值,函數就作爲setter方法來使用。這種技術被Mike
Foster的x庫廣泛使用。所以,如果你計劃使用這個庫,你很快就會熟悉這個習慣用法。
儘管如此,當我們對函數作爲第一等對象的獨立性加以利用時,它才變得真正有趣起來。
3.2 向對象附加函數
作爲一種函數式語言,JavaScript 允許脫離任何對象來定義函數,例如:
function doSomething(x , y , z) { ... }
函數還可以使用內嵌的方式來定義:
var doSomething=function(x , y , z) { ... }
作爲向面向對象方法的妥協,函數可以附加到對象上,這使得函數有了Java 或者C#的方法的"外
表"。實現方法不止一種。
我們可以將預先定義的函數附加到預先定義的對象上(在這種情況下,只有該對象可以調用這個函數,而不是從相同原型繼承的任何其他對象都可以):
myObj.doSomethingNew=doSomething;
myObj.doSomethingNew(x , y , z) ;
我們也可以添加函數使得類的每一個實例都能訪問它們:在構造函數中將函數(預先定義的或者以內嵌方式聲明的)添加到新的對象上,就像在2.2 節看到的那樣:或者將函數附加到原型上。完成之後,函數仍然不是非常牢固地附加在對象上,我們下面會看到這一點。
3.3 從其他對象借用函數
函數成爲一等對象,極大地改變了語言的能力。而且,當爲GUI 事件編寫處理代碼的時候,理解這些改變是很重要的,所以大多數Ajax 程序員覺得有必要理解它。
那麼這些新的能力是什麼呢? 首先,一個對象可以借用另外一個對象的函數,並且通過自身來調用它。讓我們定義一個類來表示分類學意義上的樹:
function Tree(name,leaf,bark){
this.name=name:
this.leaf=leaf;
this.bark=bark;
}
下面,我們將添加一個函數,這個函數提供了對樹的簡短描述:
Tree.prototype.describe = function() {
return this.name+": leaf="+this.leaf+" , bark="+this.bark;
}
如果現在實例化一個Tree 對象,並且請求它描述自己,將得到一個結果可以預見的響應:
var Beech=new Tree ("Beech" , "green , serrated edge" , "smooth");
alert(Beech.describe()) ;
警告相互將顯示文本: Beech: leaf=green , serrated edge,bark=smooth。到目前爲止一切順利。現在讓我們定義一個類來表示狗:
function Dog(name , bark) {
this.name=name;
this.bark=bark;
}
併爲Dog 類創建一個實例:
var Snowy=new Dog ( "Snowy" , "wau! wau!");
Snowy 希理向我們展示它的叫聲,儘管我們已經爲它定義了叫聲,但是並沒有定義表達函數。然而,它可以劫持Tree類的函數:
var tmpFunc=Beech.describe;
tmpFunc.call(Snowy) ;
記住, Function.call() 的第一個參數是上下文對象,即特殊變量this
將被確定爲的對象。前面的代碼將生成一個警告框,顯示文本Snowy: leaf=undefined, bark=wau! waul
。很好,這比可憐的小狗什麼都沒有要好。
那麼,這裏發生了什麼事情? 狗怎麼能夠調用實際上屬於樹的函數呢?答案是函數並不屬於那棵樹。儘管其中插入了this
引用,將函數賦給Tree
原型形成的綁定僅僅是因爲這樣能夠使用更短的符號MyTree.describe()來調用它。在內部實現中,函數保存爲一段每次調用時就會求值的文本,
因此this 的含義在一次調用和下一次調用中並不相同。
借用函數是我們可以在自己的代碼中使用的一個靈巧的技巧,但是在產品級代碼中,我們更樂意看到某人自願爲Snowy 實現一個bark() 方法。討論這個行爲真正的原因是當你在編寫事件處理代碼時, Web 瀏覽器將以後臺的方式自動幫你做這些事情。
3.4 Ajax 事件處理和函數上下文
對於鼠標和鍵盤事件的特殊種類來說,Ajax事件處理函數和大多數GUI 工具包語言(GUI toolkit language)
中的事件處理函數差不多是一樣的,我們的例子使用了OnClick 處理函數,它在鼠標在一個可視的元素上點擊的時候觸發。對於DHTML
事件處理的完整討論超出了本文的範圍,但是讓我們在這裏花一點時間,關注一個經常使粗心的開發者犯錯的特定問題。
事件處理函數要麼當作HTML 標記的一部分來聲明,例如:
<div id='myDiv' οnclick='alert:alert(this.id) '></div>
要麼使用編程方式來聲明,例如:
function clickHandler() { alert(this.id); }
myDiv.οnclick=clickHandler;
注意,在編程方式的情況下,我們傳遞的是一個對Function 對象的引用(在clickHandler後面沒有())。當在HTML 中聲明函數時,我們有效地以內嵌方式聲明瞭個匿名函數,等同於:
myDiv.οnclick=function(){ alert(this.id); }
注意,在兩種情況下,都沒有爲函數分配參數,也沒有任何方式可以伴隨鼠標的點擊傳遞參數。然而,當點擊DOM 元素的時候. Event
對象作爲了函數調用的參數,元素本身作爲上下文對象。知道這一點可以大大減少麻煩和困惑,特別是當你在編寫面向對象的代碼時。混亂的關鍵之源在於DOM
節點總是作爲上下文來傳遞,甚至在函數附加到一個不同對象的原型上的時候。在下面的例子中,我們定義了一個簡單的對象,該對象帶有一個它所知道的可視
GUI 元素的事件處理函數。我們可以把這個對象看作是MVC 術語中的模型,事件處理函數看作是控制器,DOM 元素是視圖。
function MyObj (id , div) {
this.id=id;
this.div=div;
this.div.οnclick=this.clickHandler;
}
構造函數接受一個內部的ID和一個分配了 onclick處理函數的 DOM元素作爲參數。我們像下面這樣定義事件處理函數:
MyObj.prototype.clickHandler=Function(event){
alert (this. id) ;
}
於是,當點擊 GUI元素的時候,它將顯示那個對象的 ID,這個判斷對嗎?事實上,它並沒有這樣做,因爲 MyObj.clickHandler函數將會被瀏覽器借用(就像在前面一節中任性的小狗從樹對象上借用了一個方法一樣),並且在那個元素的上下文中調 用,而不是在模型對象的上下文中。因爲元素碰巧也有一個內建的 id屬性,它將會顯示一個值,並且依賴於命名約定,這個值甚至可以與模型對象的ID相同,這會使你的誤解持續到未來的某個時間。如果希望事件處理函數引用 它附加到的模型對象,我們高要使用另外一種方式將那個對象的引用傳遞進來。我遇到過兩種習慣做法可以做這件事。從我的觀點來看,一種做法明顯要比另一種優 越,但是我使用另一種方法編程已經很多年,它一樣可以工作。本文的一個目標是爲習慣上採用的模式(和反模式)命名,所以兩種方法我們在這裏都會介紹。
1.使用名稱引用模型
在這種解決方案中,我們給模型對象的每一個實例分配全局唯一的ID,並且維護一個通過ID來引用的這些對象的全局數組。假設我們得到了一個對 DOM元素的引用,隨後就可以通過使用ID的一部分作爲查找數組( lookup array)的鍵,來引用它的模型對象。圖 1展示了這種策略。
在這種方法中,爲每一個元素生成唯一的ID 是一種系統開銷,但是ID 的生成可以自動地完成。例如,如果在Web
服務器上生成代碼,我們可以使用數組長度作爲這個鍵的一部分,也可以使用數據庫的主鍵作爲鍵。作爲一個簡單的例子,我們創建了一個類型爲MyObj
的對象,它有一個可以點擊的標題欄,調用函數rnyObj.foo()。
全局數組如下:
var MyObjects=new Array(};
構造函數如下,它在數組中註冊模型對象:
function MyObj(id) {
this.uid=id;
MyObjects[this.uid]=this;
this.render() ;
}
MyObj 對象的方法如下,其中有一些有意思的操作。我們希望當點擊標題欄的時候調用這個方法:
MyObj.prototype.foo=function(){
alert( 'foooo!!!'+this.uid);
}
對象的render()方法如下,它創建不同的DOM節點:
MyObj.prototype.render=function(){
this.body=document.createElement("div");
this.body.id=this.uid+"_body";
this.titleBar=document.createElement("div") ;
this.titleBar.id=this.uid+'_titleBar";
this.titleBar.οnclick=fooEventHandler;
}
當在這個模型對象的視圖中構建任何DOM 節點時,我們爲它們分配了一個包含了模型對象ID的ID 值。
注意,我們引用了函數FooEventHandler(),並且將它設置爲標題欄DOM 元素的onclick屬性:
function fooEventHandler(event){
var modelObj=getMyObj(this.id);
if (modelObj) { modelObj.foo(); }
}
事件處理函數需要找到MyObj 的實例,以便調用它的foo( )方法。我們提供了一個發現方法:
function getMyObj(id) {
var key=id.split("_")[0];
return MyObjects[key];
}
事件處理函數有一個到DOM 節點的引用(即this) ,可以從它的id 屬性中抽取出個鍵,用來從全局數組獲得模型對象。
要介紹的就是這些。我使用名稱引用模型(Reference Model By Name)
的方法好幾年了,一直感覺不錯,這方法很好使,但是還有另一個更加簡單、更加清晰的方法,這種方法不會給你的DOM 樹加上很多冗長的ID。
(實際上,我從未確定這究竟是好還是不好。它肯定浪費了內存,但是也使得在Mozilla DOM 檢查器中進行調試非常容易。)
2. 向DOM 節點附加模型
在DOM 事件處理的第二種方法中,所有的工作都使用對象引用來完成,而不是使用字符串,也不再需要全局查找數組。這是本文所採用的方法,圖2 展示了這種方法。
這種方法相當大地簡化了事件處理函數的工作。模型對象的構造函數不再需要專門的ID處理. foo()方法和前面定義的一樣。當構造DOM
節點時,我們發掘了JavaScript 可以附加任意屬性到任意對象上的動態能力,並且將模型對象直接附加在接收事件的DOM 節點上面:
MyObj.prototype.createView=function() {
this.body=document.createElement("div") ;
this.body.modelOb)=this;
this.titleBar=document.createElement("div");
this.titleBar.modelObj=this;
this.titleBar.οnclick=FooEventHandler;
}
當編寫事件處理函數的時候,我們可以獲得一個到後端模型的直接引用:
function fooEventHandler(event){
var modelObj=this.modelObj;
if (modelObj) { modelObj.foo(); }
}
沒有發現函數,也沒有全局查找表一一它是非常簡單的。
然而,最後還有一句警告。當使用這種模式的時候,我們在DOM 變量和非DOM 變量之間創建了循環引用。根據Web
瀏覽器有關的傳言,在目前某些流行的瀏覽器中,這對於垃圾收集是很不利的。如果正確使用這種模式,是可以避免內存開銷的,但是我還是建議在實現這種模式
(即向DOM 節點附加模型)之前學會如何降低內存使用率。
理解JavaScript 函數如何定義它的上下文,可以幫助我們爲瀏覽器事件模型開發一種優雅的、可重用的解決方案。函數在上下文之間切換的能力可能在最初會把人摘糊塗,但是理解其背後的模型可以幫助我們更好地使用它。
關於JavaScript 函數我們需要理解的最後一點,是語言創建閉包的能力。Java 和C#沒有閉包的概念,但是一些Java 和.NET
腳本語言(例如Groovy 和Boo) 支持閉包,並且C# 2.0 也將會支持閉包。我們來考察一下什麼是閉包,以及如何使用它們。
3.5 JavaScript 中的閉包
Function
對象本身是不完整的一一爲了調用它,我們需要傳進一個上下文對象以及一組參數(可能是一個空的集合)。在最簡單的情況下,閉包可以看作是捆綁了運行所需所
有資源的Function 對象。閉包在JavaScript 中是隱式而非顯式創建的。沒有構造函數new Closure ( )
,也沒有方法來得到閉包對象的句柄。創建閉包就像在代碼塊中(例如在另一個函數中)聲明函數並且使得該函數在代碼塊之外可以獲得一樣簡單。
這從概念上聽起來有點怪異,但是當我們考察一個例子的時候,它是足夠簡單的。我們定義一個簡單對象來代表一個機器人,並且記錄每個機器人創建時的系統時鐘時間。我們可以像這樣編寫構造函數:
function Robot(){
var createTime=new Date();
this.getAge=function(){ //在函數中聲明或定義另一個函數即創建了一個閉包
var now=new Date();
var age=now-createTime;
return age;
}
}
(所有的機器人都是相同的,所以我們不必費事通過構造函數給它們分配名稱或者任何其他東西。)通常,我們將createTime作爲成員屬性記錄下來,即這樣寫:
this.createTime=new Date();
但是在這裏我們故意將它創建爲本地變量(var createTime = new
Date()),其作用域限制在調用它的塊中(即構造函數中)。在構造函數的第二行,我們定義了函數getAge()。注意,這裏我們在一個函數中定義了
另一個函數,內部函數使用的本地變量createTime屬於外部函數的作用域。通過做這件事(沒有做任何其他事情),我們實際上創建了閉包。如果我們定
義了一個機器人,在頁面加載完成的時刻詢問它的"年齡"。
var robbie=new Robot();
window.οnlοad=function(){
alert(robbie.getAge());
}
它可以工作並且給了我們一個在10~50 毫秒之間的值,即腳本第一次執行和頁面加載完成之間的時間差。儘管我們已經將createTime聲明爲構造函數作用域的本地變量,只要仍然要引用機器人Robbie,它就不能被垃圾收集,因爲它綁定在了閉包中。
閉包僅僅當內部函數創建在外部函數之內的時候纔可以工作。如果我們重構這段代碼來預先定義getAge 函數,並且在所有的機器人實例之間共享它,就像這樣:
function Robot() {
var createTime=new Date();
this.getAge=roboAge;
}
function roboAge(){
var now=new Date();
var age=now-createTime;
return age;
}
那麼就沒有創建閉包,且我們得到了一個錯誤信息,告訴我們createTime沒有定義。創建閉包非常容易,以致於太容易意外地創建。因爲閉包與本地變量
綁在一起,使得它們不能被垃圾器收集。例如,如果也以這種方式捕獲DOM 節點,那麼無意中創建的閉包隨着時間的延長將會造成嚴重的內存泄漏。
創建閉包的最常見場合,是將事件處理回調函數綁定到事件源上。就像在3.4節中討論的,回調函數由使用一個上下文和一組有時候沒有什麼用處的參數來調用
(回調函數的參數是開發者無法指定的,所以有時候沒有什麼用。)。我們介紹了一種用來將額外的引用(模型對象)附加到生成事件的DOM
元素上的模式,允許通過DOM 元素來獲得模型。閉包提供了一種做這件事情的替代方法,就像這裏演示的那樣:
MyObj.prototype.createView=function(){
this.titleBar=document.createElement("div") ;
var modelObj=this; //這裏創建了閉包
this.titleBar.οnclick=function() {
fooEventHandler.call(modelObj) ;
}
}
我們定義的匿名的onclick 處理函數給本地聲明的變量modelObj 加了一個引用,於是在它的周圍創建了閉包,允許調用函數時確定modelObj 的值。注意閉包只能確定本地變量的值,而不是那些通過this引用的變量。
我們在ContentLoader(一個封裝了Http網絡功能的對象) 對象中使用了這種方法,因爲IE中提供的onreadystatechange回調函數返回window 對象作爲函數上下文。因爲window 是在全局範圍內定義的,我們沒有辦法知道是哪一個ContentLoader 對象的readyState 發生了變化,除非通過閉包傳遞一個到相關加載對象的引用。
我對水平一般的Ajax 程序員的建議是:如果有替代方法,就避免使用閉包。如果使用原型來給自定義對象類型分配函數,那麼你就不會重複創建函數,也不會創建閉包。讓我們重寫Robot類來遵循這個建議:
function Robot() {
this.createTime=new Date();
}
Robot.prototype.getAge=function() {
var now=new Date();
var age=now-this.createTime;
return age;
}
函數getAge()只定義一次,並且因爲它附加在原型上,可以被創建的所有Robot 對象訪問。閉包有它們的用處,但是最好把它們看作是一種高級的技術。如果你確實希望更深入地探索閉包,請看5.資源相關內容。
4、小結
在本文中,我們帶領你領略了JavaScript 語言的一些奇怪和更加有趣的特徵。我們有兩個目的:首先是展示這種語言強大的表現能力;其次是爲粗心者指出幾個陷阱,其中以面向對象風格的方式來思考會得到不理想甚至是危險的代碼。
我們考察了JavaScript 對於對象的支持和Object 類與Array 類之間的相似性。我們還看到幾種分別使用JSON、構造函數以及原型的概念來實例化JavaScript 對象的方法。在這個過程巾中,我們討論瞭如何在JavaScript 中以一種與語言"合作而不是對着幹"的方式來處理面向對象的概念,例如繼承和接口。
在對JavaScript Function 對象的探索中,我們看到了函數如何獨立於任何它們所賦予的對象而存在,甚至如何在對象之間借用或交換。我們使用這些知識來更好地理解對於JavaScript 事件模型。最後我們考察了閉包,看到了一些通常的編程習慣如何無意中創建了閉包,潛在地導致了內存泄漏。
與Java或者C#相比, JavaScript 提供了充分的靈活性和空間,可以爲這種語言開發個性化的風格和方法。這對於其他程序員來說是好事,因爲對自己所做的事情能瞭如指掌。當在團隊中工作時,這也可能會帶來問題,但是這些問題可以通過共享代碼約定或者編程風格來緩解。
一旦理解了JavaScript 的工作原理,對你而言它就成爲一種令人非常愉快的語言。如果你是從面向對象背景轉向Ajax開發,我們希望本文可以幫助你跨越這道鴻溝。
5、資源
相對於Web 瀏覽器編程,關於JavaScript 語言的書很少。David Flanagan 的JavaScript: The Definitive Guide (O'Reilly 2001年出版)是一本權威著作。但這本書有點舊。更新的一本好書是Nicholas Zaka 的Professional JavaScript for 脅b Developers (Wrox 2004 年出版) ,它也很好地全面描述了這種語言,並且覆蓋了該語言的一些更新發展。
在Web 上, Doug Crockford 討論了JavaScript 的面向對象開發方法,例如爲類創建私有的成員( www.crockford.com/javascript/private.html) 以及繼承( www.crockford.com/javascript/inheritance.
html ) 。 Peter-Paul Koch 的Quirksmode 網站(http://quirksmode.org) 也討論了很多這種語言的更加細微的知識點。Jim Ley 對於JavaScript 中閉包的討論可以在http://jibbering.com/faq/faq_notes/closures.html 中找到。
Mike Foster的x庫可以在www.cross-browser.com找到。