javascript 中的事件

javascript中事件的理解
在javascript中,event事件是一個必不可少的討論話題,它在和用戶的交互中起到了很重要的作用。今天我們就來討論一下JavaScript中的事件處理,並且結合它來闡敘Ajax框架實現拖動效果的原理了。
一、 Event對象
   
1   Event對象的主要屬性和方法
  event代表事件的狀態,專門負責對事件的處理,它的屬性和方法能幫助我們完成很多和用戶交互的操作,下面我們就來看看它的一些屬性和方法。
        type:事件的類型,就是HTML標籤屬性中,沒有“on”前綴之後的字符串,例如“Click”就代表單擊事件。

  srcElement:事件源,就是發生事件的元素。比如
<a onclick="check()"></a> a這個鏈接是事件發生的源頭,也就是該事件的srcElement。

  button:聲明瞭被按下的鼠標鍵,是一個整數。0代表沒有按鍵,1代表鼠標左鍵,2代表鼠標右鍵,4代表鼠標的中間鍵,如果按下了多個鼠標鍵,就把這些值加在一起,所以3就代表左右鍵同時按下。

  clientX
/clientY:是指事件發生的時候,鼠標的橫、縱座標,返回的是整數,它們的值是相對於包容窗口的左上角生成的。

  offsetX
/offsetY:鼠標指針相對於源元素的位置,可以確定單擊Image對象的哪個象素。

  altKey,ctrlKey,shiftKey:顧名思義,這些屬性是指鼠標事件發生的時候,是否同時按住了Alt、Ctrl或者Shift鍵,返回的是一個布爾值。

  keyCode:返回keydown和keyup事件發生的時候,按鍵的代碼以及keypress事件的Unicode字符。比如event.keyCode
=13代表按下了回車鍵;

  fromElement、toElement前者是指代mouseover事件移動過的文檔元素,後者指代mouseout事件中鼠標移動到的文檔元素。

  cancelBubble:一個布爾屬性,把它設置爲true的時候,將停止事件進一步起泡到包容層次的元素,它用於檢測是否接受上層元素的事件的控制。true代表不被上層元素的事件控制,false代表允許被上層元素的事件控制。

  returnValue:一個布爾值屬性,設置爲false的時候可以阻止瀏覽器執行默認的事件動作,相當於
<a href=”#” onclick=”ProcessMethod();return false;” />

         attachEvent()和detachEvent()方法:爲制定DOM對象事件類型註冊多個事件處理函數的方法,它們有兩個參數,第一個是事件類型,第二個是事件處理函數。在attachEvent()事件執行的時候,this關鍵字指向的是window對象,而不是發生事件的那個元素。

   
2    IE Event對象的一些說明
  Event對象是一個全局屬性
  在IE中,不能把Event對象作爲參數傳遞給事件處理程序,只能用window.event或者event來引用Event對象。因爲在IE中,Event是window的一個屬性,也就是說event是一個全局變量,這個變量提供了事件的細節。
 
3 關於事件的起泡的概念 

        IE中事件的起泡:IE中事件可以沿着包容層次一點點起泡到上層,也就是說,下層的DOM節點定義的事件處理函數,到了上層的節點如果還有和下層相同事件類型的事件處理函數,那麼上層的事件處理函數也會執行。例如,
<div>標籤包含了<a>,如果這兩個標籤都有 onclick事件的處理函數,那麼執行的情況就是先執行<a>標籤的onclick事件處理函數,再執行<div>的事件處理函數。如果希望<a>的事件處理函數執行完畢之後,不希望執行上層的<div>的onclick的事件處理函數了,那麼就把 cancelBubble設置爲false即可。
  
二、 IE中拖動DOM元素的例子
/*
  該函數由mousedown事件處理調用
  它爲隨後發生的mousemove和mouseup事件註冊了臨時的捕捉事件處理程序
  並用這些事件處理程序拖動指定的文檔元素
  第二個參數必須是mousedown事件的事件對象
*/

function beginDrag(elementToDrag,event)
{
  
//該元素當前位於何處
  //該元素的樣式性質必須具有left和top css屬性
  //此外,我們假定他們用象素做單位
  //var x=parseInt(elementToDrag.style.left);
  //var y=parseInt(elementToDrag.style.top);
  
  
//計算一個點和鼠標點擊之間的距離,下面的嵌套的moveHandler函數需要這些值
  var deltaX=event.clientX-parseInt(elementToDrag.style.left);
  
var deltaY=event.clientY-parseInt(elementToDrag.style.top);
  
//  註冊mousedown事件後發生的mousemove和mouseup事件的處理程序
//
  注意,它們被註冊爲文檔的捕捉事件處理程序
//
  在鼠標按鈕保持按下的狀態的時候,這些事件處理程序保持活動的狀態
//
  在按鈕被釋放的時候,它們被刪除
  document.attachEvent("onmousemove",moveHandler);
  document.attachEvent(
"onmouseup",upHandler);
   
  
//我們已經處理了該事件,不要讓別的元素看到它
 event.cancelBubble=true;
 event.returnValue
=false;
  
  
/*
    這是在元素被拖動時候捕捉mousemove事件的處理程序,它響應移動的元素
    
  
*/

  
function moveHandler(e)  
  
{
    
//把元素移動到當前的鼠標位置
    e=window.event;
    elementToDrag.style.left
=(event.clientX-deltaX)+"px";
    elementToDrag.style.top
=(event.clientY-deltaY)+"px";
    
    
//不要讓別的元素看到該事件
    event.cancelBubble=true;
    
  }

  
  
/*
    該事件將捕捉拖動結束的時候發生的mouseup事件
  
*/

  
function upHandler(e)
  
{
    
//註銷事件處理程序
      document.detachEvent("onmouseup",upHandler);
      document.detachEvent(
"onmousemove",moveHandler);}

   
      event.cancelBubble
=true;
    }
  
   調用它的HTML文件代碼:
 
<html>
 
<head>
     
<title>Untitled Page</title>
     
<script type="text/javascript" src="dragIE.js"></script>
 
</head>
 
<body>
 
<div style="position:absolute;left:100px;top:100px;background-color:White;border:solid black;">
   
<div style="background-color:Gray;border-bottom:solid black;padding:3px;font-family:Sans-Serif;font-weight:bold;" onmousedown="beginDrag(this.parentNode,event);">
   拖動我
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
   
</div>
   
<div>
   
<p>This is a test.Testing,testing</p></div>
 
</div>
 
</body>
三、 DOM中的高級事件處理
 IE 6中的事件處理,並不是W3C DOM標準的事件處理模型,所以如果上述代碼運行在Mozilla Firefox的瀏覽器中,就會失去作用,同時即將發佈的IE 7也將支持W3C DOM的二級標準,所以掌握DOM的高級事件處理顯得就很重要了,因爲W3C DOM二級標準是未來Web的發展方向,同時W3C DOM的API非常常用,爲未來更加複雜的Web開發提供了良好的基礎。
(一)事件處理程序的作用域和事件的傳播
  在正式討論DOM高級事件處理之前,我們有必要了解一下事件處理程序的作用域。事件處理程序的作用域要比普通的函數作用域複雜很多。普通的函數作用域鏈比較容易,例如在一個普通函數中查找一個變量a,那麼JavaScript解釋器會先在該函數的調用對象中查找是否有a這個變量,如果沒有,將會在作用域鏈的下一個對象,一般是全局對象中查找。但是事件處理程序沒這麼簡單,特別是用HTML的屬性定義的,它們的作用域鏈的頭部是調用它們的對象,而下一個對象並不是全局對象,而是觸發事件處理程序的對象。這樣就會出現一個問題,window和document都有一個方法open(),如果open()前面不加修飾,那麼在事件處理的函數中將會調用document.open()方法,而不是常用的window.open()方法,所以使用的時候應該明確指明是 window.open()。
