Javascript-最佳实践

一、可维护性

1、什么是可维护的代码

可维护的代码有一些特征。一般来说,如果说代码是可维护的,它需要遵循以下特点

  • 可理解性——其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
  • 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
    • 可适应性——代码以一种数据上的变化不要求完全重写的方法撰写。
  • 可扩展性——在代码架构上已考虑到在未来允许对核心功能进行扩展。
  • 可调试性——当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在。

2、代码约定

  • 可读性
    主要有 代码缩进、注释这两个方面,一些地方需要注释:
    • 函数和方法——每个函数或方法都应该包含一个注释,描述其目的和用于完成任务所可能使用的算法。陈述事先的假设也非常重要,如参数代表什么,函数是否有返回值(因为这不能从函数定义中推断出来)。
    • 大段代码——用于完成单个任务的多行代码应该在前面放一个描述任务的注释。
    • 复杂的算法——如果使用了一种独特的方式解决某个问题,则要在注释中解释你是如何做的。这不仅仅可以帮助其他浏览你代码的人,也能在下次你自己查阅代码的时候帮助理解。
    •Hack——因为存在浏览器差异,JavaScript 代码一般会包含一些hack。
  • 变量和函数命名
    命名规则:
    • 变量名应为名词如car 或person。
    • 函数名应该以动词开始,如getName()。返回布尔类型值的函数一般以is 开头,如isEnable()。
    • 变量和函数都应使用合乎逻辑的名字,不要担心长度。长度问题可以通过后处理和压缩(本章后面会讲到)来缓解。
  • 变量类型透明
    由于在JavaScript 中变量是松散类型的,很容易就忘记变量所应包含的数据类型。合适的命名方式可以一定程度上缓解这个问题,但放到所有的情况下看,还不够。有三种表示变量数据类型的方式。
    1、初始化
var found = false; //布尔值
var count = -1; //数字
var name = ""; //字符串
var person = null; //对象

2、匈牙利标记法
JavaScript 中最传统的匈牙利标记法是用单个字符表示基本类型:"o"代表对象,"s"代表字符串,"i"代表整数,"f"代表浮点数,"b"代表布尔型。如下所示:

var bFound; //布尔值
var iCount; //整数
var sName; //字符串
var oPerson; //对象

3、松散耦合

只要应用的某个部分过分依赖于另一部分,代码就是耦合过紧,难于维护。典型的问题如:对象直接引用另一个对象,并且当修改其中一个的同时需要修改另外一个。紧密耦合的软件难于维护并且需要经常重写。

  • 解耦HTML/Javascript
  • 解耦CSS/Javascript
  • 解耦应用逻辑/事件处理程序

4、编程实践

  • 尊重对象的所有权
    不要为实例或原型添加属性;
    不要为实例或原型添加方法;
    不要重定义已存在的方法
  • 避免全局量
  • 避免与null作比较
  • 使用常量

二、性能

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.getElementById("msg");
    msg.innerHTML = "Update complete";
}

该函数可能看上去完全正常,但是它包含了三个对于全局document 对象的引用。如果在页面上有多个图片,那么for 循环中的document 引用就会被执行多次甚至上百次,每次都会要进行作用域链查找。通过创建一个指向document 对象的局部变量,就可以通过限制一次全局查找来改进这个函数的性能:

//优化,创建一个指向document对象的局部变量
function updateUI2(){
    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.getElementById("msg");
    msg.innerHTML = "Update complete.";
}
  • 避免使用with语句
    在性能非常重要的地方必须避免使用with 语句。和函数类似,with 语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。由于额外的作用域链查找,在with 语句中执行的代码肯定会比外面执行的代码要慢。

二、选择正确的方法

  • 避免不必要的属性查找
    在计算机科学中,算法的复杂度是使用O 符号来表示的。最简单、最快捷的算法是常数值即O(1)。之后,算法变得越来越复杂并花更长时间执行。
    在js中访问常量和数组元素都是一个O(1)操作
