读书笔记:编写高质量代码--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 xiaocuicanary.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 adangsetTimeout(adang.say, 1000);// I'm somebodysetInterval(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 adangsetTimeout(function(){adang.say();}, 1000);// I'm adangsetInterval(function(){adang.say();}, 1000);// I'm adangbtn.onclick = function(){adang.say();};// I'm adangsetTimeout(function(){alert(this == window);}, 1000);// truebtn.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 BUTTONsetTimeout(function(){adang.say.call(btn);}, 1000);// I'm BUTTONsetInterval(function(){adang.say.call(btn);}, 1000);// I'm BUTTONbtn.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 adangthis.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 属性,默认为adangthis.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 换个新名字 xiaoqiangmyDog.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 1function 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 2function Bird(name){Animal.call(this, name);}Bird.prototype = {};// 实例化Bird对象var myBird = new Bird('xiaocui');alert(myBird.type);// animal

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

View Code

   javascript:// example 3function 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();// xxxxxmyBird.fly();// I'm flyingvar 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);// 10alert(b);// 11alert(c.join(','));// 1,2,3,4alert(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,3alert(c);// 1,2,3,4alert(b.email);// undefinedalert(d.email);// xxx@gmail.com

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

View Code

   javascript:// example 4function 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();// xxxxxmyBird.fly();// I'm flyingvar 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 是保留字,所以方法名改用DeleteArray.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,5alert(b);// 10,20,30,40,50var c = b.Delete(20);alert(c);// 10,30,40,50

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

View Code

   javascript:var a = [1, 2, 3];alert(a);// 1,2,3Array.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 caivar 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);// 3alert('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 : aalert(node.getAttribute('class'));// IE : null// Firefox : balert(node.getAttribute('className'));// IE : b// Firefox : nullalert(node.getAttribute('title'));// IE 和 Firefox : 百度alert(node.getAttribute('href'));// IE 和 Firefox : http://www.baidu.comalert(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 : aalert(node.className);// IE 和 Firefox : balert(node.title);// IE 和 Firefox : 百度alert(node.href);// IE 和 Firefox : http://www.baidu.comalert(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);// huangalert(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),谢谢:)

转载于:https://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html

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

  1. 编写高质量代码 Web前端开发修炼之道 读书笔记

    2019独角兽企业重金招聘Python工程师标准>>> 第五章 高质量的JavaScript 5.1 养成良好的编程习惯 5.1.1 团队合作-如何避免JS冲突 使用匿名函数控制变量 ...

  2. 编写高质量代码:Web前端开发修炼之道(三)

    第五章:高质量的Javascript 这章的内容我看的最久,这是跟我js基础没打好有着莫大的关系,但是还是耐着性子看完了, 不懂的东西都是百度上搜索,理解后再继续.下面是记录下来的笔记. 1)如何避免 ...

  3. 【读书笔记】编写高质量的代码Web前端开发修炼之道——曹刘阳

    2019独角兽企业重金招聘Python工程师标准>>> 一:从网页重构说起 1. web标准: 机构标准,样式标准,行为标准 2.样式和行为应该从标签中分离 精简,重用,有序 好的代 ...

  4. [原]《Web前端开发修炼之道》-读书笔记CSS部分

    如何组织CSS-分层 应用 css 的能力分两部分:一部分是css的API,重点是如何用css控制页面内元素的样式:另一部分是css框架,重点是如何对 css 进行组织.如何组织 css 可以有多种角 ...

  5. 《Web前端开发修炼之道》-读书笔记CSS部分

    如何组织CSS-分层 应用 css 的能力分两部分:一部分是css的API,重点是如何用css控制页面内元素的样式:另一部分是css框架,重点是如何对 css 进行组织.如何组织 css 可以有多种角 ...

  6. 编写规范--Web前端开发修炼之道

    1.css命名加前缀---如:ad-time   box-hb--这样辨识扩展度比较高 2.挂多个class还是新建class--多用组合,少用继承 3.如果不确定模块的上下margin特别稳定,最好 ...

  7. 编写高质量代码改善java程序的151个建议——[110-117]异常及Web项目中异常处理

    编写高质量代码改善java程序的151个建议--[110-117]异常及Web项目中异常处理 原创地址:http://www.cnblogs.com/Alandre/(泥沙砖瓦浆木匠),需要转载的,保 ...

  8. iOS-《编写高质量代码》笔记-第一章

    <编写高质量代码> 作者 刘一道 看这本书的过程中,做了一些笔记,分享一下. 建议1:视OC 为一门动态语言 写代码的时候切忌心态浮躁,急功近利. OC和C++ 都是在C的基础上加入面向对 ...

  9. 编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议1~5)...

                 The reasonable man adapts himself to the world; The unreasonable one persists in trying ...

最新文章

  1. 深入学习keepalived之一 keepalived的启动
  2. CodeForces - 1512G Short Task(欧拉筛求因子和)
  3. 微软开源基于云的生理学研究工具
  4. 详解CorelDRAW中如何合并与拆分对象
  5. 自己的 「 代码制造 check list 」
  6. 【优化调度】基于matlab粒子群算法求解梯级水电站调度优化问题【含Matlab源码 767期】
  7. CSS选择器的权重计算
  8. 荣耀 MagicBook 14 2022配置怎么样 值不值得买
  9. python中 s是什么意思_python中字符串 s[ : -1]是什么意思?
  10. Huawei RH2288 V3 风扇噪音大的解决方案
  11. 【剑指offer】解题思路 53-68
  12. 运维之阿里云和本地虚拟机的连接问题
  13. 微信小程序-导航吸顶+view锚点
  14. MySQL Shell系列——升级检查器
  15. zabbix4.0配置钉钉机器人告警详细教程
  16. 《杀出个黎明》(From Dusk Till Dawn)--This is the fucking movie
  17. 网络抓包工具Charles的介绍与使用
  18. 怎么画好一个项目甘特图(内附实用模板)
  19. 判断素数三种不同的方法(java)
  20. 需求经理作业——第二组头脑风暴结果

热门文章

  1. php rabbmq教程_RabbitMQ+PHP 教程一(Hello World)
  2. Go 学习笔记(65)— Go 中函数参数是传值还是传引用
  3. 那还剩下多少学习激情?
  4. LeetCode简单题之长按键入
  5. LeetCode简单题之最小绝对差
  6. SpringMVC——通俗易懂讲讲Ajax~
  7. Keras神经网络集成技术
  8. Python 代理爬取网站数据
  9. CentOS7下安装nvm
  10. Python七大原则,24种设计模式