1

1、可维护性

1.1 可维护代码特征

1. 可理解性
2. 直观性
3. 可适应性
4. 可扩展性
5. 可调试性

1.2 代码约定

1、可读性(代码缩进和代码注释)
2、变量和函数命名

  • 变量名应该为名词
  • 函数名应该以动词开始
  • 变量和函数都应使用合乎逻辑的名字,不要担心长度。

3、变量类型透明
(1)第一种方式是初始化,初始化为一个特定的数据类型可以很好的指明变量的类型;

//通过初始化指定变量类型
var found = false;    //布尔型
var count = -1;       //数字
var name = "";        //字符串
var person = null;    //对象
  • 缺点在于无法用于函数声明中的函数参数。

(2)第二种方式是使用匈牙利标记法来指定变量的类型。匈牙利标记法在变量名之前加上一个或多个字符来表示数据类型。

//用于指定数据类型的匈牙利标记法
var bFound = false; //布尔型
var iCount = -1;    //数字
var sName = "";     //字符串
var oPerson = null;  //对象
  • 缺点在于让代码某种程度上难以阅读,阻碍了没有用它时代码的直观性和句子式的特征。

(3)第三种方式是使用类型注释。

//用于指定类型的类型注释
var found  /*:Boolean*/ = false; 
var count  /*:int*/  = -1;    
var name   /*:String*/ = "";     
var person /*:Object*/ = null; 
  • 缺点在于不能用多行注释一次注释大块的代码。(在每一行上使用单行注释可解决这个问题)

1.3 松散耦合

只要应用的某个部分过于依赖另一部分,代码就是耦合过紧,难以维护。

1、解耦HTML/JavaScript
2、解耦CSS/JavaScript

  • 由于JavaScript必须与HTML和CSS共存,所以让各自完全定义起自己的目的非常重要:
  • JavaScript应该定义行为,HTML应该定义内容,CSS应该定义外观。

3、解耦应用逻辑/事件处理程序

应用和业务逻辑之间松散耦合的几条原则:

  • 勿将event对象传给其他方法;只传来自event对象中所需的数据;
  • 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;
  • 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑。

1.4 编程实践

1、尊重对象所有权

  • 不要为实例或原型添加属性;
  • 不要为实例或原型添加方法;
  • 不要重定义已存在的方法;

可以通过以下方式为对象创建新的功能

  • 创建包含所需功能的新对象,并用它与相关对象进行交互;
  • 创建自定义类型,继承需要进行修改的类型,然后可以为自定义类型添加额外功能;

2、避免全局量

最多创建一个全局变量,让其他对象和函数存在其中。

var MyApplication  = {
    name : "Nicholas",
    sayName : function(){
        alert(this.name);
    }
};

命名空间包括创建一个用于放置功能的对象。唯一的全局变量作为一个容器,其中定义了其他方法,用这种方式将功能组合在一起的对象,叫做命名空间。命名空间有助于确保代码可以在同一页面上与其他代码以无害的方式一起工作。

3、避免与null进行比较

直接将值与null比较是使用过度的,并且常常由于不充分的类型检查导致错误。如果看到了与null比较的代码,尝试使用一下技术替换:

  • 如果值应为一个引用类型,使用instanceof操作符检查其构造函数;
  • 如果值应为一个基本类型,使用typeof检查其类型;
  • 如果是希望对象包含某个特定的方法名,则使用typeof操作符确保指定名字的方法存在于对象上。

4、使用常量

将数据从应用逻辑分离出来的思想。可以在不冒引入错误的风险的同时,就改变数据。如下例

function validate(value) {
    if(!value) {
        alert("Invalid value");
        location.href = "/errors(invalid.php)";
    }
}

可以通过将数据抽取出来变成单独定义的常量的方式,将应用逻辑与数据修改隔离开来。修改如下:

var Constants = {
    INVALID_VALUE_MSG: "Invalid value",
    INVALID_VALUE_URL: "/errors/invalid.php" 
};

function validate(value) {
    if(!value) {
        alert(Constant.INVALID_VALUE_MSG);
        location.href = COnstant.INVALID_VALUE_URL;
    }
}