(二)事件傳播和註冊事件處理程序
1.事件傳播
  在二級DOM標準中,事件處理程序比較複雜,當事件發生的時候,目標節點的事件處理程序就會被觸發執行,但是目標節點的父節點也有機會來處理這個事件。事件的傳播分爲三個階段,首先是捕捉階段,事件從 Document對象沿着DOM樹向下傳播到目標節點,如果目標的任何一個父節點註冊了捕捉事件的處理程序,那麼事件在傳播的過程中就會首先運行這個程序。下一個階段就是發生在目標節點自身了,註冊在目標節點上的相應的事件處理程序就會執行;最後是起泡階段,事件將從目標節點向上傳回給父節點,同樣,如果父節點有相應的事件處理程序也會處理。在IE中,沒有捕捉的階段,但是有起泡的階段。可以用stopPropagating()方法來停止事件傳播,也就是讓其他元素對這個事件不可見,在IE 6中,就是把cancelBubble設置爲true。
2.註冊事件處理程序
  和IE一樣, DOM標準也有自己的事件處理程序,不過DOM二級標準的事件處理程序比IE的強大一些,事件處理程序的註冊用addEventListner方法,該方法有三個參數,第一個是事件類型,第二個是處理的函數,第三個是一個布爾值,true表示制定的事件處理程序將在事件傳播的階段用於捕捉事件,否則就不捕捉,當事件發生在對象上才觸發執行這個事件處理的函數,或者發生在該對象的字節點上,並且向上起泡到這個對象上的時候,觸發執行這個事件處理的函數。例如:document.addEventListener(
"mousemove",moveHandler,true);就是在mousemove事件發生的時候,調用moveHandler函數,並且可以捕捉事件。
  可以用addEventListener爲一個事件註冊多個事件處理的程序,但是這些函數的執行順序是不確定,並不像C#那樣按照註冊的順序執行。
在Mozilla Firefox中用addEventListener註冊一個事件處理程序的時候,this關鍵字就表示調用事件處理程序的文檔元素,但是其他瀏覽器並不一定是這樣,因爲這不是DOM標準,正確的做法是用currentTarget屬性來引用調用事件處理程序的文檔元素。
3.二級DOM標準中的Event
和IE不同的是,W3C DOM中的Event對象並不是window全局對象下面的屬性,換句話說,event不是全局變量。通常在DOM二級標準中,event作爲發生事件的文檔對象的屬性。Event含有兩個子接口,分別是UIEvent和MutationEvent,這兩個子接口實現了Event的所有方法和屬性,而 MouseEvent接口又是UIEvent的子接口,所以實現了UIEvent和Event的所有方法和屬性。下面,我們就看看Event、 UIEvent和MouseEvent的主要屬性和方法。
  
1.Event
    type:事件類型,和IE類似,但是沒有“on”前綴,例如單擊事件只是“click”。
    target:發生事件的節點。
    currentTarget:發生當前正在處理的事件的節點,可能是Target屬性所指向的節點,也可能由於捕捉或者起泡,指向Target所指節點的父節點。
    eventPhase:指定了事件傳播的階段。是一個數字。
    timeStamp:事件發生的時間。
    bubbles:指明該事件是否起泡。
    cancelable:指明該事件是否可以用preventDefault()方法來取消默認的動作。
    preventDefault()方法:取消事件的默認動作;
    stopPropagation()方法:停止事件傳播。
  
2.UIEvent
    view:發生事件的window對象。
    detail:提供事件的額外信息,對於單擊事件、mousedown和mouseup事件都代表的是點擊次數。
  
3.MouseEvent
   button:一個數字,指明在mousedown、mouseup和單擊事件中,鼠標鍵的狀態,和IE中的button屬性類似,但是數字代表的意義不一樣,0代表左鍵,1代表中間鍵,2代表右鍵。
   altKey、ctrlKey、shiftKey、metaKey:和IE相同,但是IE沒有最後一個。
clientX、clientY:和IE的含義相同,但是在DOM標準中,這兩個屬性值都不考慮文檔的滾動情況,也就是說,無論文檔滾動到哪裏,只要事件發生在窗口左上角,clientX和clientY都是0,所以在IE中,要想得到事件發生的座標相對於文檔開頭的位置,要加上 document.body.scrollLeft和document.body.scrollTop。
   screenX、screenY:鼠標指針相對於顯示器左上角的位置,如果你想打開新的窗口,這兩個屬性很重要。
   relatedTarget:和IE中的fromElement、toElement類似,除了對於mouseover和mouseout有意義外,其他的事件沒什麼意義。
(三)兼容於兩種主流瀏覽器的拖動DOM元素的例子
  好了,剛纔講了這麼多DOM編程和IE中的事件,那麼如何編寫兼容IE和Mozilla Firefox兩種主流瀏覽器的拖拽程序呢?代碼如下:
function beginDrag(elementToDrag,event)
{
  
var deltaX=event.clientX-parseInt(elementToDrag.style.left);
  
var deltaY=event.clientY-parseInt(elementToDrag.style.top);
  
if(document.addEventListener) 
{
  document.addEventListener(
"mousemove",moveHandler,true);
  document.addEventListener(
"mouseup",upHandler,true);
}

else if(document.attachEvent)
{
  document.attachEvent(
"onmousemove",moveHandler);
  document.attachEvent(
"onmouseup",upHandler);
  
}

  
  
if(event.stopPropagation)   event.stopPropagation();
  
else event.cancelBubble=true;
  
if(event.preventDefault)  event.preventDefault();
  
else event.returnValue=false;
  
  
function moveHandler(e)  
  
{
  
if (!e) e=window.event; //如果是IE的事件對象,那麼就用window.event
  //全局屬性,否則就用DOM二級標準的Event對象。
    elementToDrag.style.left=(event.clientX-deltaX)+"px";
    elementToDrag.style.top
=(event.clientY-deltaY)+"px";
    
     
if(event.stopPropagation)   event.stopPropagation();
    
else event.cancelBubble=true;
    
  }

  
  
function upHandler(e)
  
{
       
if(document.removeEventListener)
    
{
      document.removeEventListener(
"mouseup",upHandler,true);
      document.removeEventListener(
"mousemove",moveHandler,true);}

      
else
    
{
      document.detachEvent(
"onmouseup",upHandler);
      document.detachEvent(
"onmousemove",moveHandler);}

    }

      
if(event.stopPropagation)   event.stopPropagation();
    
else event.cancelBubble=true;
    
最後我們來回顧一下事件執行過程中的三個階段:
 
1 捕捉階段,事件從Document對象沿Dom解析的樹向下傳播給目標節點。
 
2 目標節點觸發階段 事件處理程序在目標上的運行階段 
 
3 起泡階段 事件從目標元素向上傳播或者起泡回Document對象的文檔層次。



JavaScript獲取網頁中HTML元素的幾種方法分析
getElementById getElementsByName getElementsByTagName 大概介紹 
  getElementById ,getElementsByName ,getElementsByTagName
  後兩個是得到集合,byid只是得到單個對象
  getElementById 的用法
  舉個例子:
  
<a id="link1" name="linkname1" href=http://homepage.yesky.com>網頁陶吧</a>
  同一頁面內的引用方法:
  
1、使用id:
  link1.href,返回值爲http:
//homepage.yesky.com
  2、使用name:
  document.all.linkname1.href,返回值爲http:
//homepage.yesky.com
  3、使用sourseIndex:
  document.all(
4).href //注意,前面還有HTML、HEAD、TITLE和BODY,所以是4
  4、使用鏈接集合:
  document.anchors(
0).href
  
//全部的集合有all、anchors、applets、areas、attributes、behaviorUrns、bookmarks、boundElements、cells、childNodes、children、controlRange、elements、embeds、filters、forms、frames、images、imports、links、mimeTypes、options、plugins、rows、rules、scripts、styleSheets、tBodies、TextRectangle,請參考MSDN介紹。
  其實方法3和方法4是一樣使用的集合,只是一個是all,可以包括頁面所有標記,而anchors只包括鏈接。
  
5、getElementById:
  document.getElementById(
"link1").href
 
  
6、getElementsByName:
  document.getElementsByName(
"linkname1")[0].href //這也是一個集合,是所有name等於該方法所帶參數的標記的集合
  7、getElementsByTagName:
  document.getElementsByTagName(
"A")[0].href //這也是一個集合,是所有標記名稱等於該方法所帶參數的標記的集合
  8、tags集合:
  document.all.tags(
"A")[0].href
  
//與方法7一樣是按標記名稱取得一個集合
  除此之外:
  event.scrElement可以獲得觸發時間的標記的引用;
  document.elementFromPoint(x,y)可以獲得x和y座標處的元素的引用;
  document.body.componentFromPoint(event.clientX,event.clientY)可以獲得鼠標所在處元素的引用;
  還可以通過元素的父子節點和兄弟節點關係來引用,如nextSibling(當前節點的後一節點)、previousSibling(當前節點的前一節點)、childNodes、children、firstChild、lastChild、parentElement等都是父子節點和兄弟節點的一些引用;還不僅限於此。
  上面是同一頁面內的常見引用方法,另外還涉及到不同頁面中的
  getElementsByName返回的是所有name爲指定值的所有元素的集合
  “根據 NAME 標籤屬性的值獲取對象的集合。”
  集合比數組要鬆散的多, 集合裏每個子項的類型可以不同, 集合只是把某些元素放在一起作爲一類來使用, 相比之下數組就嚴格多了, 每個子項都是統一的類型. document.getElementsByName, document.getElementsByTagName, document.formName.elements 這類方法所得到的結果都是集合.
  例:
<html> 
<head> 
<title>fish</title> 
<script language="javascript"> 
function get()
var xx=document.getElementById("bbs"
alert(
"標記名稱:"+xx.tagName); 
}
 
function getElementName()
var ele = document.getElementsByName("happy"); 
alert(
"無素爲happy的個數:" + ele.length); 
}
 