var value = 5;
var sum = 10 + value;
alert(sum);

var values = [5,10];
var sum = values[0] + values[1];
alert(sum);

用变量和数组要比访问对象上的属性更有效率,后者是一个O(n)操作。对象上的任何属性查找都要比访问变量或者数组花费更长时间,因为必须在原型链中对拥有该名称的属性进行一次搜索。简而言之,属性查找越多,执行时间就越长。

//6次属性查找
var query = window.location.href.substring(window.location.href.indexOf("?"));

//优化后,4次属性查找,节省了33%的时间
var url = window.location.href;
var query = url.substring(url.indexOf("?"));
  • 优化循环
    一个循环的基本优化步骤如下所示。
    •(1) 减值迭代——大多数循环使用一个从0 开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。
    •(2) 简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说避免属性查找或其他O(n)的操作。
    •(3) 简化循环体——循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
    •(4) 使用后测试循环——最常用for 循环和while 循环都是前测试循环。而如do-while 这种后测试循环,可以避免最初终止条件的计算,因此运行更快。
for(var i=0; i<values.length; i++){
    process(values[i]);
}
//修改成减值迭代,将终止条件从value.length 的O(n)调用简化成了0 的O(1)调用
for(var i=values.length-1; i>=0; i--){
    process(values[i]);
}
  • 展开循环
  • 避开双重解释
    当JavaScript 代码想解析JavaScript 的时候就会存在双重解释惩罚。当使用eval()函数或者是Function 构造函数以及使用setTimeout()传一个字符串参数时都会发生这种情况。
  • **性能的其他注意事项 **
    •原生方法较快——只要有可能,使用原生方法而不是自己用JavaScript 重写一个。原生方法是用诸如C/C++之类的编译型语言写出来的,所以要比JavaScript 的快很多很多。JavaScript 中最容易被忘记的就是可以在Math 对象中找到的复杂的数学运算;这些方法要比任何用JavaScript 写的同样方法如正弦、余弦快的多。
    •Switch 语句较快 —— 如果有一系列复杂的if-else 语句,可以转换成单个switch 语句则可以得到更快的代码。还可以通过将case 语句按照最可能的到最不可能的顺序进行组织,来进一步优化switch 语句。
    • 位运算符较快 —— 当进行数学运算的时候,位运算操作要比任何布尔运算或者算数运算快。选择性地用位运算替换算数运算可以极大提升复杂计算的性能。诸如取模,逻辑与和逻辑或都可以考虑用位运算来替换。

3、最小化语句数

JavaScript 代码中的语句数量也影响所执行的操作的速度。完成多个操作的单个语句要比完成单个操作的多个语句快。所以,就要找出可以组合在一起的语句,以减少脚本整体的执行时间。这里有几个可以参考的模式。

  • 多个变量声明
// 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();
  • 插入迭代值
    当使用迭代值(也就是在不同的位置进行增加或减少的值)的时候,尽可能合并语句。
var name = values[i];
i++;
// 优化后
var name = values[i++];
  • 使用数组和对象字面量
    种创建数组和对象的方法:使用构造函数或者是使用字面量。使用构造函数总是要用到更多的语句来插入元素或者定义属性,而字面量可以将这些操作在一个语句中完成。
//用4 个语句创建和初始化数组——浪费
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
//用4 个语句创建和初始化对象——浪费
var person = new Object();
person.name = "Tom";
person.age = 26;
person.sayName = function(){
    alert(this.name);
};
//只用一条语句创建和初始化数组
var values = [123, 456, 789];
//只用一条语句创建和初始化对象
var person = {
    name:"Tom",
    age:26,
    sayName:function(){
        alert(this.name);
    }
};

4、优化DOM交互

