最佳实践
可维护性
什么是可维护性的代码
-
如果说代码是可维护的,它需要遵循以下特点
- 可理解性——其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。
- 直观性——代码中的东西一看就能明白,不管其操作过程多么复杂。
- 可适应性——代码以一种数据上的变化不要求完全重写的方法撰写。
- 可扩展性——在代码架构上已考虑到在未来允许对核心功能进行扩展
- 可调试性——当有地方出错时,代码可以给予你足够的信息来尽可能直接地确定问题所在。
代码约定
可读性
- 可读性大部分内容和代码的缩进相关,当所有人使用一样的缩进方式时,整个项目的代码都会更加易于阅读
-
可读性的另一方面是注释,下面内容需要进行注释
- 函数和方法——每个函数或方法都应该包含一个注释,描述其目的和用于完成任务所可能使用的算法。陈述事先的假设也非常重要,如参数代表什么,函数是否有返回值(因为这不能从函数定义中推断出来)。
- 大段代码——用于完成单个任务的多行代码应该在前面放一个描述任务的注释。
- 复杂的算法——如果使用了一种独特的方式解决某个问题,则要在注释中解释你是如何做的。这不仅仅可以帮助其他浏览你代码的人,也能在下次你自己查阅代码的时候帮助理解。
- Hack——因为存在浏览器差异,JavaScript 代码一般会包含一些 hack。不要假设其他人在看代码的时候能够理解 hack 所要应付的浏览器问题。如果因为某种浏览器无法使用普通的方法,所以你需要用一些不同的方法,那么请将这些信息放在注释中。这样可以减少出现这种情况的可能性:有人偶然看到你的 hack,然后“修正”了它,最后重新引入了你本来修正了的错误。
变量和函数命名
-
命名规则
- 变量名应为名词如 car 或 person 。
- 函数名应该以动词开始,如 getName() 。返回布尔类型值的函数一般以 is 开头,如isEnable() 。
- 变量和函数都应使用合乎逻辑的名字,不要担心长度。长度问题可以通过后处理和压缩来缓解
变量类型透明
-
三种表示变量数据类型的方式
-
初始化
//通过初始化指定变量类型var found = false; //布尔型var count = -1; //数字var name = ""; //字符串var person = null; //对象
-
使用匈牙利标记法
//用于指定数据类型的匈牙利标记法var bFound; //布尔型var iCount; //整数var sName; //字符串var oPerson; //对象
-
使用类型注释
//用于指定类型的类型注释var found /*:Boolean*/ = false;var count /*:int*/ = 10;var name /*:String*/ = "Nicholas";var person /*:Object*/ = null
-
松散耦合
-
几条规则
- 勿将 event 对象传给其他方法;只传来自 event 对象中所需的数据;
- 任何可以在应用层面的动作都应该可以在不执行任何事件处理程序的情况下进行;
- 任何事件处理程序都应该处理事件,然后将处理转交给应用逻辑
编程实践
尊重对象所有权
- 不要为实例或原型添加属性;
- 不要为实例或原型添加方法;
- 不要重定义已存在的方法。
- 创建包含所需功能的新对象,并用它与相关对象进行交互;
- 创建自定义类型,继承需要进行修改的类型。然后可以为自定义类型添加额外功能。
避免全局量
//两个全局量——避免!!var name = "Nicholas";function sayName(){alert(name);}//一个全局量——推荐var MyApplication = {name: "Nicholas",sayName: function(){ alert(this.name);}}
避免与null进行比较
function sortArray(values){if (values != null){ //避免! values.sort(comparator);}}function sortArray(values){if (values instanceof Array){ // 推荐 values.sort(comparator);}}
-
如果看到了与null比较的代码,尝试使用以下技术替换
- 如果值应为一个引用类型,使用 instanceof 操作符检查其构造函数;
- 如果值应为一个基本类型,使用 typeof 检查其类型;
- 如果是希望对象包含某个特定的方法名,则使用 typeof 操作符确保指定名字的方法存在于对象上。
使用常量
var Constants = {INVALID_VALUE_MSG: "Invalid value!",INVALID_VALUE_URL: "/errors/invalid.php"};function validate(value){if (!value){ alert(Constants.INVALID_VALUE_MSG); location.href = Constants.INVALID_VALUE_URL;}}
性能
注意作用域
避免全局查找
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.";}//改进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.getElementById("msg");msg.innerHTML = "Update complete.";}
避免with语句
function updateBody(){with(document.body){ alert(tagName); innerHTML = "Hello world!";}}//改进function updateBody(){var body = document.bodyalert(body.tagName);body.innerHTML = "Hello world!";}
选择正确方法
避免不必要的属性查找
优化循环
- 减值迭代——大多数循环使用一个从 0 开始、增加到某个特定值的迭代器。在很多情况下,从最大值开始,在循环中不断减值的迭代器更加高效。
- 简化终止条件——由于每次循环过程都会计算终止条件,所以必须保证它尽可能快。也就是说避免属性查找或其他 O(n)的操作。
- 简化循环体——循环体是执行最多的,所以要确保其被最大限度地优化。确保没有某些可以被很容易移出循环的密集计算。
- 使用后测试循环——最常用 for 循环和 while 循环都是前测试循环。而如 do-while 这种后测试循环,可以避免最初终止条件的计算,因此运行更快
展开循环
//credit: Speed Up Your Site (New Riders, 2003)var iterations = Math.floor(values.length / 8);var leftover = values.length % 8;var i = 0;if (leftover > 0){do { process(values[i++]);} while (--leftover > 0);}do {process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);process(values[i++]);} while (--iterations > 0);
避免双重解释
//某些代码求值——避免!!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);
其他
- 原生方法较快——只要有可能,使用原生方法而不是自己用 JavaScript 重写一个。原生方法是用诸如 C/C++之类的编译型语言写出来的,所以要比 JavaScript 的快很多很多。JavaScript 中最容易被忘记的就是可以在 Math 对象中找到的复杂的数学运算;这些方法要比任何用 JavaScript 写的同样方法如正弦、余弦快的多。
- Switch 语句较快 —— 如果有一系列复杂的 if-else 语句,可以转换成单个 switch 语句则可以得到更快的代码。还可以通过将 case 语句按照最可能的到最不可能的顺序进行组织,来进一步优化 switch 语句。
- 位运算符较快 —— 当进行数学运算的时候,位运算操作要比任何布尔运算或者算数运算快。选择性地用位运算替换算数运算可以极大提升复杂计算的性能。诸如取模,逻辑与和逻辑或都可以考虑用位运算来替换
最小化语句数
多个变量声明
//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 = "Nicholas";person.age = 29;person.sayName = function(){alert(this.name);};//改进//只用一条语句创建和初始化数组var values = [123, 456, 789];//只用一条语句创建和初始化对象var person = {name : "Nicholas",age : 29,sayName : function(){ alert(this.name);}};
优化DOM交互
最小化现场更新
var list = document.getElementById("myList"), fragment = document.createDocumentFragment(), item, i;for (i=0; i < 10; i++) {item = document.createElement("li");fragment.appendChild(item);item.appendChild(document.createTextNode("Item " + i));}list.appendChild(fragment);
使用innerHTML
var list = document.getElementById("myList"), html = "", i;for (i=0; i < 10; i++) {html += "
使用事件代理
HTMLCollection
var images = document.getElementsByTagName("img"), image, i, len;for (i=0, len=images.length; i < len; i++){image = images[i];//处理}
部署
构建过程
-
写的代码不应该原封不动地放入浏览器中
- 知识产权问题 —— 如果把带有完整注释的代码放到线上,那别人就更容易知道你的意图,对它再利用,并且可能找到安全漏洞。
- 文件大小 —— 书写代码要保证容易阅读,才能更好地维护,但是这对于性能是不利的。浏览器并不能从额外的空白字符或者是冗长的函数名和变量名中获得什么好处。
- 代码组织 —— 组织代码要考虑到可维护性并不一定是传送给浏览器的最好方式。
验证
-
JSLint可以查找JavaScript代码中语法错误以及常见的编码错误,可以发掘潜在问题
- eval() 的使用;
- 未声明变量的使用;
- 遗漏的分号;
- 不恰当的换行;
- 错误的逗号使用;
- 语句周围遗漏的括号;
- switch 分支语句中遗漏的 break ;
- 重复声明的变量;
- with 的使用;
- 错误使用的等号(替代了双等号或三等号);
- 无法到达的代码。
压缩
文件压缩
- 删除额外的空白(包括换行);
- 删除所有注释;
- 缩短变量名。
HTTP压缩
-
对于 Apache Web服务器,有两个模块可以进行 HTTP压缩: mod_gzip (Apache1.3.x)和 mod_deflate(Apache 2.0.x)。对于 mod_gzip ,可以给 httpd.conf 文件或者是 .htacces s文件添加以下代码启用对JavaScript的自动压缩:
告诉 mod_zip 要包含任何以.js 结尾的文件mod_gzip_item_include file \.js$