</script></head> 
<body> 
<h2 id="bbs">獲取文件指定的元素</h2> 
<hr> 
<form> 
<input type="button" onclick="get()" value="獲取標題標記"> 
<input type="button" name="happy" onclick="getElementName()" value="click ">
<input type="button" name="happy" onclick="getElementName()" value="click ">
<input type="button" name="happy" onclick="getElementName()" value="click ">
<input type="button" name="happy" onclick="getElementName()" value="click ">
<input type="button" name="happy" onclick="getElementName()" value="click "> 
</form> 
</body> 
</html>
 
  
  document.getElementsByName()這個方法.它對一個和多個的處理是一樣的,我們可以用:
  Temp 
= document.getElementsByName('happy')來引用
  當Temp只有1個的時候,那麼就是Temp[
0],有多個的時候,用下標法Temp[i]循環獲取
  也有例外:
  在ie 中getElementsByName(“test“)的時候返回的是id
=test的object數組,而firefox則返回的是name= test的object的數組。
  按照w3c的規範應該是返回的是name
= test的object的數組。
  firefox和ie中的getElementByID相同:獲取對 ID 標籤屬性爲指定值的第一個對象的引用。
  注意getElementsByName 有s在裏面
  document.getElementById()可以控制某個id的tag
  document.getElementsByName(),返回的是一個具有相同 name 屬性的元素的集合,而不是某個,注意有“s”。
  而 document.getElementsByTagName() 返回的是一組相同 TAG 的元素集合。
  同一個name可以有多個element,所以用document.getElementsByName(
"theName")
  他return 一個collection,引用的時候要指名index
  
var test = document.getElementsByName('testButton')[0];
  id那個,是唯一的
  還應該注意:對類似沒有name屬性,對它name屬性爲僞屬性document.getElementsByName() 會失效,當然TD可以設置ID屬性,然後用 document.getElementsByID(
"DDE_NODAY"); 調用。
注:document.all 有些地方講的不對。 document.all.tt1有可能是一個集合。


由淺到深學習JavaScript類

類是什麼?

        許多剛接觸編程的朋友都可能理解不了類,其實類是對我們這個現實世界的模擬,把它說成“類別”或者“類型”可能會更容易理解一些。比如“人”這種動物就是一個類,而具體某一個人就是“人”這個類的一個實例,“人”可以有許多實例(地球人超過六十億了),但“人”這個類只有一個。你或許會說那男人和女人不也是人麼?怎麼只能有一個?其實這裏要談到一個繼承的東西,後邊纔講,請繼續看下去。

如何建立一個類?
        在C
++中是以class來聲明一個類的,JavaScript與C++不同,它使用了與函數一樣的function來聲明,這就讓許多學Jscript的朋友把類與函數混在一起了,在Jscript中函數與類確實有些混,但使用久了自然而然會理解,這篇文章是針對想進攻面向對象編程的朋友而寫,就不打算一下子討論得太深了。
        請看下邊這個類的定義:
        
        
function WuYouUser()
        
{
                
this.Name; //名字
        }

        
        上邊的代碼定義了一個WuYouUser(無憂用戶)類,它有個屬性:Name(名字)。Name就是WuYouUser類的一個屬性。
        一個類有固定的屬性,但類的實例卻有不同的屬性值,就像我是屬於“人”這個類的,性別是男,而我有一個女同學,她也屬於“人”類,但她的性別屬性值卻爲女。
        那麼如何聲明某個類的一個實例呢?非常簡單:
        
        
var Wo = new WuYouUser(); //實例一:“我”
        var Biyuan = new WuYouUser(); //實例二:“碧原”(Biyuan哥,不好意思。。。嘿嘿)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類的屬性

        這個Wo(我)就是WuYouUser類的一個實例,它擁有WuYouUser給它的一切:Name屬性、Sex屬性以及Age屬性,我們可以這樣子來設置它的屬性:
        
        Wo.Name 
= "泣紅亭";
        
        很簡單是不是?試着運行
        
        window.document.write(Wo.Name);
        
        看看,是不是輸出了我的名字:泣紅亭?
        
        同樣設置一下碧原兄的屬性
        
        Biyuan.Name 
= "碧原";
        
        運行
        
                window.document.write(Biyuan.Name);
                
        可以看到輸出了
"碧原",也就說明了Biyuan與Wo同樣是WuYouUser類的實例,但卻是不同的實體,具有不同的屬性值。
        
        屬性是可以設置默認值的,無憂裏都有記錄大家各自發了多少貼子,我們也同樣給WuYouUser類添加一個發貼數量的屬性ArticleCount
        
        
function WuYouUser()
        
{
                
this.Name;
                
this.ArticleCount = 0;
        }

        
        一個無憂新用戶剛註冊完之後他的發貼數量爲0,在上邊的代碼中可以看到直接給屬性ArticleCount設置值爲0。
        
        可以運行一下這樣的代碼:
        
        
var Wo = new WuYouUser();
        window.document.write(Wo.ArticleCount);
        
        可以看到輸出了0,說明ArticleCount屬性被我們成功設置默認值爲0

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類的方法
        
        方法這個詞不大好理解,我覺得說成行爲會更容易理解。一個人具有許多共同的行爲,比如睡覺、吃飯、走路等等,現在我們給WuYouUser類添加一個發貼的方法。
        
        
function WuYouUser()
        
{
                
this.Name;
                
this.ArticleCount = 0;
                
                
this.NewArticle = function()
                
{
                        
/*
                        *
                        *        具體如何發貼我們大家都知道,不就是打打字,加加圖片再按一下保存之類的按鈕麼?
                        *        關於具體如何發貼的代碼沒有必要在這裏寫出來,我們要了解的僅僅是方法的定義與使用
                        *        我們在這裏實現一個最簡單的功能,也是很重要的功能:給我們的發貼數量加上1!
                        *        注意:恐龍等級就是這樣加出來的,因此呀……大家狂發貼吧。。。
                        
*/

                        
                        
this.ArticleCount++;
                }

        }

        
        既然定義好了這個方法,我們來試試效果如何:
        
        
var Wo = new WuYouUser();
        Wo.NewArticle();
        document.write(Wo.ArticleCount);
        
        可以看到輸出了1,說明我們發貼成功了!真是有歷史紀念意義的一刻,離恐龍等級又近一步了。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
靜態屬性

        靜態屬性又稱公共屬性,它不屬於某個類的實例,而是直接屬於某個類。
        
        比如說無憂用戶有一個屬性:註冊用戶的數量,它是屬於整個無憂用戶的,而不是屬於泣紅亭或者誰的
        靜態屬性的聲明方法是:
        
        類名.prototype.屬性名 