关键在于将数据和使用它的逻辑进行分离。需要提取作为常量的值大致有以下几种情况:

  • 重复值:任何在多处用到的值都应抽取为一个常量;
  • 用户界面字符串:任何用于显示给用户的字符串,都应该被抽取出来以方便国际化;
  • URLs:在WEB应用中,资源位置很容易变更,所以推荐用一个公共地方存放所有的URL;
  • 任意可能会更改的值

2、性能

2.1 注意作用域

访问全局变量总是比访问局部变量慢。只要能减少花费在作用域上的时间,就能增加脚本的整体性能。

1、避免全局查找

function updateUI() {
    var imgs = document.getElementsByTagName("img");
    for(var i=0, len=imgs.length; i<len; i++) {
        imgs[i].title = document.title + "image" + i;
    } 
    var msg = document.getElementsById("msg");
    msg.innerHTML = "Update complete."
}

上述函数看起来完全正常,但是它包含了三个对于全局document对象的引用。通过创建一个指向document对象的局部变量,就可以通过限制一次全局查找来改进这个函数的性能。改进后的代码如下:

function updateUI() {
    var doc = document;
    var imgs = doc.getElementsByTagName("img");
    for(var i=0, len=imgs.length; i<len; i++) {
        imgs[i].title = doc.title + "image" + i;
    } 
    var msg = doc.getElementsById("msg");
    msg.innerHTML = "Update complete."
}

2、避免with语句

with语句会创建自己的作用域,因此会增加其中执行的代码的作用域的长度。
大多数情况下,可以是同局部变量完成相同的事情而不引入新的作用域。

function updateBody() {
    with(document.body) {
        alert(tagName);
        innerHTML = "Hello World!";
    }
}

以上代码中的with语句让document.body变得更容易使用,但是其实可以使用局部变量达到相同的效果。

function updateBody() {
    var body = document.body;
    alert(body.tagName);
    body.innerHTML = "Hello World!";
}

2.2 选择正确的方法

1、避免不必要的属性查找

  • 一旦多次用到属性对象,应该将其存储在局部变量中。一般来讲,只要能减少算法的复杂度,就要尽可能减少。尽可能多的使用局部变量将属性查找替换为值查找。进一步讲,如果既可以用数字化的数组位置进行访问,也可以使用命名属性,那么使用数字位置。

2、优化循环

  • (1)减值迭代:很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。
  • (2)简化终止条件
  • (3)简化循环体:确保没有某些可以被很容易移出循环的密集操作。
  • (4)使用后测循环:最常使用的for循环和while循环都是前测试循环。而如do-while这种后测试循环,可以避免最终终止条件的计算,因此运行更快。

3、展开循环(可以考虑一种叫做Duff装置的技术)
当循环次数是确定的,消除循环并使用多次函数调用往往更快。如下循环代码

for(var i=0; i<values.length; i++) {
    process(values[i]);
}

可以改为

process(values[0]);
process(values[1]);
process(values[2]);

//Duff装置的基本概念是通过计算迭代的次数是否为8的倍数将一个循环展开为一系列语句。

var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
do{
    switch(startAt) {
        case 0: process(value[i++]);
        case 7: process(value[i++]);
        case 6: process(value[i++]);
        case 5: process(value[i++]);
        case 4: process(value[i++]);
        case 3: process(value[i++]);
        case 2: process(value[i++]);
        case 1: process(value[i++]);
    }
    startAt = 0;
}while(--iterations > 0);

Math.ceil()函数返回大于或等于一个给定数字的最小整数
Math.floor()函数返回小于或等于一个给定数字的最大整数

DUff装置的实现是通过将values数组中的元素个数除以8来计算出循环需要进行多少次迭代的。然后使用取整的上限函数确保结果是整数。如果完全根据8来进行迭代,可能会有一些不能被处理到的元素,这个数量保存在startAt变量中。首次执行该循环时,会检查startAt变量看有需要多少次额外调用。那么最开始的时候process()则只会被调用2次。在接下来的循环中,startAt被重置为0,这样之后的每次循环都会调用8次process()。展开循环可以提升大数据及的处理速度。

