第一章 简介
模式
模式是指一个通用问题的解决方案。
模式分三种
设计模式
编码模式:javascript特有的
反模式:常见的 引发的问题比解决的问题更多的一种方法。
JavaScript:基本概念
面向对象
只有五种基本类型不是对象:数值类型,字符串类型,布尔类型,空类型和未定义类型。
函数实际上也是对象,函数有属性和方法。
对象主要有两种:
原生的(Native)
原生的对象分为内置对象(数组,日期等) 和用户自定义对象 (var o={})
主机的(Host)
包含windows对象和所有的DOM对象。
没有类
JavaScript中没有类。
原型(Prototypes)
JavaScript没有继承,可以使用多种方法实现继承,通常使用原型。
原型是一个对象,并且创建的每一个都会自动获取一个Prototypes属性,该属性指向一个新的空对象。
该对象几乎等同于采用对象字面量或Object()创建的对象,区别在于它的constructor属性指向了所创建的函数,而不是指向内置的Object()函数。可以为该空对象增加成员变量,以后其他对象也可以从该对象继承并像使用自己的属性一样使用该对象的属性。
原型就是一个对象,每一个函数都有Prototype属性。
ECMAScript 5
核心的JavaScript编程语言(不包含DOM,BOM和额外的主机对象)是基于ECMAScript标准(缩写是ES).
strict模式
避免使用arguments.callee之类的构造函数
ES5 Object.create() 等同于ES3 Object()
暗示全局变量(implied globals):任何变量,如果未经声明,就为全局对象所有
JSLint
JavaScript代码质量检查工具
Console
Console对象不是JavaScript语言的一部分,而是浏览器提供的一个运行环境。
P15
另一种创建隐式全局变量的反模式是带有var声明的链式赋值。
function foo(){
var a=b=0;
// 等于 var a=(b=0);
}
隐含全局变量不是真正的变量,而是全局对象的属性。属性可以通过delete操作符删除,但变量不可以。
P16
访问全局对象,可以按如下方式访问
var global=(function(){return this;}());
P18 提升:JavaScript允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等于在函数顶部进行申明。
P19 在所有的浏览器中,通过将HTML容器上需要遍历的次数缓存起来都会大大提高速度。
P21 使用正常的for循环来处理数组,并使用for-in循环来处理对象。
P23 不要给内置的原型增加属性
P24使用===和!====操作符对数值和类型进行比较
P26 避免使用eval(),可使用new Function()代替eval().
P27 使用paresInt()
P45 不要使用new Object()构造函数
第二章 基本技巧
编写可维护的代码
尽量少用全局变量
Javascript使用函数管理作用域。变量在函数内生命,只在函数内有效。全局变量在函数外部生命,在函数内部无需声明即可食用。
每个Javascript环境都有全局对象,可在函数外部使用this进行访问。
创建的每一个全局变量都为全局对象所有。
myglobal="hello"; //反模式
console.log(myglobal);
console.log(window.myglobal);
console.log(window['myglobal']);
console.log(this.myglobal);
全局变量的问题
自执行立即生效函数 the self-executing immediate functions
Javascript特性:
1 Javascript可直接使用变量,甚至无需声明
2 Javascript有个暗示全局变量(implied globals)的概念,任何变量,如果未经声明,就为全局对象所有。
function sum(x,y){
//反模式:暗示全局变量 result
result=x+y;
return result;
}
function sum(x,y){
//正确的写法
var result=x+y;
return result;
}
另一种创建隐式全局变量的反模式是带有var声明的链式赋值
function foo(){
var a=b=0;
//反模式 a是局部变量 b是全局变量
}
首先,优先级较高的是表达式b=0,此时b未经声明。表达式的返回值为0,它被赋予给局部变量a 。相当于var a=(b=0);
function foo(){
//正确的赋值方式 对链式赋值的所有变量都进行了声明
var a,b;
a=b=0;
}
变量释放时的副作用
隐含全局变量与明确定义的全局变量的细微不同:能否删除
- 使用var创建的全局变量(这类变量在函数外部创建)不能删除
- 不使用var创建的隐性全局变量(尽管是在函数内部创建)可以删除
这表明隐含全局变量是全局对象的属性,属性可以通过delete操作符删除,但变量不可以。
//定义三个全局变量
var global_var=1;
global_novar=2; //反模式
(function(){
global_fromfunc=3 //反模式
}());
//企图删除
delete global_var; //false
delete global_novar; //true;
delete global_fromfunc; //true;
//测试删除情况
typeof global_var; //number类型
typeof global_novar; //undefined 类型
typeof global_fromfunc; //undefined类型
访问全局对象
var global=(function(){
return this;
}());
从内嵌函数的作用域访问window对象 (不带硬编码的方式)
单一var模式(Single var Pattern)
只使用一个var在函数顶部进行变量声明的模式。
function func(){
var a=1,
b=2,
sum=a+b,
myobject={},
i,
j;
//函数体
}
function updateElement(){
var el=document.getElementById('result'),
style=el.style;
//使用el和style再做其他事...
}
提升:零散变量的问题
Javascript允许在函数的任意地方声明多个变量,无论在哪里声明,效果都等于在函数顶部进行声明。所以容忍先使用后声明的情况。
//反模式
myname="global";//全局变量
function func(){
alert(myname); //未定义
var myname="local";
alert(myname); //局部变量
}
func();
前面的代码等同于下面的代码
myname="global"; //全局变量
function func(){
var myname; //等同于->var myname=undefined;
alert(myname); //未定义
myname="local";
alert(myname); //局部
}
func();
for循环
for循环经常用在遍历数组或类数组对象。
好的for循环模式是将已经遍历过的数组(或容器)的长度缓存起来。如以下代码所示。
for(var i=0, max=myarray.length;i<max;i++){
//对myarray[i]进行处理
}
单变量模式,可以将变量放到循环以外
function looper(){
var i=0,
max,
myarray=[];
//...
for(i=0,max=myarray.length;i<max;i++){
//处理myarray[i]
}
}
++和--提倡 excessive trickiness 过分棘手
改进版,逐步将至0,这样更快
var i,myarray=[];
for(i=myarray.length;i--;){
//处理myarray[i]
}
var myarray=[],
i=myarray.length;
while(i--){
//处理 myarray[i]
}
for-in循环
for-in循环应该用来遍历非数组对象。使用for-in循环也被称为枚举enumeration
//对象
var woman={
hands:4
};
var man={
hands:2,
legs:2,
heads:1
};
//代码的其他部分
//将一个方法添加到所有对象上
if(typeof Object.prototype.clone==="undefined"){
Object.prototype.clone=function(){
alert('克隆');
};
}
//1
//for-in循环
for(var i in man){
if(man.hasOwnProperty(i)){ //filter
console.log(i,":",man[i]);
}
}
/*
控制台中的结果
hands:2
legs:2
heads:1
*/
//2
//反模式
//不适用hasOwnProperty()进行检查后使用for-in循环的结果
for(var i in man){
console.log(i,":",man[i]);
}
/*
控制台中的结果
hands:2
legs:2
heads:1
clone:function Object.clone()
*/
另外一种使用hasOwnProperty()的模式是在Object.prototype中调用该函数
for(var i in man){
if(Object.prototype.hasOwnProperty.call(man,i)){ //过滤
console.log(i,":",man[i]);
}
}
使用hasOwnProperty对man对象进行精炼后,可以避免命名冲突,也可以使用一个本地变量来缓存比较长的属性名。
var i,
hasOwn=Object.prototype.hasOwnProperty;
for(var i in man){
if(hasOwn.call(man,i)){ //过滤
console.log(i,":",man[i]);
}
}
变种 略过花括号
//警告 :不能通过JSLint检查
var i,
hasOwn=Object.prototype.hasOwnProperty;
for(i in man) if(hasOwn.call(man,i)){ //过滤
console.log(i,":",man[i]);
}
不要增加内置的原型
增加构造函数的原型属性是一个增强功能性的强大的方法,但有时候该方法过于强大。
增加内置构造函数(例如Object(),Array(),Function()等)的原型是很有诱惑的,但这可能会严重影响可维护性。
实在需要增加自定义方法可以用如下代码:
if(typeof Object.prototype.myMethod!=="function"){
Object.prototype.myMethod=function(){
//implementation....
}
}
switch模式
var inspect_me=0,
result='';
switch(inspect_me){
case 0:
result="zero";
break;
case 1:
result="one";
break
default:
result="unknown";
}
避免使用瘾式类型转换
使用=== 和!==操作符
var zero=0;
if(zero===false){
//因为zero是0,而不是false,所以代码未执行
}
//反模式
if(zero==false){
//该代码会被执行。。。
}
避免使用eval()
var obj={
name:"lilu",
}
//反模式
var property="name";
alert(eval("obj."+property));
//推荐的方法
var property="name";
alert(obj[property]);
eval()包含安全隐患,这样做有可能执行被篡改过的代码。
通过setInterval(), setTimeout()和function()等构造函数传递参数,也会导致类似eval的隐患。
//反模式
setTimeout("myFunc()",1000);
setTimeout("myFunc(1,2,3)",1000);
//推荐模式
setTimeout(myFunc,1000);
setTimeout(function(){
myFunc(1,2,3);
},1000);
eval()中任何采用var定义的变量会自动变成全局变量。因此可以通过使用Function()或者将eval()调用封装到一个即时函数中。
console.log(typeof un); //未定义
console.log(typeof deux); //未定义
console.log(typeof trois); //未定义
var jsstring="var un=1;console.log(un);";
eval(jsstring); //logs "1"
jsstring="var deux=2; console.log(deux);";
new Function(jsstring)(); //logs "2"
jsstring="var trois=3; console.log(trois);";
(function(){
eval(jsstring);
}()); //logs "3";
console.log(typeof un); //数值类型
console.log(typeof deux); //未定义
console.log(typeof trois); //未定义
new Function()和eval()的区别在于eval()会影响到作用于链,而Function更多地类似于一个沙盒。无论在哪里执行Function,它都仅仅能看到全局作用域。 Function的使用和new Function是一样的。
(function(){
var local=1;
eval("local=3; console.log(local)"); //logs 3
console.log(local); //logs 3
}());
(function(){
var local=1;
Function("console.log(typeof local);")(); //logs 未定义
}());
使用parseInt()的数值约定
每次具体制定进制参数
var month="06",
year="09";
month=parseInt(month,10);
year=parseInt(year,10);
//另外一个将字符串转换为数值的方法是 +"08" //结果是8 Number("08") //8
编码约定
一致遵循约定比这个具体约定是什么更为重要。
缩进
JSLint默认值 4个空格缩进
function outer(a, b){
var c = 1,
d = 2,
inner;
if (a > b){
inner = function () {
return {
r: c - d
};
};
} else {
inner = function () {
return {
r: c + d
};
};
}
return inner;
}
大括号
应该经常使用大括号
开放的大括号位置
分号插入机制 semicolon insertion mechanism
空格
命名约定
构造函数的首字母大写
分割单词
构造函数可以用使用大驼峰命名法
函数和方法名可以用小驼峰命名法
函数的变量可以用小写单词和下划线连接
其他命名默模式
变量名全部大写代表该变量在生命周期中不可变
编写注释
编写API文档
/**
* @tag value
*/
/**
* 反转一个字符串
*
* @param {String} 输入血药反转的字符串
* @return {String} 反转后的字符串
*/
var reverse=function(input){
//...
return output;
};
YUIDoc范例
http://www.jspatterns.com/book/2/
/**
* 我的JavaScript应用程序
*
* @module myapp
*/
var MYAPP = {};
/**
* 一个数字工具
* @namespace MYAPP
* @class math_stuff
*/
MYAPP.math_stuff={
/**
* Sums two numbers
*
* @method sum
* @param {Number} 是第一个数
* @param {Number} 是第二个数
* @return {Number} 两个输入的总和
*/
sum: function (a, b) {
return a + b;
},
/**
* Multiplies two numbers
*
* @method multi
* @param {Number} 是第一个数
* @param {Number} 是第二个数
* @return {Number} 两个输入相乘后结果
*/
multi: function (a, b) {
return a * b;
}
};
/**
* Constructs Person objects
* @class Person
* @constructor
* @namespace MYAPP
* @param {String} first 是名字
* @param {String} last 是姓氏
*/
MYAPP.Person =function (first, last){
/**
* 人的姓名
* @property first_name
* @type String
*/
this.first_name=first;
/**
* Last (family) name of the person
* @property last_name
* @type String
*/
this.last_name=last;
};
/**
* Returns the name of the person object
*
* @method getName
* @return {String} 人的姓名
*/
MYAPP.Person.prototype.getName = function () {
return this.first_name + ' ' + this.last_name;
};