= 屬性值;
        
        比如給WuYouUser類定義一個註冊用戶的數量Count:
        
        WuYouUser.prototype.Count 
= 0;
        
        那麼如何讀取它呢?有兩種方法:
        
        
1. 直接用 WuYouUser.prototype.Count
        
2. 使用Wo.Count
        
        這兩者沒有區別,都是得到0
        
        雖然讀取方法可以有兩種,但在改變它的時候卻得特別小心了,請看下邊代碼
        
        
var Biyuan = new WuYouUser();
        WuYouUser.prototype.Count
++;
        document.write(Wo.Count);
        document.write(Biyuan.Count);
        
        你會發現兩者的Count屬性都是1,也就是說WuYouUser.prototype.Count改變了會影響到各個實例的相應屬性,其實原理就是Wo、Biyuan的Count屬性與WuYouUser.prototype.Count根本就是同一個!
        
        現在來看另外一段代碼:
        
        
var Biyuan = new WuYouUser();
        
        Biyuan.Count
++//特別注意一下這裏,這是直接改變Biyuan的Count屬性
        document.write(Biyuan.Count); // 輸出 1
        document.write(WuYouUser.prototype.Count); //輸出 0
        document.write(Wo.Count); //同樣輸出0,爲什麼?
        
        可以看到如果直接修改實例的靜態屬性值,那麼會出現其它實例甚至類的靜態屬性與它不同步了?這是因爲直接修改的時候,該實例會生成一個屬於該實例的屬性Count,這個時候Biyuan.Count不再與WuYouUser.prototype.Count是同一個了,也不與Wo.Count是同一個,這個Count屬性是屬於Biyuan自己所有的,以後改變了它也只是影響它自己而已。
        
        因此如果不是特別的需要,建議不管在讀取還是賦值的時候,都統一使用WuYouUser.prototype.Count這樣的方式,以做到萬無一失!
        
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
靜態方法
        
        與靜態屬性相似,它也有個另稱:公共方法,同樣屬於類本身的。
        
        靜態方法的定義方式是:
        
        類名.方法名 
= function(參數1,參數2...參數n) 
        
{
                
//方法代碼
        }

        
        我們現在就來定義一個無憂用戶類的註冊新用戶靜態方法:
        
        WuYouUser.prototype.AddOne 
= function()
        
{
                
//***  同樣具體代碼不寫出來,給靜態屬性Count增加1,表示註冊用戶數量又多一個
                WuYouUser.prototype.Count++;
        }

        
        現在我們來看看如何用它,同樣有兩種方法:
        
        
1.直接使用WuYouUser.prototype.AddOne()
        
2.使用某實例的AddOne()
        
        這兩種方法沒有什麼不同:
        
        
var Wo = new WuYouUser();
        
var Biyuan = new WuYouUser();
        document.write(WuYouUser.prototype.Count); 
// 0
        
        Wo.AddOne();
        document.write(WuYouUser.prototype.Count); 
// 1
        document.write(Wo.Count); // 1
        document.write(Biyuan.Count); // 1
        
        WuYouUser.prototype.AddOne();
        document.write(WuYouUser.prototype.Count); 
// 2
        document.write(Wo.Count); // 2
        document.write(Biyuan.Count); // 2
        
        可以看出不管是使用Wo.AddOne()還是WuYouUser.prototype.AddOne()效果都是一樣的,都是給WuYouUser.prototype.Count加上1
        
        現在再看一段代碼:
        
function NewClass() //由於上邊的WuYouUser類不合適當這個例子的代碼,我聲明瞭一個新類NewClass
        {
                
this.Name = "泣紅亭"//這裏默認值爲我的名字
        }

        
        NewClass.prototype.ChangeName 
= function(NewName)
        
{
                
this.Name = NewName;
        }

        
        
var Wo = new NewClass();
        Wo.ChangeName(
"鄭運濤"); //我的真名
        
        可以看到Wo.Name確實已經變成了
"鄭運濤",這個方法似乎是可以用的,但裏邊是不是內有天機呢?
        再看下邊的代碼,類的定義以及ChangeName的定義我們照樣,但改變一下下邊的代碼:
        
        NewClass.prototype.ChangeName(
"鄭運濤");
        document.write(NewClass.Name); 
//undefined,即未定義
        document.write(NewClass.prototype.Name); //鄭運濤
        var Wo = new NewClass();
        document.write(Wo.Name); 
//泣紅亭
        
        可以看到我們並沒有定義NewClass.prototype.Name這個靜態屬性,但編譯器給我們自己加了一個。
        可是再看下邊輸出Wo.Name,它並不是爲
"鄭運濤",而是原來的默認值"泣紅亭",說明了什麼?
        其實很簡單,看一下NewClass的定義裏已經有Name這個屬性,因此Wo也有自己的Name屬性,它跟NewClass.prototype.Name並不是同一個的,因此就還是那樣子。
        
        那爲什麼前一個例子運行了Wo.ChangeName(
"鄭運濤")卻能夠實現改變Wo.Name屬性呢?其實在這裏跟改變Wo.Count的值是同一個道理,編譯器自動給Wo增加了一個方法ChangeName,這個方法代碼與NewClass.prototype.ChangeName一樣,但Wo.ChangeName是Wo這個實例所特有的,而非NewClass.prototype.ChangeName!
        
        分析可知道在靜態方法裏儘量不要使用this這樣的關鍵字來引用實例本身的屬性,除非你有特別的目的,而且能夠清楚地明白這裏邊的運行機制!
        
        如果真的需要在靜態方法裏使用this,可以直接把this當作參數傳進去:
        
        NewClass.ChangeName 
= function(This,NewName) //注意這裏是This,不是this
        {
                This.Name 
= NewName;
        }

        
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
構造函數

        一個類在初始化的時候其實也是一個函數的執行過程,這個函數就是構造函數,我們看一下下邊的代碼:
        
        
function WuYouUser()
        
{
                
this.Name = "泣紅亭"//默認定義爲泣紅亭
                alert(this.Name);
        }

        
var Wo = new WuYouUser();//可以看到出現一個窗口顯示泣紅亭三個字
        
        可以看出類的定義不僅僅是定義了它的屬性與方法,還同時可以加入一些代碼,而這些代碼就是該類的構造函數的代碼,在實例聲明過程中被執行!
        其實說起來,類的屬性與類的方法都是在構造函數裏執行定義的,看下邊的代碼:
        
        
function WuYouUser()
        
{
                
this.Name = "泣紅亭";
                
return;
                
this.Sex = "";
        }

        
var Wo = new WuYouUser();
        document.write(Wo.Name); 
//泣紅亭
        document.write(Wo.Sex); //undefined,即未定義
        
        看得出什麼?Sex屬性是在return;之後的,而WuYouUser類的構造函數遇到return即停止運行,換句話說this.Sex 
= "";這一行是沒有被執行,即Sex屬性根本沒有被定義!
        
        構造函數可以有參數,參數值在聲明實例的時候被傳入:
        
function WuYouUser(Name)
        
{
                
this.Name = Name;
        }

        
var Wo = new WuYouUser("泣紅亭");
        document.write(Wo.Name); 
//泣紅亭
        
        構造函數不需要返回值,但如果你設置了返回值,可以把它當成一個函數來使用。
        
function Sum(a, b)
        
{
                
this.a = a;
                
this.b = b;
                
return this.a + this.b;
        }

        document.write(Sum(
1223)); //輸出的是12與23的和35
        var Obj = new Sum(12,23);
        document.write(Obj.a) 
