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>网页陶吧
  同一页面内的引用方法:
  
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>fishtitle> 
<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+"
");
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+"
");
 
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
");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("
");
28 
29 out("cls1
");
30 out(cls1.a);
31 out(cls1.b);
32 out(cls1.c);
33 out("
");
34 
35 out("new cls1
");
36 out((new cls1).a);
37 out((new cls1).b);
38 out((new cls1).c);
39 out("
");
40 
41 out("cls2
");
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
");
23 out((new BaseClass).a);
24 out((new BaseClass).b);
25 out((new BaseClass).c);
26 out(BaseClass.c);
27 out("
");

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
");
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

原来子类对象里可以同时变更父对象中的属性!强吧!!!哈哈!

结束!

你可能感兴趣的:(javascript)