面向对象程序员JavaScript指南(2)

来源:互联网 发布:js获取url端口号 编辑:程序博客网 时间:2024/05/22 03:35

   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 返回值64hectares ,那么addreas ()将会返回96 。如果我不使用parseFloat ,就将得到容易被误解的值3264hectares!
      概括来说,鸭子类型使事情保持简单,但是要求你相信开发团队能够明白所有的细节。鸭子类型在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方法来使用。这种技术被MikeFoster的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' onclick='alert:alert(this.id) '></div>
      要么使用编程方式来声明,例如:
          function clickHandler() { alert(this.id); }
          myDiv.onclick=clickHandler;
      注意,在编程方式的情况下,我们传递的是一个对Function 对象的引用(在clickHandler后面没有())。当在HTML 中声明函数时,我们有效地以内嵌方式声明了个匿名函数,等同于:
          myDiv.onclick=function(){ alert(this.id); }
      注意,在两种情况下,都没有为函数分配参数,也没有任何方式可以伴随鼠标的点击传递参数。然而,当点击DOM 元素的时候. Event对象作为了函数调用的参数,元素本身作为上下文对象。知道这一点可以大大减少麻烦和困惑,特别是当你在编写面向对象的代码时。混乱的关键之源在于DOM节点总是作为上下文来传递,甚至在函数附加到一个不同对象的原型上的时候。在下面的例子中,我们定义了一个简单的对象,该对象带有一个它所知道的可视GUI 元素的事件处理函数。我们可以把这个对象看作是MVC 术语中的模型,事件处理函数看作是控制器,DOM 元素是视图。

          function MyObj (id , div) {

                 this.id=id;

                 this.div=div;

                 this.div.onclick=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 是一种系统开销,但是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.onclick=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 展示了这种方法。

通过在DOM节点上附件模型的引用来在事件处理函数中发现和引用模型

     这种方法相当大地简化了事件处理函数的工作。模型对象的构造函数不再需要专门的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.onclick=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 = newDate()),其作用域限制在调用它的块中(即构造函数中)。在构造函数的第二行,我们定义了函数getAge()。注意,这里我们在一个函数中定义了另一个函数,内部函数使用的本地变量createTime属于外部函数的作用域。通过做这件事(没有做任何其他事情),我们实际上创建了闭包。如果我们定义了一个机器人,在页面加载完成的时刻询问它的"年龄"。
         var robbie=new Robot();
         window.onload=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.onclick=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找到。

原创粉丝点击