// 12
        document.write(Obj.b) // 23
        
        感覺挺奇妙,對吧?我寫這文章寫着寫着也覺得挺奇妙的,呵呵!
        
        但強烈建議不要把一個類當成一個函數來使用!如果你需要的是一個函數,請直接寫成函數而不要寫成類,以免搞混了。
        
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
繼承

        繼承這個詞在面向對象的編程裏是非常重要的,雖然JavaScript並不是真正面向對象的語言,而是跟VB一樣是基於對象的語言,它同樣提供了繼承機制。
        
        文章開頭時談到了男人與女人,這也同樣是兩個不同的類,但卻具有相同的一些屬性以及方法,而這些相同的特性是來自“人”這個類的,換句話說男人與女人繼承了“人”的所有特性!但是男人與女人卻有其不同的地方,編程語言裏的繼承也一樣,一個類A繼承了另一個類B,那麼類B就是類A的父類,類A就是類B的派生類,也稱爲子類。比如男人就是人的派生類,而人就是男人的父類。最高一級的類稱爲基類,想象一下就可以明白,男人繼承自人,男孩繼承自男人,人就是男孩的基類,男人就是男孩的父類。
        

        
>>>>>>>>>>>>>>>>>>>>
        題外:多重繼承
        
        這裏再涉及一個多重繼承的話題,但如果你僅僅是學JavaScript的話就沒有必要看下去,因爲JavaScript不提供多重繼承,準確一點說沒有一種簡單而標準的方法來實現多重繼承(其實是有辦法實現的,只不過麻煩了一點,而且確實沒有必要)。
        
        在C
++中是有多重繼承的概念的,這裏是討論JavaScript,因此不打算講,只是說說它的一點點思想以供參考。
        
        在上邊男孩的繼承問題中,男孩其實不僅僅是繼承自男人,還繼承自孩子(有男孩子,也有女孩子)這個類,因此,它同時繼承了兩個類:男人與男孩,這就是所謂的多重繼承。
        
        好,這個問題打住,我們還是迴歸主題。
        
>>>>>>>>>>>>>>>>>>>>
        
        先看第一個類的定義:
        
        
function A()
        
{
                
this.Name = "泣紅亭";
                alert(
this.Name);
        }


        這個類定義了一個屬性Name,默認值爲
"泣紅亭"
        
        現在看第二個類的定義:
        
        
function B()
        
{
                
this.Sex = "";
                alert(
this.Sex);
        }

        
        定義了一個屬性Sex,默認值爲
""
        
        繼承的方式就是 子類.prototype 
= new 父類();        
        現在我們來讓B類繼承A類:
        
        B.prototype 
= new A();
        
        
        
        運行這一段代碼:
        
        
var Obj = new B(); //首先打開警告窗口顯示"泣紅亭",再顯示"男"
        
        可以從上邊的結果看出B類繼承了A類,擁有了A類的屬性Name,並且執行了A類的構造函數,而且A類的構造函數在B類的構造函數執行之前執行。因此我們利用這個可以實現重寫父類的方法以及重設置父類某屬性的默認值:
        
        
function A()
        
{
                
this.Name = "泣紅亭";
                
this.Show = function()
                
{
                        alert(
"這是A類的Show方法");
                }

                alert(
this.Name);
        }

        
        
function B()
        
{
                
this.Name = "鄭運濤";
                
this.Show = function()
                
{
                        alert(
"這是B類的Show方法");
                }

                alert(
this.Name);
        }

        
        
var Obj = new B();
        Obj.Show();
        
        結果出現了三次警告窗口,第一個內容爲泣紅亭,是執行A類的構造函數裏的alert(
this.Name),那時候Name屬性值還爲"泣紅亭",因爲B類的構造函數還沒執行,第二次內容爲"鄭運濤",這是B類裏的alert(this.Name),因爲B類的構造函數裏給Name重賦值爲"鄭運濤"。最後是調用了Obj.Show(),執行了不是A類的Show方法裏的Show(顯示"這是A類的Show方法"),而是執行了B類的Show(顯示"這是B類的Show方法"),很明顯Show方法被重寫了。
        
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
類作爲一個對象時的屬性與方法(不知道如何簡潔地表達,因此用了這麼長的題目)

        不知道在這裏談這個話題是否有點混人耳目,但又覺得不談這篇文章就不算完整,因爲文章目的就是要讓人搞清楚類的方方面面。
        
        看了這一小節的題目,或許你會覺得奇怪,類就是類,怎麼會“作爲一個對象”呢?在JavaScript裏,一切都是對象,包括類!對象可以有屬性,可以有方法,類也同樣可以有,但這個非常容易跟前邊說到的靜態屬性與靜態方法搞混了,因此要仔細看清楚兩者的分別!
        
        定義一個類:
        
function WuYouUser()
        
{
                
this.Name = "泣紅亭";
        }

        
        定義類作爲一個對象時的屬性:
        
        WuYouUser.Url 
= "http://www.livebaby.cn"//靜態屬性的定義是:WuYouUser.prototype.Url = "http://www.livebaby.cn";
        var Wo = new WuYouUser();
        document.write(WuYouUser.Url); 
//http://www.livebaby.cn
        document.write(Wo.Url); //undefined,即未定義!注意這裏的未定義
        
        從這裏可以看出Url這個屬性是WuYouUser自個所有,改變了它與其它類以及它的子類完全無關!
        
        引用類的屬性只有一個辦法,就是類名.屬性名,改變它也一樣。
        
        定義類作爲一個對象時的方法:
        
        WuYouUser.ChangeUrl 
= function()
        
{
                
this.Url = "http://www.livebaby.cn";
        }

        
        你或許會覺得奇怪,這裏的this是什麼?因爲ChangeUrl這個方法是屬於對象WuYouUser的,因此this指的就是WuYouUser本身!
        
        可以運行下邊的代碼試試:
        
        document.write(WuYouUser.Url); 
// http://www.livebaby.cn 
        WuYouUser.ChangeUrl();
        document.write(WuYouUser.Url); 
// http://www.livebaby.cn 
        
        明顯ChangeUrl直接修改了WuYouUser.Url的值,因此後邊才能輸出http:
//www.livebaby.cn 
        
        
        如果你這一節看不明白,也不要着急,編程嘛,許多東東都只能意會不能言傳,而且我又沒口才,說不清楚,只要以後多寫寫代碼,多用用類自然而然會體會到這一些,還有可以去看看JSVM的代碼,裏邊幾乎每個類都有用到類作爲一個對象時的屬性與方法。
        
        
        
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
後言

        首先感謝你能夠有耐心看到這裏,我也沒想到寫了這麼多才能夠寫得像樣一點,請別介意。




你真的會JavaScript嗎

很久沒有看到這樣讓人脣齒留香的好文了。上次看到的是一篇是 Douglas Crockford 的JavaScript, We Hardly 
new Ya(我簡單翻譯了一下,譯文在後)。

同其他教你如何用面向對象的思想編寫JavaScript的其他文章一樣,該文也是着重在這麼幾個要素:
?    JavaScript的對象就是一個關聯數組。 
?    JavaScript 函數也是一個對象。 
?    原型(Prototype) 
?    閉包(Closures) 
?    繼承/私有屬性/靜態方法 
?    命名空間 
作者文筆很好,英文很容易看懂,沒有生僻的用詞(順便提一下,《PPK on JavaScript》作者的英文不敢恭維)。用來舉例的代碼也很貼切。
特別是文章的開頭很有意思,作者寫到他和一個據說已經寫了快4年JavaScript的女程序員聊天,女程序員認爲她的JS水平very good,後來作者發現她確實會寫,但僅僅是會寫,其實對JavaScript的內涵所知甚少。
作者想用這個例子說明,有很多具備Java
/C++/C#開發經驗的開發人員在編寫JavaScript或者轉行到FED(比如我)的時候,想當然的把那些標準面嚮對象語言的思想套用在JavaScript上,反而走入迷途。
對此我深有體會,我正是在真正參與了一次Ajax的項目並真正讀懂了Prototype框架的源碼之後,對JavaScript有了完全全新的認識。
總之,推薦閱讀。附上JavaScript, We Hardly 
new Ya的譯文,譯得匆忙,定有行文不通之處,請客官見諒!
JavaScript 的 
new, 好久不見啊
原文: JavaScript, We Hardly 
new Ya--Douglas Crockford。    
JavaScript是一門基於原型的語言,但它卻擁有一個 
new 操作符使得其看起來象一門經典的面對對象語言。那樣也迷惑了程序員們,導致一些有問題的編程模式。
其實你永遠不需要在JavaScript使用 
new Object()。用字面量的形式{}去取代吧。

