读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)
读书笔记:编写高质量代码--web前端开发修炼之道
这本书看得断断续续,不连贯,笔记也是有些马虎了,想了解这本书内容的童鞋可以借鉴我的这篇笔记,希望对大家有帮助。
笔记有点长,所以分为一,二两个部分:
第一部分:1-4章的笔记
第二部分:5章以及一些总结性的建议笔记等
转载本文,请带上本文地址(http://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html),谢谢:)
第五章:高质量的JavaScript
1、团队合作--如何避免js冲突
a 使用匿名函数控制变量作用域
"(function(){})()":这种形式很巧妙,先定义一个匿名函数,然后立即执行它。
b 利用全局作用域的变量在各匿名函数间搭起桥梁
需要严格控制全局变量的数量,大量滥用就违背了使用匿名函数的初衷。
c 命名空间
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 方法用于继承。我们自己定义:
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的指向不同:
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
使用匿名函数可以解决这个问题,如下:
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 指向,如:
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 改变指向之前,将它指向的对象保存到一个变量中也是常用的方法,如:
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):
抛开计算机思维,使用生活中的思维进行编程。
面向过程的思维是描述一个个“动作”,而面向对象的思维就是描述一个个“物件”,客观生活中的物件,都可以通过面向对象思维映射到程序中--“物件”对应“对象”,“状态”对应“属性”,“行为”对应“动作”。
面向过程编程:
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 实例化出来的对象,其属性和行为来自于两部分,一部分来自于构造函数,另一部分来自于原型。
共有和私有
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
从上我们可以知道,公有方法不能访问私有属性和私有行为,那么如何解决呢?最好的方法就是把所有的公有方法都私有化(都写在类的构造函数里面),这样属性和行为都共同作用在构造函数的作用域里,如下:
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:
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的构造函数和原型。参考以下代码:
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 方法实现,如下:
javascript:
// example 2
function Bird(name){
Animal.call(this, name);
}
Bird.prototype = {};
// 实例化Bird对象
var myBird = new Bird('xiaocui');
alert(myBird.type);// animal
构造函数的属性和行为已经实现了继承,接下来我们要实现原型中属性和行为的继承。
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:
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:
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:
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 函数:
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的环节。
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 的方式进行实例化,我们平时习惯更简短的方式调用它们,但其本质上是一样的。
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并不提供这些方法,那么我们自己扩展它好了。
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都指向实例化的对象。明白了这一点,以上代码就不难理解了。
除了可以扩展内置类的方法,我们还可以重写内置类的方法。
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 方法,可以为我们在调试时提供更多有用的信息。
内置类的方法可以重写,但属性却不能重写,比如。
javascript:
Array.prototype.length = 1;
String.prototype.length = 1;
alert([1, 2, 3].length);// 3
alert('abc'.length);// 3
在javascript中,包括内置类和自定义类,所有类的祖先都是Object,所以如果想对所有对象都扩展方法,可以通过修改Object类的原型实现,如:
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();
修改内置类的原型可以再编程时给我们带来很大方便,但也有些人非常排斥这种做法,认为它对内置类的原型造成了“污染”,因为内置类的原型也可以理解为是全局作用域的,如果对它进行修改,在多人合作时有可能对别人的代码造成影响。修改内置类的原型等于修改了统一的游戏规则,虽然可以带来很大的方便,但同时也会给多人合作带来冲突隐患,它是有副作用的。
所以有些人更愿意使用这样的方式来扩张内置类的方法,如下:
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、标签的自定义属性
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 函数实现。
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对象作为事件的参数存在,如下:
html: <input type="button" id="btn" value="click me" /> javascript: document.getElementById('btn').onclick = function(){ alert(arguments.length);// IE下弹出0;Firefox下弹出1 };
如果在标签的内联事件中触发事件又如何呢?代码:
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] 的变量名。
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 之类都是不行的。
html: <input type="button" id="btn" value="click me" onclick="alert(event.type);" />
这段代码在ie 和 firefox下都正确的弹出 "click"。
有趣的是,标签内联事件中我们甚至可以写注释,可以使用字符串:
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 绑定的事件函数会替代标签内联事件:
html: <input type="button" id="btn" value="click me" onclick="alert(123);" /> javascript: doccument.getElementById('btn').onclick = function(){ alert(456); };
其结果就是,弹出 456 ,不弹出 123。
再如果重复绑定呢:
javascript:
doccument.getElementById('btn').onclick = function(){
alert(123);
};
doccument.getElementById('btn').onclick = function(){
alert(456);
};
结果会弹出 456 ,不弹出 123 。后面的会覆盖前面的。
如果通过 attachEvent 和 addEventListener 来绑定事件:
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 属性。
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属性
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';
第三种方式:
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),谢谢:)