4、避免双重解释

//某些代码求值——避免!!
eval("alert('Hello World!')");
//创建新函数——避免!!
var sayHi = new Function("alert('Hello World!')");
//设置超时——避免!!
setTimeout("alert('Hello World!')",500);

修改为

//某些代码求值——已修正
alert('Hello World!');
//创建新函数——已修正
var sayHi =  function(){
    alert('Hello World!')
};
//设置超时——已修正
setTimeout(function(){
    alert('Hello World!');
},500);

5、性能其他的注意事项

  • 原生方法比较快;
  • Switch语句较快;
  • 位运算符比较快

2.3 最小化语句数

完成多个操作的单个语句要比完成单个操作的多个语句快。

1、多个变量的声明

//4个语句——浪费!
var count = 5;
var color = "blue";
var values = [1,2,3];
var now = new Date();

修改为

var count = 5;
    color = "blue";
    values = [1,2,3];
    now = new Date();

2、插入迭代值

当使用迭代值(也就是在不同的位置进行增加或者减少的值)的时候,尽可能使用合并语句。

var name = values[i];
    i++;

例如以上代码修改为

var name = values[i++];

3、使用数组和对象字面量

创建数组和对象的方法有两种:使用构造函数或者是字面量。使用构造函数总是要用到更多的语句来插入元素或者定义属性,而字面量可以将这些操作在一个语句中完成。

//用4个语句创建和初始化数组——浪费   
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;

//只用一条语句创建和初始化数组
var values = [123, 456, 789];

只要有可能,尽量使用数组和对象的字面量表达方式来消除不必要的语句。

2.4 优化DOM交互

1、最小化现场更新

  • 之所以叫现场更新,是因为需要立即(现场)对页面对用户的显示进行更新。
  • 一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。

2、使用innerHTML

  • 对于大的DOM更改,使用innerHTML要比使用标准DOM方法创建同样的DOM结构快得多。
  • 使用innerHTML的关键在于最小化调用它的次数。

3、使用事件代理

  • 任何可以冒泡的事件都不仅仅可以在事件目标上进行处理,目标的任何祖先节点上也能处理。使用这个只是就可以将事件处理程序附加到更高层的地方负责多个目标的事件处理。

4、注意HTMLCollection

  • 任何时候要访问HTMLCollection,不管它是一个属性还是方法,都是在文档上进行一个查询,这个查询开销很昂贵。最小化访问HTMLCollection的次数可以极大地改进脚本的性能。当在循环中使用HTMLCollection的时候,下一步应该是获取要使用的项目的引用,以便避免在循环体内多次调用HTMLCollection。
var images = document.getElementsByTagName("img");
    i, len;
for(i=0, length=images.length; i<len; i++) {
    //处理   
}

修改为

var images = document.getElementsByTagName("img");
    image,
    i, len;
for(i=0, length=images.length; i<len; i++) {
    image = images[i];
    //处理   
}

编写JavaScript的时候,一定要知道何时返回HTMLCollection对象,这样你就可以最小化他们的访问。发生一下情况时会返回HTMLCollection对象:

  • 进行了对getElementByTagName()的调用;
  • 获取了元素的childNodes属性;
  • 获取了元素的attribute属性;
  • 访问特殊的集合,如document.forms、document.images等。

当使用HTMLCOllection对象时,合理使用会极大提升代码的执行速度。

3、部署

1、构建过程
2、验证
3、压缩

  • 为了协助部署,推荐设置一个可以将JavaScript合并为较少文件的构建过程;
  • 有了构建过程也可以对源代码自动运行额外的处理和过滤,例如可以运行JavaScript验证器来确保没有语法错误或者是代码没有潜在的问题;
  • 在部署前推荐使用压缩器将文件尽可能变小;
  • 和HTTP压缩一起使用可以让JavaScript文件尽可能小,因此对整体页面性能的影响也会最小。

皮卡丘丘丘
50 声望12 粉丝

不忘初心方得始终,念念不忘必有回响