同理,不要使用 
new Array() ,而代之以字面量[]。JavaScript中的數組並不象Java中的數組那樣工作的,使用類似Java的語法只會讓你糊塗。
同理不用使用 
new Number, new String, 或者 new Boolean。這些的用法只會產生無用的類型封裝對象。就直接使用簡單的字面量吧。
不要使用 
new Function 去創建函數對象。用函數表達式更好。比如:
frames[
0].onfocus = new Function(”document.bgColor=’antiquewhite’”)
更好的寫法是:
frames[
0].onfocus = function () {document.bgColor = ‘antiquewhite’;};
第二種形式讓腳本編譯器更快的看到函數主體,於是其中的語法錯誤也會更快被檢測出來。有時候程序員使用 
new Function 是因爲他們沒有理解內部函數是如何工作的。
selObj.onchange 
= new Function(”dynamicOptionListObjects[”+
        dol.index
+”].change(this)”);
如果我們讓用字符串做函數體,編譯器不能看到它們。如果我們用字符串表達式做函數體,我們同樣也看不到它們。更好的方式就是不要盲目編程。通過製造一個返回值爲函數的函數調用,我們可以明確的按值傳遞我們想要綁定的值。這允許我們在循環中初始化一系列 selObj 對象。
selObj.onchange 
= function (i) {
    
return function () {
        dynamicOptionListObjects[i].change(
this);
    }
;
}
(dol.index);
直接對一個函數使用new永遠不是一個好主意。比如, 
new function 對構造新對象沒有提供什麼優勢。
myObj 
= new function () {
    
this.type = ‘core’;
}
;
更好的方式是使用對象字面量,它更輕巧,更快捷。
myObj 
= {
    type: ‘core’
}
;
假如我們需要創建的對象包含的方法需要訪問私有變量或者函數,更好的方式仍然是避免使用new.
var foo = new function() {
    
function processMessages(message) {
        alert(”Message: ” 
+ message.content);
    }

    
this.init = function() {
        subscribe(”
/mytopic”, this, processMessages);
    }

}

通過使用 
new 去調用函數,對象會持有一個無意義的原型對象。這隻會浪費內存而不會帶來任何好處。如果我們不使用new,我們就不用在對象鏈維護一個無用的prototype對象。所以我們可以用()來正確的調用工廠函數。var foo = function () {
    
function processMessages(message) {
        alert(”Message: ” 
+ message.content);
    }

    
return {
        init: 
function () {
            subscribe(”
/mytopic”, this, processMessages);
        }

    }
;
}
();
所以原則很簡單: 唯一應該要用到new操作符的地方就是調用一個古老的構造器函數的時候。當調用一個構造器函數的時候,是強制要求使用new的。有時候可以來new一下, 有的時候還是不要了吧。 
1.    Feedback:對於javaScript的Prototype我理解是一顆描述繼承樹的鏈子
使用javaScript進行OO開發是完全可以的
和java,C 等對比,有一點做不到:
對於父類的成員方法(非構造方法)要麼完全重寫,要麼原封不動,也就是無法在子類的成員方法中調用父類的同名方法
其他的重寫、重載、構造、接口、繼承、多態都是沒問題的
當然腳本語言沒有編譯器就無法進行比如做接口實現是否完整等檢查
靠程序員自己控制好吧 

JavaScript, We Hardly 
new Ya
JavaScript is a prototypal language, but it has a 
new operator that tries to make it look sort of like a classical language. That tends to confuse programmers, leading to some problematic programming patterns.
You never need to use 
new Object() in JavaScript. Use the object literal {} instead. Similarly, don’t use new Array(), use the array literal [] instead. Arrays in JavaScript work nothing like the arrays in Java, and use of the Java-like syntax will confuse you.
Do not use 
new Number, new String, or new Boolean. These forms produce unnecessary object wrappers. Just use simple literals instead. 
Do not use 
new Function to create function values. Use function expressions instead. For example,
frames[
0].onfocus = new Function("document.bgColor='antiquewhite'")
is better written as
frames[
0].onfocus = function () {document.bgColor = 'antiquewhite';};
The second form allows the compiler to see the 
function body sooner, so any errors in it will be detected sooner. Sometimes new Function is used by people who do not understand how inner functions work.
selObj.onchange 
= new Function("dynamicOptionListObjects["+
        dol.index
+"].change(this)"); 
If we keep 
function bodies in strings, the compiler can’t see them. If we keep function bodies as string expressions, we can’t see them either. It is better to not program in ignorance. By making a function that returns a function, we can explicitly pass in the values we want to bind. This allows us to initialize a set of selObj in a loop. 
selObj.onchange 
= function (i) {
    
return function () {
        dynamicOptionListObjects[i].change(
this);

    }
;
}
(dol.index);
It is never a good idea to put 
new directly in front of function. For example, new function provides no advantage in constructing new objects. 
myObj 
= new function () {
    
this.type = 'core';
}
;
It is better to use an object literal. It is smaller, faster. 
myObj 
= {
    type: 
'core'
}
;
If we are making an object containing methods that are bound to private variables and functions, it is still better to leave off the 
new prefix.
var foo = new function() {
    
function processMessages(message) {
        alert(
"Message: " + message.content);
    }

    
this.init = function() {
        subscribe(
"/mytopic"this, processMessages);
    }

}

By using 
new to invoke the function, the object holds onto a worthless prototype object. That wastes memory with no offsetting advantage. If we do not use the new, we don’t keep the wasted prototype object in the chain. So instead we will invoke the factory function the right way, using ().
var foo = function () {
    
function processMessages(message) {
        alert(
"Message: " + message.content);
    }

    
return {
        init: 
function () {
            subscribe(
"/mytopic"this, processMessages);
        }

    }
;
}
();
So the rule is simple: The only time we should use the 
new operator is to invoke a pseudoclassical Constructor function. When calling a Constructor function, the use of new is mandatory. 
There is a time to 
new, and a time to not. 

Prototype的深度探索
1 什麼是prototype

       JavaScript中對象的prototype屬性,可以返回對象類型原型的引用。這是一個相當拗口的解釋,要理解它,先要正確理解對象類型(Type)以及原型(prototype)的概念。
        前面我們說,對象的類(Class)和對象實例(Instance)之間是一種“創建”關係,因此我們把“類”看作是對象特徵的模型化,而對象看作是類特徵的具體化,或者說,類(Class)是對象的一個類型(Type)。例如,在前面的例子中,p1和p2的類型都是Point,在JavaScript中,通過instanceof運算符可以驗證這一點:
        p1 
instanceof Point
        p2 
instanceof Point

        但是,Point不是p1和p2的唯一類型,因爲p1和p2都是對象,所以Obejct也是它們的類型,因爲Object是比Point更加泛化的類,所以我們說,Obejct和Point之間有一種衍生關係,在後面我們會知道,這種關係被叫做“繼承”,它也是對象之間泛化關係的一個特例,是面向對象中不可缺少的一種基本關係。
        在面向對象領域裏,實例與類型不是唯一的一對可描述的抽象關係,在JavaScript中,另外一種重要的抽象關係是類型(Type)與原型(prototype)。這種關係是一種更高層次的抽象關係,它恰好和類型與實例的抽象關係構成了一個三層的鏈,下圖描述了這種關係:
        
//TODO:

        在現實生活中,我們常常說,某個東西是以另一個東西爲原型創作的。這兩個東西可以是同一個類型,也可以是不同類型。習語“依葫蘆畫瓢”,這裏的葫蘆就是原型,而瓢就是類型,用JavaScript的prototype來表示就是“瓢.prototype 