在JavaScript 各个方面中,DOM 毫无疑问是最慢的一部分。DOM 操作与交互要消耗大量时间,因为它们往往需要重新渲染整个页面或者某一部分。进一步说,看似细微的操作也可能要花很久来执行,因为DOM要处理非常多的信息。理解如何优化与DOM 的交互可以极大得提高脚本完成的速度。

  • 最小化现场更新
    一旦你需要访问的DOM 部分是已经显示的页面的一部分,那么你就是在进行一个现场更新。之所以叫现场更新,是因为需要立即(现场)对页面对用户的显示进行更新。每一个更改,不管是插入单个字符,还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。现场更新进行得越多,代码完成执行所花的时间就越长;完成一个操作所需的现场更新越少,代码就越快。
var list = document.getElementById("myList"),
item,
i;
for(i=0; i<10; i++){
    item = document.createElement("li");
    list.appendChild(item);
    item.appendChild(document.createTextNode("item" + i));
}

8.} 这段代码为列表添加了10 个项目。添加每个项目时,都有2 个现场更新:一个添加<li>元素,另一个给它添加文本节点。这样添加10 个项目,这个操作总共要完成20 个现场更新。
要修正这个性能瓶颈,需要减少现场更新的数量。一般有2 种方法。第一种是将列表从页面上移除,最后进行更新,最后再将列表插回到同样的位置。这个方法不是非常理想,因为在每次页面更新的时候它会不必要的闪烁。第二个方法是使用文档片段来构建DOM 结构,接着将其添加到List 元素中。这个方式避免了现场更新和页面闪烁问题。请看下面内容:

var list = document.getElementById("myList"),
fragment = document.createDocumentFragment(),
item,
i;
for(i=0; i<10; i++){
    item = document.createElement("li");
    fragment.appendChild(item);
    item.append(document.createTextNode("Item" + i));
}
list.appendChild(fragment);

一旦需要更新DOM,请考虑使用文档片段来构建DOM结构,然后再将其添加到现存的文档中。

  • 使用innerHTML
    有两种在页面上创建DOM 节点的方法:使用诸如createElement()和appendChild()之类的DOM 方法,以及使用innerHTML。对于小的DOM更改而言,两种方法效率都差不多。然而,对于大的DOM 更改,使用innerHTML 要比使用标准DOM方法创建同样的DOM 结构快得多。
var list = document.getElementById("myList"),
html = "",
i;
for(i=0; i<10; i++){
    html += "<li>Item" + i + "</li>";
}
list.innerHTML = html;
  • 使用事件代理
  • 注意HTMLCollection
    HTMLCollection 对象的陷阱已经在本书中讨论过了,因为它们对于Web 应用的性能而言是巨大的损害。记住,任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。最小化访问HTMLCollection 的次数可以极大地改进脚本的性能。
    也许优化HTMLCollection 访问最重要的地方就是循环了。前面提到过将长度计算移入for 循环的初始化部分。
var images = document.getElementsByTagName("img"),
i,
len;
for(i=0,len=images.length; i<len; i++){
    //处理
}

这里的关键在于长度length 存入了len 变量,而不是每次都去访问HTMLCollection 的length属性
编写JavaScript 的时候,一定要知道何时返回HTMLCollection 对象,这样你就可以最小化对他们的访问。发生以下情况时会返回HTMLCollection 对象:
• 进行了对 getElementsByTagName() 的调用;
• 获取了元素的 childNodes 属性;
• 获取了元素的 attributes 属性;
• 访问了特殊的集合,如document.forms、document.images 等。

三、部署

……

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 注意作用域 避免全局查找 一个例子: 该函数可能看上去完全正常,但是它包含了三个对于全局document对象的引用...
    Daredevil阅读 3,974评论 5 50
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,132评论 0 13
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,106评论 0 21
  • 天气晴朗不出去逛逛怎么对得起老天的恩赐呢?一觉睡到下午四点,操起相机就出门扫街了,这是我住了二十多年的街道。 顺着...
    九号咖啡屋阅读 427评论 12 10
  • 今夜秋风成笔月如笺 蘸着厚重的子夜书写遥远 这一颗星是你的名字,还有这一颗 那已模糊的誓言 其实我写了好多想念 有...
    清白脸庞阅读 198评论 5 5