读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)

读书笔记:编写高质量代码--web前端开发修炼之道

这本书看得断断续续,不连贯,笔记也是有些马虎了,想了解这本书内容的童鞋可以借鉴我的这篇笔记,希望对大家有帮助。

笔记有点长,所以分为一,二两个部分:

第一部分:1-4章的笔记

第二部分:5章以及一些总结性的建议笔记

转载本文,请带上本文地址(http://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html),谢谢:)

 

第五章:高质量的JavaScript
1、团队合作--如何避免js冲突
   a 使用匿名函数控制变量作用域
     "(function(){})()":这种形式很巧妙,先定义一个匿名函数,然后立即执行它。
   b 利用全局作用域的变量在各匿名函数间搭起桥梁
     需要严格控制全局变量的数量,大量滥用就违背了使用匿名函数的初衷。
   c 命名空间

View Code
     script
     (function(){
       var a = 123, b='hello world';
       GLOBAL.A = {};
       GLOBAL.A.CAT = {};
       GLOBAL.A.CAT.name = a;
       GLOBAL.A.CAT.sayName = function(){
         alert(GLOBAL.A.CAT.name);
       };
     });

   d 代码注释
     添加必要的注释,可以大大提高代码的可维护性,对于团队合作来说,更是十分有必要的。
2、给程序一个统一的入口--window.onload 和 DOMReady
3、extend
   javascript 是支持面向对象的语言,但是它并不提供 extend 方法用于继承。我们自己定义:

View Code
   script:
   function extend(subClass, superClass){
     var F = function(){};
     F.prototype = superClass.prototyte;
     subClass.prototyte = new F();
     subClass.prototyte.constructor = subClass;
     subClass.superClass = superClass.prototyte;
     if(superClass.prototyte.constructor == Object.prototype.constructor){
       superClass.prototype.constructor = superClass;
     }
     function Animal(name){
       this.name = name;
       this.type = 'animal';
     }
     Animal.prototype = {
       say: function(){
         alert('I\'m a(an) '+ this.type +', my name is '+ this.name);
       }
     };
     function Bird(name){
       this.constructor.superClass.constructor.apply(this, arguments);
       this.type = 'brid';
     }
     extend(Bird, Animal);
     Bird.prototype.fly = function{
       alert('I\'m flying');
     }
     var canary = new Brid('xiaocui');
     canary.say();// I'm a(an) bird, my name is xiaocui
     canary.fly();// i'm flying

4、保持代码弹性
5、可复用性
   组件需要一个根节点,以保持每个组件之间的独立性
6、避免产生副作用
7、通过传参实现定制
   如果一个函数内某个因素很不稳定,我们可以将它从函数内部分离出来,以参数的形式传入,从而将不稳定因素和函数解耦。
8、控制 this 关键字的指向
   在javascript里,this指针的确是让人捉摸不透的东西。例如javascript伪协议和内联时间对于this的指向不同:

View Code
   html:
   // 弹出 “A”
   <a href="#" onclick="alert(this.tagName)">click me</a>
   // 弹出“underfined”
   <a href="javascript:alert(this.tagName)">click me</a>
   // 弹出“true”
   <a href="javascript:alert(this==window)">click me</a>
   setTimeout 和 setInterval 也会改变 this 的指向,如下:
   javascript:
   var name = "somebody";
   var adang = {
     name: 'adang',
     say: function(){
       alert('I\'m '+ this.name);
     }
   };
   adang.say();// I'm adang
   setTimeout(adang.say, 1000);// I'm somebody
   setInterval(adang.say, 100);// I'm somebody
   另外,"DomNode.onXXX" 也会改变 this 的指向,如:
   javascript:
   var name = "somebody";
   var btn = document.getElementById('btn');
   var adang = {
     name: 'adang',
     say: function(){
       alert('I\'m '+ this.name);
     }
   };
   btn.onclick = adang.say;// I'm BUTTON

   使用匿名函数可以解决这个问题,如下:

View Code
   javascript:
   var name = "somebody";
   var btn = document.getElementById('btn');
   var adang = {
     name: 'adang',
     say: function(){
       alert('I\'m '+ this.name);
     }
   };
   adang.say();// I'm adang
   setTimeout(function(){adang.say();}, 1000);// I'm adang
   setInterval(function(){adang.say();}, 1000);// I'm adang
   btn.onclick = function(){adang.say();};// I'm adang
   setTimeout(function(){alert(this == window);}, 1000);// true
   btn.onclick = function(){alert(this == btn);};// true

   setTimeout, setInterval 和 DomNode.onXXX 改变的都是直接调用函数里的 this 的指向,其中 setTimeout 和 setInterval 将直接调用的函数里的 this 指向 window。DomNode.onXXX 将直接调用的函数里的 this 指向 DomNode。使用匿名函数将我们的处理函数封装起来,可以将我们的处理函数由直接调用变成通过匿名函数间接调用。
   另外,还可以通过 call 和 apply 函数来改变处理函数的 this 指向,如:

View Code
   javascript:
   var name = "somebody";
   var btn = document.getElementById('btn');
   var adang = {
     name: 'adang',
     say: function(){
       alert('I\'m '+ this.name);
     }
   };
   adang.say.call(btn);// I'm BUTTON
   setTimeout(function(){adang.say.call(btn);}, 1000);// I'm BUTTON
   setInterval(function(){adang.say.call(btn);}, 1000);// I'm BUTTON
   btn.onclick = function(){adang.say.apply(btn);};// I'm BUTTON

   在 javascript 里使用继承就需要用到 call 或 apply 函数。
   在 this 改变指向之前,将它指向的对象保存到一个变量中也是常用的方法,如:

View Code
   html:
   <input type="button" value="click me" id="btn" name="BUTTON" />
   javascript:
   var name = "somebody";
   var adang = {
     name: 'adang',
     say: function(){
       alert('I\'m '+ this.name);
     },
     init: function(){
       // this 指向 adang 对象
       var This = this;
       document.getElementById('btn').onclick = function(){
         // this 指向 btn 的 DOM 节点,This 指向 ading 对象
     This.say();// I'm adang
     this.say();// 报错,this.say is not a function
       };
     }
   };
   adang.init();

   this 关键字会改变指向,只要避开这个关键字就可以得到一个稳定的引用。
9、预留回调接口
10、变成中的DRY规则
   DRY:don't repeat yourself,强调在程序中不要将相同的代码重复编写多次,更好的做法是只写一次,然后多处引用。(减少代码量,方便修改维护)
11、用 hash 对象传参
   使用 {key: value, xxx,} 对象传递参数
12、面向过程编程和面向对象编程
   面向过程:
     将程序分成“数据”和“处理函数”两部分,程序以“处理函数”为核心,如果要执行什么操作,就将“数据”传给响应的“处理函数”,返回我们需要的结果。
   面向过程有三个方面的问题:
     a 数据和处理函数没有直接的关联,在执行操作的时候,我们不但要选择相应的处理函数,还要自己准备处理函数需要的数据,也就是说,在执行才做时,我们需要同时关注处理函数和数据。
     b 数据和处理函数都暴露在同一作用域内,没有私有和公有的概念,整个程序中所有的数据和处理函数都可以互相访问,在开发阶段初期也许开发速度会很快,但到了开发后期和维护阶段,由于整个程序耦合得非常紧密,任何一个处理函数和数据都有可能关联到其他地方,容易牵一发而动全身,从而加大了修改难度。
     c 面向过程的思维方式是典型的计算机思维方式--输入数据给处理器,处理器内部执行运算,处理器返回结果。而实际生活中,我们的思路却不是这样.
   面向对象(Object Oriented):
     抛开计算机思维,使用生活中的思维进行编程。
     面向过程的思维是描述一个个“动作”,而面向对象的思维就是描述一个个“物件”,客观生活中的物件,都可以通过面向对象思维映射到程序中--“物件”对应“对象”,“状态”对应“属性”,“行为”对应“动作”。
     面向过程编程:

View Code
     javascript:
     var name = 'adang', state = 'awake';
     var say = function(oName){
       alert('I\'m '+ oName);
     };
     var sleep = function(oState){
       oSate = 'asleep';
     };
     say(name);
     sleep(state);
     面向对像编程:
     javascript:
     var adang = {
       name: 'adang',
       state: 'awake',
       say: function(){
         alert('I\'m '+ this.name);
       },
       sleep: function(){
         this.state = 'asleep';
       }
     };
     adng.say();
     adang.sleep();

   面向对象(Object Oriented),简称OO。OO其实包括OOA(Object Oriented Analysis,面向对象分析)、OOD(Object Oriented Design,面向对象设计)和OOP(Object Oriented Programming,面向对象的程序设计)。面向对象的语法只对应OOP,只是OO的一部分。
     一个典型的OO编程过程应该是先整理需求,根据需求进行OOA,将真实世界的客观物件抽象成为程序中的类或对象,这个过程经常会用到的是UML语言,也称UML建模,OOA的输出结果是一个个类或对象的模型图。接下来要进行OOD,这一步的目的是处理类之间的耦合关系,设计类或对象的接口,此时会用到各种设计模式,例如观察者模式、责任链模式等。OOA和OOD是个反复迭代的过程,他们本身没有清晰的边界,是互相影响、制约的。等OOA和OOD结束之后,才到OOP,进行实际的编码工作。
     OOA和OOD是面向对象编程的思想和具体的语言无关,而OOP是面向兑现编程的工具,和选用的语言相关。OOP是使用面向对象技术的基础,面向对象的思维最后是要通过OOP来实施的。
   javascript的面向对象编程
     javascript是基于原型的语言,通过 new 实例化出来的对象,其属性和行为来自于两部分,一部分来自于构造函数,另一部分来自于原型。
   共有和私有

View Code
     javascript:
     // 定义 Animal 类
     function Animal(name){
       // 共有属性
       this.name = name || 'xxx';
       this.type = 'animal';
       // 私有属性
       var age = 20;
       // 私有方法
       var more = function(){
         alert('I\'m moving now');
       }
     }
     Animal.prototype = {
       // 公有方法
       say: function(){
         alert('I\'m a(an) '+ this.type +', my name is '+ this.name +',I\'m '+ age);
       },
       act: function(){
         move();
       }
     };
     // 实例化Animal类
     varmyDog = new Animal('wangcai');
     myDog.say();// 报错,age未定义
     myDog.act();// 报错,move is not defined

     从上我们可以知道,公有方法不能访问私有属性和私有行为,那么如何解决呢?最好的方法就是把所有的公有方法都私有化(都写在类的构造函数里面),这样属性和行为都共同作用在构造函数的作用域里,如下:

View Code
     javascript:
     function Animal(name){
       this.name = name || 'xxx';
       this.style = 'animal';
       var age = 20;
       var move = function(){
         alert('xxxx');
       };
       this.say = function(){
         alert('xxxx');
       };
       this.act = function(){
         move();
       };
     }
     Animal.prototype = {};
     var myDog = new Animal('waicai');
     myDog.say();
     myDog.act();

     将所有属性和行为全部写在构造函数里,的确方便,但并不推荐这么做。因为一个类的原型在内存中只有一个,写在原型中的行为,可以被所有实例所共享,实例化的时候,并不会在实例的内存中复制一份,而写在类里的行为,实例化的时候会在每个实例里复制一份,占用更多的内存空间。
     写在原型中的行为一定是公有的,而且无法访问私有属性,所以如何处理私有行为和私有属性是个难题。一般来说,如果对属性和行为的私有性有非常高的强制性,比如说多人合作,为了确保维护不会出现问题,在开发之初明确各个类的接口,除了必要的接口设为共有,其他所有接口一律设为私有,以此来降低类之间的耦合程度,确保可维护性,这时我们不得不牺牲内存,将私有行为放在构造函数里,实现真正的私有;
     命名来确定私有,比如 this._age = 20;
     监听属性的 valueChange:

View Code
     javascript:
     function Animal(name){
       var name = name, type = 'animal';
       var _age = 20;
       var master = 'adang';// 添加 master 属性,默认为adang
       this.getName = function(){
         return name;
       };
       this.setName = function(o){
         if(o != 'waicai' && o != 'xiaoqiang'){
           alert('您设置的那么值不合要求');
           return;
         }
         name = o;
         this._valueChangeHandler('name');// 触发 name 属性的 valueChange 事件
       };
       this.getMaster = function(){// master 属性的获取方法
         return master;
       };
       this.setMaster = function(o){// master 属性的设置方法
         master = o;
         thi._valueChangeHandler('master');// 触发 master 属性的 valueChange 事件
       };
       this.getType = function(){};
       this.setType = function(o){alert('赋值失败,Animal类的 type 属性是只读的 ');};
       this._getAge = function(){return _age;};
       this._setAge = function(o){_age = o;};
     }
     Animal.prototype = {
       _move: function(){
         alert('I\'m moving now');
       },
       say: function(){
         alert('I\'m a(an) '+ this.getType() +', my name is '+ this.getName() +', I\'m '+ this._getAge());
       },
       act: function(){
         this._move();
       },
       onChange: function(valueName, fun){// 公有行为,用于注册属性的 valueChange 事件
         this['_'+ valueName +'ChangeHandlers'] = this['_'+ valueName +'ChangeHandlers'] || [];
         this['_'+ valueName +'ChangeHandlers'].push(fun);
       },
       _valueChangeHandler: function(valueName){
         var o = this['_'+ valueName +'ChangeHandlers'];
         if(o){
           for(var i=0,n=o.length; i<n; i++){
             var methodName = 'get'+ valueName.charAt(0).toUpperCase() + valueName.slice(1);
             o[i](this[methodName]);// 把 this.getType() 作为属性来调用,返回值作为参数传递
           }
         }
       }
     };
     var myDog = new Animal('wangcai');
     // 给 myDog 注册 name 属性的 valueChange 事件
     myDog.onChange('name', function(o){
       if(o == 'xiaoqiang'){
         alert('1');
       }else{
         alert('2');
       }
     });
     // 给 myDog 换个新名字 xiaoqiang
     myDog.setName('xiaoqiang');// 1
     //给 myDog 再注册一个 name 属性的 valueChange 事件
     myDog.onChange('name', function(o){
       alert('my new name is '+ o);
     });
     ..........

     在真实世界中,我们很多的思维习惯都是状态驱动的,编程时监听属性的 valueChage 事件可以帮助我们更接近真实世界的思维习惯。
13、继承
   正统的面向对象的语言都会提供 extend 之类的方法用于处理类的继承,但javascript并不提供 extend 方法。
   在javascript中实例的属性和行为是由构造函数和原型两部分共同组成的,我们定义2个类:Animal 和 Bird ,他们在内存中的表现如下:
   图例:
     Animal类        Animal的构造函数        Animal的原型
     Bird类          Bird的构造函数          Bird的原型
   二者互不联系。
   如果想让 Bird 继承自 Animal,那么我们需要把 Animal 构造函数和原型中的属性和行为全部传给Bird的构造函数和原型。参考以下代码:

View Code
   javascript:
   // 先定义 Animal 类
   function Animal(name){
     this.name = name, this.type = 'animal';
   }
   Animal.prototype = {
     say: function(){
       alert('I\'m a(an) '+ this.type +', my name is '+ this.name);
     }
   };
   // 我们再定义一个类 Bird
   // example 1
   function Bird(name){
     Animal(name);
   }
   Bird.prototype = {};
   // 实例化Bird对象
   var myBird = new Bird('xiaocui');
   alert(myBird.type);// undefined

   为什么这个会错呢?
   在javascript中,function 有 2 种不同的用法:
   a 作为函数存在,直接使用“()”进行调用,例如:function test(){}; test(); test 被用作函数,直接被 “()”符号调用
   b 作为类的构造函数存在,使用 new 调用,例如“function test(){}; new test();” test 作为类的构造函数,通过 new 进行 test 类的实例化。
   这 2 种方法的调用,function 内部的 this 指向会有所不同---作为函数的 function,其 this 指向的是 window 对象,而作为类构造函数的 function,其 this 指向的是实例对象。
   让 Animal 内部的 this 指向 Bird 类的实例,可以通过 call 或 apply 方法实现,如下:

View Code
   javascript:
   // example 2
   function Bird(name){
     Animal.call(this, name);
   }
   Bird.prototype = {};
   // 实例化Bird对象
   var myBird = new Bird('xiaocui');
   alert(myBird.type);// animal

   构造函数的属性和行为已经实现了继承,接下来我们要实现原型中属性和行为的继承。

View Code
   javascript:
   // example 3
   function Bird(name){
     Animal.call(this, name);
   }
   Bird.prototype = Animal.prototype;
   Bird.prototype.fly = function(){
     alert('I\'m flying');
   };
   // 实例化Bird对象
   var myBird = new Bird('xiaocui');
   myBird.say();// xxxxx
   myBird.fly();// I'm flying
   var myDog = new Animal('wangcai');
   myDog.fly();// I'm flying

   我们只想给 Bird 类添加 fly 行为,为什么 Animal 类也获得了 fly 行为呢?这涉及 传值 和 传址 两个问题---在javascript中,赋值语句会用 传值 和传址 两种不同的方式进行赋值,如果是数值型、布尔型、字符型等基本数据类型,在进行赋值时会将数据复制一份,将复制的数据进行赋值,也就是通常所说的传值,如果是数组、hash对象等复杂数据类型(数组、hash对象可包括简单类型数据),在进行赋值时会直接用内存地址赋值,而不是将数据复制一份,这就是通常所说的 传址。eg:

View Code
   javascript:
   var a = 10, b = a;// 基本数据类型
   b++;
   var c = [1, 2, 3], d = c;
   d.push(4);
   alert(a);// 10
   alert(b);// 11
   alert(c.join(','));// 1,2,3,4
   alert(d.join(','));// 1,2,3,4

   c, d 指向同一份数据地址,所以改变其中一个,另外一个也会改变,而基本数据类型不会。
   那么如何复制对象呢?最简单的做法是遍历数组或者hash对象,将数组或hash对象这种复杂的数据拆分成一个个简单数据,然后分别赋值,eg:

View Code
   javascript:
   var a = [1, 2, 3], b = {name: 'adang', sex: 'male', tel: '123456'};
   var c = [], d = {};
   for(var p in a){
     c[p] = a[p];
   }
   for(var p in b){
     d[p] = b[p];
   }
   c.push(4);
   d.email = 'xxx@gmail.com';
   alert(a);// 1,2,3
   alert(c);// 1,2,3,4
   alert(b.email);// undefined
   alert(d.email);// xxx@gmail.com

   prototype 本质上也是个 hash 对象,所以直接使用它赋值时会进行传址,这也是为什么子类扩张prototype,而父类也会带上子类的prototype。我们可以用 for in 来遍历prototype,从而实现prototype的传值。但因为 prototype 和 function(用作类的function)的关系,我们还有另一种方法实现prototype的传值---new SomeFunction(),eg:

View Code
   javascript:
   // example 4
   function Bird(name){
     Animal.call(this, name);
   }
   Bird.prototype = new Animal();// 这里不是很明白,new 一个父类实例赋值给 Bird.prototype 就能实现 原型 的继承了吗?如果是,是为什么呢?
   Bird.prototype.constructor = Bird;
   Bird.prototype.fly = function(){
     alert('I\'m flying');
   };
   // 实例化Bird对象
   var myBird = new Bird('xiaocui');
   myBird.say();// xxxxx
   myBird.fly();// I'm flying
   var myDog = new Animal('wangcai');
   myDog.fly();// 报错,myDog.fly is not a function

   我们发现这里有这么一句:Bird.prototype.constructor = Bird; 这是因为 Bird.prototype = new Animal(); 时,Bird.prototype.constructor 指向了 Animal,我们需要将它纠正,重新指回 Bird。
   这样的方式可以顺利的实现javascript的继承,但是我们还可以进一步将它们进行封装,定义个 extend 函数:

View Code
   javascript:
   function extend(subClass, superClass){
     var F = function(){};// 作为class的函数
     F.prototype = superClass.prototype;
     subClass.prototype = new F();
     subClass.prototype.constructor = subClass;// 指回来
     subClass.superclass = superClass.prototype;
     if(superClass.prototype.constructor == Object.prototype.constructor){
       superClass.prototype.constructor = superClass;
     }
   }

   function Animal(name){
     this.name = name;
     this.type = 'animal';
   }
   Animal.prototype = {
     say: function(){
       alert('I\'m a(an) '+ this.type +' , my name is '+ this.name);
     }
   };
   function Bird(name){
     this.constructor.superclass.constructor.apply(this, arguments);
     this.type = 'Bird';
   }
   extend(Bird, Animal);
   Bird.prototype.fly = function(){
     alert('I\'m flying');
   }

   var canary = new Bird('xiaocui');
   canary.say();// .........
   canary.fly();// I'm flying

14、用面向对象方式重写代码
   下面以 电话本程序 为例.
   首先,我们要进行 OOA,从现实逻辑中抽象出类。使用面向过程的编程方式时,处理函数是非常核心的部分,在命名的时候它很可能是个动词,例如 getTel、addItem,而面向对象的编程方式,最核心的部分是类,类的命名往往是个名词,例如 Animal、Bird。我们说明面向对象编程的时候,往往用一些客观世界真实存在的东西来举例,例如人、猫、狗,但类不一定是客观存在的某个物件,例如这次我们要写的电话本程序,就很难对应到客观设计界的某个真实存在的物件,它更像是一个逻辑上的物件,管理者关于电话记录的所有逻辑。它保存着许多电话记录,它可以用来添加、删除和查询电话记录。我们给它取个什么名字好呢?PhonebookManager,电话本管理者。
   现在我们首先要明确类名以及类的接口,至于接口里的具体逻辑如何实现,我们暂时先不管,在 OOA 这一步,我们只需要用 UML 语言将类描述出来即可,如下图:
   UML图:
   |--------------------|
   |  PhonebookManager  |
   |--------------------|
   |  -phonebook:hash   |
   |--------------------|
   |  +getTel():string  |
   |  +addItem():void   |
   |  +removeItem():void|
   |--------------------|
   UML 描述类的方式很简单,一个方框代表一个类,将方框划成上中下三栏,第一栏填入类名,第二栏填入类的属性,第三栏填入类的行为,其中公有属性和公有行为需要在属性和行为名前加上“+”号,私有属性和行为需要加上“-”号。
   在本例中,PhonebookManager 类有一个电话本属性 phonebook,记录着保存在 PhonebookManager 中的电话记录,有 getTel、addItem、removeItem行为,分别用于查询电话、添加记录和删除记录。因为我们需要让类的对外提供的接口尽可能的少,除了必要的接口应该设为公有,其他的都应该设为私有。
   一般来说,OOA结束之后,我们需要进行OOD,但本例实在太简单了,用不上OOD。只有一些复杂的逻辑处理才可能用得上OOD。
   接下来,我们要将OOA和OOD的成果用程序编写出来,也就是OOP的环节。

View Code
   javascript:
   // 定义电话本管理类
   function PhonebookManager(o){
     this._phonebook = o;
   }
   PhonebookManager.pototype = {
     // 查询电话
     getTel: function(oName){
       var tel = '';
       for(var i=0; i<this._phonebook.length; i++){
         if(this._phonebook[i].name == oName){
           tel = this._phonebook[i].tel;
           break;
         }
       }
       return tel;
     },
     // 添加记录
     addItem: function(oName, oTel){
       this._phonebook.push({name: oName, tel: oTel});
     },
     // 删除记录
     removeItem: function(oName){
       var n;
       for(var i=0; i<this._phonebook.length; i++){
         if(this._phonebook[i].name == oName){
           n = i;
           break;
         }
       }
       if(n != undefined){
         this._phonebook.splice(n, 1);
       }
     }
   };
   使用:
   javascript:
   var myPhonebookManager = new PhonebookManager({
     {name: 'zhang', tel: '111'},
     {name: 'wang', tel: '222'}
   });
   var zhangTel = myPhonebookManager.getTel('zhang');
   myPhonebookManager.addItem('huang', '333');
   myPhonebookManager.removeItem('wang');

   电话本的重构已经完成了,进行tab 的重构。
   (略)
15、prototype 和内置类
   javascript语言中提供了一些内置类,包括 Array、String、Function等,它们剔红了javascript的大部分基本数据类型。这些内置类通常会提供一些方法和属性,例如 Array 类提供的 length 属性,push、pop方法,String提供length属性,replace、split方法,Function 提供 call、apply方法等。
   需要说明的是,这些内置类不一定需要通过 new 的方式进行实例化,我们平时习惯更简短的方式调用它们,但其本质上是一样的。

View Code
   javascript:
   var a = bew String('hello world');// 通过new String()实例化 string 类型对象
   var b = 'hello world';// 直接通过 '' 实例化 string 类型对象
   alert(a.length);
   alert(b.length);
   var c = new Array(1, 2, 3);
   var d = [1, 2, 3];
   c.push(4);
   d.pop();
   alert(c);
   alert(d);

   只要是类就会有原型,不管它是自定义类还是javascript的内置类,我们可以通过修改内置类的原型,让javascript基本类型的对象获得一些有趣的功能。例如,在很多语言中,Array具有each、map等方法,但javascript没有。没关系,既然原生的javascript并不提供这些方法,那么我们自己扩展它好了。

View Code
   javascript:
   Array.prototype.each = function(fun){
     for(var i=0,n=this.length; i<n; i++){
       fun(this[i], i);
     }
   };
   Array.prototype.clone = function(){
     var o = [];
     this.each(function(v, k){
       o[k] = v;
     });
     return o;
   };
   Array.prototype.map = function(fun){
     var o = [];
     this.each(function(v, k){
       o[k] = function(v, k);
     });
     return o;
   };
   // 因为在IE中 delete 是保留字,所以方法名改用Delete
   Array.prototype.Delete = function(a){
     var o = this.clone();
     for(var i=o.length, n=0; i>n; i--){
       if(o[i] == a){
         o.splice(i, 1);
       }
     }
     return o;
   };
   var a = [1, 2, 3, 4, 5];
   var str = '';
   a.each(function(v, k){
     str += k+':'+ v +', ';
   });
   alert(str);// 0:1, 1:2, 2:3, 3:4, 4:5,
   var b = a.map(function(v, k){
     return v*10;
   });
   alert(a);// 1,2,3,4,5
   alert(b);// 10,20,30,40,50
   var c = b.Delete(20);
   alert(c);// 10,30,40,50

   这段代码中最难理解的地方在于 扩展中this代表什么?以前我们说过,无论在类的构造函数中还是在原型中,this都指向实例化的对象。明白了这一点,以上代码就不难理解了。
   除了可以扩展内置类的方法,我们还可以重写内置类的方法。

View Code
   javascript:
   var a = [1, 2, 3];
   alert(a);// 1,2,3
   Array.prototype.toString = function(){
     return 'I\'m an  array';
   }
   alert(a);// I'm an array
   值得一提的是,alert(a) 时,自动调用了 a 的toString 方法。在需要字符串时,对象会隐式地自动调用 toString 方法,包括我们自定义的对象。
   javascript:
   function Dog(o){
     this.name = o;
   }
   var myDog = new Dog('wang cai');
   alert(myDog);// [object object]
   Dog.prototype.toString = function(){
     return 'my name is '+ this.name;
   }
   alert(myDog);// my name is wang cai
   var me = {
     name: 'adang',
     email: 'xxx@163.com',
     toString: function(){
       return 'I\'m adang,my email is xxx@163.com';
     }
   };
   alert(me);// I'm adang,my email is xxx@163.com

   给自定义类定义 toString 方法,可以为我们在调试时提供更多有用的信息。
   内置类的方法可以重写,但属性却不能重写,比如。

View Code
   javascript:
   Array.prototype.length = 1;
   String.prototype.length = 1;
   alert([1, 2, 3].length);// 3
   alert('abc'.length);// 3

   在javascript中,包括内置类和自定义类,所有类的祖先都是Object,所以如果想对所有对象都扩展方法,可以通过修改Object类的原型实现,如:

View Code
   javascript:
   Object.prototype.test = function(){
     alert('hello world');
   }
   var a = [1, 2, 3], b = 'abc', c = {}, d = true, e = function(){};
   a.test();
   b.test();
   c.test();
   d.test();
   e.test();
   function Dog(o){
     this.name = o;
   }
   Dog.prototype.toString = function(){
     return 'my name is '+ this.name;
   }
   var f = new Dog(wang cai);
   f.test();

   修改内置类的原型可以再编程时给我们带来很大方便,但也有些人非常排斥这种做法,认为它对内置类的原型造成了“污染”,因为内置类的原型也可以理解为是全局作用域的,如果对它进行修改,在多人合作时有可能对别人的代码造成影响。修改内置类的原型等于修改了统一的游戏规则,虽然可以带来很大的方便,但同时也会给多人合作带来冲突隐患,它是有副作用的。
   所以有些人更愿意使用这样的方式来扩张内置类的方法,如下:

View Code
   javascript:
   function myArray(o){
     this.getArray = function(){
       return o;
     }
   }
   myArray.prototype = {
     each: function(fun){
       var o = this.getArray();
       for(var i=0, n=o.length; i<n; i++){
         fun(o[i], i);
       }
     }
   };
   var a = new myArray([1, 2, 3]), str = '';
   a.each(function(v, k){
     str += k +':'+ 'v'+ ', ';
   });
   alert(str);// 0:1, 1:2, 2:3, 

16、标签的自定义属性

View Code
   html:
   <a id="a" class="b" title="百度" href="http://www.baidu.com" onclick="alert(this.href);return false;" data-type="链接">baidu</a>
   javascript:
   var node = document.getElementById('a');
   alert(typeof node);// object
   // 使用 getAttribute('xxx') 获取节点对象属性
   alert(node.getAttribute('id'));
   // IE 和 Firefox : a
   alert(node.getAttribute('class'));
   // IE : null
   // Firefox : b
   alert(node.getAttribute('className'));
   // IE : b
   // Firefox : null
   alert(node.getAttribute('title'));
   // IE 和 Firefox : 百度
   alert(node.getAttribute('href'));
   // IE 和 Firefox : http://www.baidu.com
   alert(node.getAttribute('onclick'));
   // IE : function onclick(){alert(this.href);return false;}
   // Firefox : alert(this.href);return false;
   alert(node.getAttribute('innerHTML'));
   // IE : baidu
   // Firefox : null
   // 使用 node.xxx 获取节点对象属性
   alert(node.id);
   // IE 和 Firefox : a
   alert(node.className);
   // IE 和 Firefox : b
   alert(node.title);
   // IE 和 Firefox : 百度
   alert(node.href);
   // IE 和 Firefox : http://www.baidu.com
   alert(node.onclick);
   // IE : function onclick(){alert(this.href);return false;}
   // Firefox : function onclick(event){alert(this.href);return false;}
   alert(node.innerHTML);
   // IE 和 Firefox : baidu

   除了常规属性,我们还可以给 html 标签定义一些自定义属性,这些自定义属性同样在javascript中获取。但和常规属性不同,Firefox 下无法通过 node.xxx 获取到自定义属性值,只能使用 node.getAttribute('xxx') 获取。
   所以,从兼容性考虑,笔者建议对于常规属性,统一使用 node.xxx 的方式读取,对于自定义属性,统一使用 node.getAttribute('xxx') 读取。
   将复类型的数据转化成字符串,称为数据的序列化,其逆操作叫做数据的反序列化。
   借助html自定义属性,我们可以储存各种各样的数据,因为属性只能是字符串类型的,所以我们要先把复杂数据序列化,做成 长得像hash或数组的字符串 才行。
   数据的反序列化一般使用 eval 函数实现。

View Code
   javascript:
   var strObject = '{name: "huang", tel: "123"}';
   var obj = eval('('+ strObject +')');
   alert(obj.name);// huang
   alert(obj.tel);// 123

17、标签的内联事件和event对象
   event 对象在IE和Firefox下的表现是不同的。在IE下,event是window对象的一个属性,是在全局作用域下的,而在Firefox里,event对象作为事件的参数存在,如下:

View Code
   html:
   <input type="button" id="btn" value="click me" />
   javascript:
   document.getElementById('btn').onclick = function(){
     alert(arguments.length);// IE下弹出0;Firefox下弹出1
   };

   如果在标签的内联事件中触发事件又如何呢?代码:

View Code
   html:
   <input type="button" id="btn" value="click me" onclick="handler();" />
   javascript:
   function handler(){
     alert(arguments.length);
   }

   在IE肯Firefox下,这段代码弹出的都是 0。
   在标签内联事件中,我们使用 arguments[0] 可以再Firefox 下访问到 event 对象。
   不使用标签内联事件时,我们可以给处理函数传参,从而指定 arguments[0] 的变量名。

View Code
   html:
   <input type="button" id="btn" value="click me" />
   javascript:
   document.getElementById('btn').onclick = function(e){
     e = window.event || e;// 兼容 ie 和 firefox ,指向 event 对象
   };

   在标签内联事件中,我们没办法指定参数名,是不是就没办法直接写个变量在 ie 和 firefox 下兼容的指向 event 对象呢?不是的,可以用 event 这个变量名兼容的指向 event 对象,注意,只能是 event,诸如 a,b,Event 之类都是不行的。

View Code
   html:
   <input type="button" id="btn" value="click me" onclick="alert(event.type);" />

   这段代码在ie 和 firefox下都正确的弹出 "click"。
   有趣的是,标签内联事件中我们甚至可以写注释,可以使用字符串:

View Code
   html:
   <input type="button" id="btn" value="click me" onclick="alert(1);//alert(2);alert(3);" />
   <input type="button" id="btn" value="click me" onclick="alert(1);/*alert(2);*/alert(3);" />
   <input type="button" id="btn" value="click me" onclick="var a='abc';alert(typeof a);" />

   如果我们既用标签内联事件绑定了事件,又用 DomNode.onXXX 绑定了事件,那么 DomNode 绑定的事件函数会替代标签内联事件:

View Code
   html:
   <input type="button" id="btn" value="click me" onclick="alert(123);" />
   javascript:
   doccument.getElementById('btn').onclick = function(){
     alert(456);
   };

   其结果就是,弹出 456 ,不弹出 123。
   再如果重复绑定呢:

View Code
   javascript:
   doccument.getElementById('btn').onclick = function(){
     alert(123);
   };
   doccument.getElementById('btn').onclick = function(){
     alert(456);
   };

   结果会弹出 456 ,不弹出 123 。后面的会覆盖前面的。
   如果通过 attachEvent 和 addEventListener 来绑定事件:

View Code
   html:
   <input type="button" id="btn" value="click me" onclick="alert(123);" />
   javascript:
   function handler(){
     alert(456);
   }
   var btn = document.getElementById('btn');
   if(document.all){
     btn.attachEvent("onclick", handler);
   }else{
     btn.addEventListener("click", handler, false);
   }

   先弹出 123,然后弹出 456.

18、利用事件冒泡机制
   冒泡的思路是在祖先节点上监听事件,结合 event.target/event.srcElement 来实现最终效果。利用冒泡可以让事件挂钩更干净,有效减少内存开销。

19、改变DOM样式的三种方式
   javascript 编程很重要的一个功能就是用于改变DOM节点的样式。
   a 最简单最直接的方式就是设置 DomNode 的 style 属性。

View Code
   html:
   <span id="test">hello world</span>
   javascript:
   var node = document.getElementById('test');
   node.style.color = 'red';
   node.style.backgroundColor = 'black';
   node.style.fontSize = '40px';
   node.style.fontWeight = 'bold';

   这种写法拖沓又冗长,而且过多的承担起了变现层的职责,而变现层应该是由css控制的,所以又有了下面的方式,控制 DomNode 的class属性

View Code
   style:
   .testStyle {color: red;background-color: black;font-size: 40px;font-weight: bold;}
   html:
   <span id="test">hellow world</span>
   javascript:
   var node = document.getElementById('test');
   node.className = 'testStyle';

   第三种方式:
 

View Code
  html:
   <span id="test">hellow world</span>
   javascript:
   function addStyleNode(str){
     var styleNode = document.createElement('style');
     styleNode.type = 'text/css';
     if(styleNode.styleSheet){
       styleNode.styleSheet.cssText = str;
     }else{
       styleNode.innerHTML = str;
     }
     document.getElementsByTagName('head')[0].appendChild(styleNode);
   }
   addStyleNode('span {font-size: 40px;background: #000;color: #fff;}#test {color: red;}');

   需要注意的是,style的DOM节点在Firefox下可以直接对innerHTML属性进行读写操作,但在IE下,它的innerHTML属性是只读的。IE下要通过styleSheet.cssText进行写操作。

20、写在规则前面的话
   项目的可维护性第一。好的可维护性可以从四个方面获得:
   a 代码的耦合,高度模块化,将页面内的元素视为一个个模块,互相独立,尽量避免耦合过高的代码,从html、css、javascript三个层面考虑模块化。
   b 良好的注释。
   c 注意代码的弹性,在性能和弹性的选择上,一般情况下以弹性为优先考虑条件,在保证弹性的基础上,适当优化性能。
   d 严格按照规范编写代码。

21、一些总结性的结论(参考):
   为避免命名冲突,命名规则如下:
   a 公共组件因为高度重用,命名从简,不要加前缀。
   b 各栏目的响应代码,需要前缀,前缀为工程师姓名拼音的首字母。例如:"ad_"。(我觉得该条可酌情考虑)
   c 模块组件化,组件中的class或id名采用骆驼命名法和下划线相结合的方式,单词之间的分隔靠大写字母分开,从属关系靠下划线分隔。例如:.textList_first {}
   d 命名清晰,不怕命名长,确保css优先级权重足够低,方便扩展时的覆盖操作。
   e 命名要有意义,尽量使用英语命名,不要用拼音。

   分工安排:
   a 公共组件(包括common.css和common.js)一人维护,各子频道专人负责,每个频道正常情况下一人负责,要详细写明注释,如果多人合作,维护的人员要添加注释信息。
   b 视觉设计师设计完设计图后,先和交互设计师沟通,确定设计可行,然后先将设计图给公共组件维护者,看设计图是否需要提取公共组件,然后再提交给相应频道的前端工程师。如果有公共组件要提取,公共组件维护者需要对频道前端工程师说明。
   c 如果确定没有公共组件需提取,交互设计师直接和各栏目的前端工程师交流,对照着视觉设计师的设计图进行需求说明,前端工程师完成需求。
   d 前端工程师在制作页面时,需先去common文件中查询是否已经存在设计图中的组件,如果有,直接调用;如果没有,则在app.css和app.js中添加相应的代码(app指各频道自己的文件)。
   e 前端工程师在制作过程中,发现高度重用的模块,却未被加入到公共组件中,需向公共组件维护人员进行说明,然后公共组件维护人决定是否添加该组件。如果确定添加,则向前端工程师们说明添加了新组件,让前端工程师们检查之前是否添加了类似组件,统一更新成新组件的用法,删除之前自定义的css和javascript。虽然麻烦,但始终把可维护性放在首位。
   f 公共组件维护者的公共组件说明文档,需提供配套的图片和说明文字,方便阅读。

   注释规则:
   a 公共组件和各栏目的维护者都需要在文件头部加上注释说明:
   /**
   * 文件用途说明
   * 作者姓名
   * 联系方式
   * 制作日期
   **/
   /**
   * 公共组件
   * author: adang
   * email: xxx@gmail.com
   * date: 2012-10-11
   **/
   b 大的模块注释
   //=============
   // 代码用途
   //=============
   c 小的注释
   // 代码说明
   注意:注释单独占一行

   html规范:
   a 统一文档类型说明。
   b 统一文件编码。
   c 统一TAB缩进长度(四个空格)。
   d 标签名,属性名全部小写,属性加引号,单标签需闭合。
   e html应在保证弹性的基础上尽量减少嵌套层数。
     严格区分作为内容的图片和作为背景的图片。作为背景的图片采用css sprite整合。大图的安排也遵从 common+app 的方式。css sprite 虽然减少了http请求,但需background-position定位增加了可维护成本。如果图片有修改,建议不要删除已添加的图片,而是在空白处新增修改后的图片,减少修改的风险。
   f 标签语义化,webdevelper去样式可读性良好。
   g 方便服务端嵌套模板,html需为模块添加注释。格式为:
   <!--头部开始{-->
   .....
   <!--}头部结束-->

   css 规范:
   a css reset 用YUI的css reset。
   b css 采用 css reset+common.css+app.css的形式。
   c app.css采用分工制,一个前端工程师负责一个栏目,如果多人维护,需要添加注释。
   d 为方便组件模块化和提高弹性,正常情况下,为避免外边界冲突,组件不设置外边界,外边界用组合css的方式实现,如:
   html:
   <p>12345</p>
   <ul class="textList">
     <li>1)xxxx</li>
     <li>2)xxxx</li>
   </ul>
   <p>abcde</p>
   <ul class="textList2">
     <li>1)xxxx</li>
     <li>2)xxxx</li>
   </ul>
   css:
   .textList, .textList2 {margin-top: 10px;xxx}
   .textList2 {margin-top: 20px;}
   上面的方式,不灵活,可参考下面的代码组织方式:
   html:
   <p>12345</p>
   <ul class="textList marginTop10">
     <li>1)xxxx</li>
     <li>2)xxxx</li>
   </ul>
   <p>abcde</p>
   <ul class="textList marginTop20">
     <li>1)xxxx</li>
     <li>2)xxxx</li>
   </ul>
   css:
   .textList {xxx}
   e 为便面组件的上下外边距重合问题和IE的haslayout引发的bug,各模块除特殊需求,一律采用 marginTop 设置上下外边距。
   f 优先对已存在的common.css中类进行组合,较少自定义类的数量。
   g css用一行的写法,避免行数太长,不利查找。
   h 正是发布前应进行压缩,压缩后文件的命名应添加“_min”后缀。

   javascript 规范:
   a 底层javascript库采用YUI。
   b 统一头部中只载入YUI load组件,其他组件通过loader对象加载。
   c javascript尽量便面使用全局变量,通过命名空间或匿名函数将变量封装到闭包中。
   d 正是发布前应进行压缩,压缩后文件的命名应添加“_min”后缀。

 

笔记第一部分:1-4章的笔记

转载请带上原文地址(http://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html),谢谢:)

 

posted @ 2012-12-04 11:04  小鱼儿-lovefishs  阅读(460)  评论(0编辑  收藏  举报