=某個葫蘆”或者“瓢.prototype= new 葫蘆()”。
        要深入理解原型,可以研究關於它的一種設計模式——prototype pattern,這種模式的核心是用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。JavaScript的prototype就類似於這種方式。

        關於prototype pattern的詳細內容可以參考《設計模式》(《Design Patterns》)它不是本文討論的範圍。

        注意,同類型與實例的關係不同的是,原型與類型的關係要求一個類型在一個時刻只能有一個原型(而一個實例在一個時刻顯然可以有多個類型)。對於JavaScript來說,這個限制有兩層含義,第一是每個具體的JavaScript類型有且僅有一個原型(prototype),在默認的情況下,這個原型是一個Object對象(注意不是Object類型!)。第二是,這個對象所屬的類型,必須是滿足原型關係的類型鏈。例如p1所屬的類型是Point和Object,而一個Object對象是Point的原型。假如有一個對象,它所屬的類型分別爲ClassA、ClassB、ClassC和Object,那麼必須滿足這四個類構成某種完整的原型鏈,例如:
        
//TODO:
        

        下面這個圖描述了JavaScript中對象、類型和原型三者的關係:
        
//TODO:

        有意思的是,JavaScript並沒有規定一個類型的原型的類型(這又是一段非常拗口的話),因此它可以是任何類型,通常是某種對象,這樣,對象
-類型-原形(對象)就可能構成一個環狀結構,或者其它有意思的拓撲結構,這些結構爲JavaScript帶來了五花八門的用法,其中的一些用法不但巧妙而且充滿美感。下面的一節主要介紹prototype的用法。



2 prototype使用技巧

      在瞭解prototype的使用技巧之前,首要先弄明白prototype的特性。首先,JavaScript爲每一個類型(Type)都提供了一個prototype屬性,將這個屬性指向一個對象,這個對象就成爲了這個類型的“原型”,這意味着由這個類型所創建的所有對象都具有這個原型的特性。另外,JavaScript的對象是動態的,原型也不例外,給prototype增加或者減少屬性,將改變這個類型的原型,這種改變將直接作用到由這個原型創建的所有對象上,例如:
 
<script>    
     
function Point(x,y)
      
{
         
this.x = x;
         
this.y = y;
     }

     
var p1 = new Point(1,2);
     
var p2 = new Point(3,4);
     Point.prototype.z 
= 0999//動態爲Point的原型添加了屬性
     alert(p1.z);
     alert(p2.z);  
//同時作用於Point類型創建的所有對象
 </script>


如果給某個對象的類型的原型添加了某個名爲a的屬性,而這個對象本身又有一個名爲a的同名屬性,則在訪問這個對象的屬性a時,對象本身的屬性“覆蓋”了原型屬性,但是原型屬性並沒有消失,當你用delete運算符將對象本身的屬性a刪除時,對象的原型屬性就恢復了可見性。利用這個特性,可以爲對象的屬性設定默認值,例如:
 
<script>
 
function Point(x, y)
  
{
     
if(x) this.x = x;
     
if(y) this.y = y;
 }

 Point.prototype.x 
= 0;
 Point.prototype.y 
= 0;
 
var p1 = new Point;
 
var p2 = new Point(1,2);
 
</script>


上面的例子通過prototype爲Point對象設定了默認值(
0,0),因此p1的值爲(0,0),p2的值爲(1,2),通過delete p2.x, delete p2.y; 可以將p2的值恢復爲(0,0)。下面是一個更有意思的例子:
 
<script>
 
function classA()
  
{
     
this.a = 100;
     
this.b = 200;
     
this.c = 300;
 
     
this.reset = function()
      
{
         
for(var each in this)
          
{
             
delete this[each];
         }

     }

 }

 classA.prototype 
= new classA();
 
 
var a = new classA();
 alert(a.a);
 a.a 
*= 2;
 a.b 
*= 2;
 a.c 
*= 2;
 alert(a.a);
 alert(a.b);
 alert(a.c);
 a.reset();   
//調用reset方法將a的值恢復爲默認值
 alert(a.a);
 alert(a.b);
 alert(a.c);
 
</script>


利用prototype還可以爲對象的屬性設置一個只讀的getter,從而避免它被改寫。下面是一個例子:
 
<script>
 
function Point(x, y)
  
{
     
if(x) this.x = x;
     
if(y) this.y = y;
 }

 Point.prototype.x 
= 0;
 Point.prototype.y 
= 0;
 
 
function LineSegment(p1, p2)
  
{
     
//私有成員
     var m_firstPoint = p1;
     
var m_lastPoint = p2;
      
var m_width = {
          valueOf : 
function(){return Math.abs(p1.x - p2.x)},
          toString : 
function(){return Math.abs(p1.x - p2.x)}
     }

      
var m_height = {
          valueOf : 
function(){return Math.abs(p1.y - p2.y)},
          toString : 
function(){return Math.abs(p1.y - p2.y)}
     }

     
//getter
     this.getFirstPoint = function()
      
{
         
return m_firstPoint;
     }

     
this.getLastPoint = function()
      
{
         
return m_lastPoint;
     }

 
      
this.length = {
          valueOf : 
function(){return Math.sqrt(m_width*m_width + m_height*m_height)},
          toString : 
function(){return Math.sqrt(m_width*m_width + m_height*m_height)}
     }

 }

 
var p1 = new Point;
 
var p2 = new Point(2,3);
 
var line1 = new LineSegment(p1, p2);
 
var lp = line1.getFirstPoint();
 lp.x 
= 100;  //不小心改寫了lp的值,破壞了lp的原始值而且不可恢復
 alert(line1.getFirstPoint().x);
 alert(line1.length); 
//就連line1.lenght都發生了改變
 </script>


將this.getFirstPoint()改寫爲下面這個樣子:
this.getFirstPoint = function()
{
        
function GETTER(){};
        GETTER.prototype 
= m_firstPoint;
        
return new GETTER();
}

則可以避免這個問題,保證了m_firstPoint屬性的只讀性。
 
<script>
 
function Point(x, y)
  
{
     
if(x) this.x = x;
     
if(y) this.y = y;
 }

 Point.prototype.x 
= 0;
 Point.prototype.y 
= 0;
 
 
function LineSegment(p1, p2)
  
{
     
//私有成員
     var m_firstPoint = p1;
     
var m_lastPoint = p2;
      
var m_width = {
          valueOf : 
function(){return Math.abs(p1.x - p2.x)},
          toString : 
function(){return Math.abs(p1.x - p2.x)}
     }

      
var m_height = {
          valueOf : 
function(){return Math.abs(p1.y - p2.y)},
          toString : 
function(){return Math.abs(p1.y - p2.y)}
     }

     
//getter
     this.getFirstPoint = function()
      
{
              
function GETTER(){};
             GETTER.prototype 
= m_firstPoint;
             
return new GETTER();
     }

     
this.getLastPoint = function()
      
{
              
function GETTER(){};
             GETTER.prototype 
= m_lastPoint;
             
return new GETTER();
     }

 
      
this.length = {
          valueOf : 
function(){return Math.sqrt(m_width*m_width + m_height*m_height)},
          toString : 
function(){return Math.sqrt(m_width*m_width + m_height*m_height)}
     }

 }

 
var p1 = new Point;
 
var p2 = new Point(2,3);
 
var line1 = new LineSegment(p1, p2);
 
var lp = line1.getFirstPoint();
 lp.x 
= 100;  //不小心改寫了lp的值,但是沒有破壞原始的值
 alert(line1.getFirstPoint().x);
 alert(line1.length); 
//line1.lenght不發生改變
 
 
</script>



實際上,將一個對象設置爲一個類型的原型,相當於通過實例化這個類型,爲對象建立只讀副本,在任何時候對副本進行改變,都不會影響到原始對象,而對原始對象進行改變,則會影響到副本,除非被改變的屬性已經被副本自己的同名屬性覆蓋。用delete操作將對象自己的同名屬性刪除,則可以恢復原型屬性的可見性。下面再舉一個例子:
<script>
function Polygon()
{
//http://www.livebaby.cn
var m_points = [];
m_points 
= Array.apply(m_points, arguments);
function GETTER(){};
GETTER.prototype 
= m_points[0];
this.firstPoint = new GETTER();
this.length = {
valueOf : 
function(){return m_points.length},
toString : 
function(){return m_points.length}
}

this.add = function(){
m_points.push.apply(m_points, arguments);
}

this.getPoint = function(idx)
{
return m_points[idx];
}

this.setPoint = function(idx, point)
{
if(m_points[idx] == null)
{
m_points[idx] 
= point;
}

else
{
m_points[idx].x 
= point.x;
m_points[idx].y 
= point.y;
}

}

}

var p = new Polygon({x:1, y:2},{x:2, y:4},{x:2, y:6});
alert(p.length);
alert(p.firstPoint.x);
alert(p.firstPoint.y);
p.firstPoint.x 
= 100//不小心寫了它的值
alert(p.getPoint(0).x);  //不會影響到實際的私有成員
delete p.firstPoint.x; //恢復
alert(p.firstPoint.x);
p.setPoint(
0{x:3,y:4}); //通過setter改寫了實際的私有成員
alert(p.firstPoint.x);  //getter的值發生了改變
alert(p.getPoint(0).x);
</script>


注意,以上的例子說明了用prototype可以快速創建對象的多個副本,一般情況下,利用prototype來大量的創建複雜對象,要比用其他任何方法來copy對象快得多。注意到,用一個對象爲原型,來創建大量的新對象,這正是prototype pattern的本質。
下面是一個例子: 
 
<script>
//http://www.livebaby.cn
 var p1 = new Point(1,2);
 
var points = [];
  
var PointPrototype = function(){};
 PointPrototype.prototype 
= p1;
 
for(var i = 0; i < 10000; i++)
  
{
 points[i] 
= new PointPrototype();
 
//由於PointPrototype的構造函數是空函數,因此它的構造要比直接構造//p1副本快得多。
 }

 
</script>


除了上面所說的這些使用技巧之外,prototype因爲它獨特的特性,還有其它一些用途,被用作最廣泛和最廣爲人知的可能是用它來模擬繼承,關於這一點,留待下一節中去討論。

3 prototype的實質

        上面已經說了prototype的作用,現在我們來透過規律揭示prototype的實質。
        我們說,prototype的行爲類似於C
++中的靜態域,將一個屬性添加爲prototype的屬性,這個屬性將被該類型創建的所有實例所共享,但是這種共享是隻讀的。在任何一個實例中只能夠用自己的同名屬性覆蓋這個屬性,而不能夠改變它。換句話說,對象在讀取某個屬性時,總是先檢查自身域的屬性表,如果有這個屬性,則會返回這個屬性,否則就去讀取prototype域,返回protoype域上的屬性。另外,JavaScript允許protoype域引用任何類型的對象,因此,如果對protoype域的讀取依然沒有找到這個屬性,則JavaScript將遞歸地查找prototype域所指向對象的prototype域,直到這個對象的prototype域爲它本身或者出現循環爲止,我們可以用下面的圖來描述prototype與對象實例之間的關係:
        
//TODO:

4 prototype的價值與侷限性

        從上面的分析我們理解了prototype,通過它能夠以一個對象爲原型,安全地創建大量的實例,這就是prototype的真正含義,也是它的價值所在。後面我們會看到,利用prototype的這個特性,可以用來模擬對象的繼承,但是要知道,prototype用來模擬繼承儘管也是它的一個重要價值,但是絕對不是它的核心,換句話說,JavaScript之所以支持prototype,絕對不是僅僅用來實現它的對象繼承,即使沒有了prototype繼承,JavaScript的prototype機制依然是非常有用的。
        由於prototype僅僅是以對象爲原型給類型構建副本,因此它也具有很大的侷限性。首先,它在類型的prototype域上並不是表現爲一種值拷貝,而是一種引用拷貝,這帶來了“副作用”。改變某個原型上引用類型的屬性的屬性值(又是一個相當拗口的解釋:P),將會徹底影響到這個類型創建的每一個實例。有的時候這正是我們需要的(比如某一類所有對象的改變默認值),但有的時候這也是我們所不希望的(比如在類繼承的時候),下面給出了一個例子:
 
<script>
 
function ClassA()
  
{
 
this.a=[];
 }

 
function ClassB()
  
{
  
this.b=function(){};
 }

 ClassB.prototype
=new ClassA();
 
var objB1=new ClassB();
 
var objB2=new ClassB();
 objB1.a.push(
1,2,3);
 alert(objB2.a);
 
//所有b的實例中的a成員全都變了!!這並不是這個例子所希望看到的。
 </script>


淺析Javascript中繼承和Prototype的關係

javascript中支持類的定義,而且定義的方式與函數基本上也相同。
1  function out(val){
2   document.write(val+"<br>");
3 }
;
4 
5  function BaseClass() {
6   this.a="I'm BaseClass.a .";
7 }
;
第一行的內容可以看成是一個函數,第五行可以看成是一個類。

     我們繼續,現在我們來看看Javascript 中的繼承,以及 Prototype  與繼承的關係。先來看看下面這個代碼。你能想出運行的結果嗎?
 
1 <script>
 
2  // author: http://meil.livebaby.cn 
 3 function out(val){
 
4   document.write(val+"<br>");
 
5 }
;
 
6 
 
7 function BaseClass() {
 
8   this.a="I'm BaseClass.a .";
 
9 }
;
10 BaseClass.prototype.b="I'm BaseClass.prototype.b .";
11 BaseClass.c="I'm BaseClass.c .";
12 
13 var cls1=function(){
14   this.a="I'm cls1.a .";
15 }
;
16 cls1.prototype.b="I'm cls1.prototype.b .";
17 cls1.c="I'm cls1.c .";
18 
19 var cls2=function(){};
20 cls2.prototype=cls1.prototype;
21 
22 out("BaseClass<br>");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("<hr>");
28 
29 out("cls1<br>");
30 out(cls1.a);
31 out(cls1.b);
32 out(cls1.c);
33 out("<hr>");
34 
35 out("new cls1<br>");
36 out((new cls1).a);
37 out((new cls1).b);
38 out((new cls1).c);
39 out("<hr>");
40 
41 out("cls2<br>");
42 out((new cls2).a);
43 out((new cls2).b);
44 out((new cls2).c);
45 
46 </script>


運行結果:

BaseClass

I
'm BaseClass.a .
I
'm BaseClass.prototype.b .
undefined
I
'm BaseClass.c .
________________________________________
cls1

undefined
undefined
I
'm cls1.c .
________________________________________
new cls1

I
'm cls1.a .
I
'm cls1.prototype.b .
undefined
________________________________________
 cls2

undefined
I
'm cls1.prototype.b .
undefined


哈哈!有點暈了!?好像不太一樣。

下面來分析一下:

1.先看看這幾行:
22 out("BaseClass<br>");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("<hr>");

25行是調用了對象的c屬性,類中沒有定義,所以“undefined”
26行直接調用了,類的靜態屬性,就正常顯示了
其他的大家應該都明白了,就不多說了。

2.繼續
30 out(cls1.a);
31 out(cls1.b);
32 out(cls1.c);

首先大家應該清楚cls1在這裏是類,那就明瞭。這裏cls1只有一個靜態屬性,就是c,其他的屬性只能通過它的對象訪問。用類名來訪問對不起,找不到只能顯示“undefined”,看下面的代碼就清楚了。

3.繼續
36 out((new cls1).a);
37 out((new cls1).b);
38 out((new cls1).c);

你不是說得用對象訪問嗎?我new這回可以了吧?恩!沒問題?
不過不是都沒問題這個不行-- out((new cls1).c); 那個是類的靜態屬性用這個  32 out(cls1.c); 就OK。

4.繼續
41 out("cls2<br>");
42 out((new cls2).a);
43 out((new cls2).b);
44 out((new cls2).c);

這個的結果有點疑惑,先等等。看看我們是怎麼寫的

cls2.prototype=cls1.prototype;

哦!用prototype來繼承的,對!
a是不能繼承的,c是靜態的也不能被繼承。

5.在補充點內容,讓你根多的瞭解JavaScript中繼承的特性
1 var cls3=function(){};
2 cls3.prototype=BaseClass.prototype;

4 cls3.prototype.d="I
'm cls3"
5 out((new cls3).d);
6 out((new BaseClass).d);
 運行結果:
I'm cls3
I'm cls3

原來子類對象裏可以同時變更父對象中的屬性!強吧!!!哈哈!

結束!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章