- 要让代码可维护,首先它必须可读。多人协作共同开发的情况下,大家要使用同样的缩进方式。
- 可读性的另外一个方面是注释:
- 函数和方法 - 每个函数或方法都应该包含一个注释,描述其目的和用于完成任务所可能使用的算法。另外还可以包括前置条件的说明,参数的含义,以及是否有返回值等。
- 大段代码 - 用于完成单个任务的多行代码应该在前面放一个描述任务的注释。
- 复杂的算法 - 为了让别人看懂,同时也为了下次自己看代码的时候更容易一些,应该给复杂的算法加一些描述思路的注释。
命名的一般规则:
因为js中的变量是松散类型的,因此很容易忘记变量最初包含的数据类型。可以通过给变量初始化的方式,记住变量的数据类型。
// 通过初始化指定变量类型 var found = false; // 布尔型 var count = -1; // 数字 var name = ""; // 字符串 var person = null; // 对象
减少应用内部的依赖,使代码更容易维护。
直接在HTML里面写JS, 或者直接在JS中创建大量的HTML,都是过于紧密的耦合。
// 使用<script>元素的情况 <script type="text/javascript"> document.write("Hello World"); </script> // 直接在HTML中加入js事件处理代码 <input type="button" value="Click Me" onclick="doSomething()" /> // 在JS中创建大量HTML function insertMessage(msg) { var container = document.getElementById("container"); container.innerHTML = "<div class=\"msg\"><p class=\"post\">" + msg + "</p>" + "<p><em>Latest message above.</em></p></div>"; }
CSS主要负责页面的显示,和JS一样,都是HTML之上的层次。我们经常使用JS来更改样式:
// CSS 对 JavaScript 的紧密耦合 element.style.color = "red"; element.style.backgroundColor = "blue";
可以通过动态更改样式类而非特定样式来实现更松散的耦合:
// CSS 对 JavaScript 的松散耦合 element.className = "edit";
通过只修改某个元素的CSS类,就可以让大部分样式信息严格保留在CSS中。
每个Web application一般都有相当多的事件处理程序,监听着无数的不同的事件。例如:
function handleKeyPress(event) { event = EventUtil.getEvent(event); if (event.keyCode == 13) { var target = EventUtil.getTarget(event); var value = 5 * parseInt(target.value); if (value > 10) { document.getElementById("error-msg").style.display = "block"; } } }
事件处理程序应该从事件对象中提取相关信息,并将这些信息传送给处理应用逻辑的某个方法中。
function validateValue(value) { var value = 5 * parseInt(value); if (value > 10) { document.getElementById("error-msg").style.display = "block"; } } function handleKeyPress(event) { event = EventUtil.getEvent(event); if (event.keyCode == 13) { var target = EventUtil.getTarget(event); validateValue(target.value); } }
应用和业务逻辑之间松散耦合的几条原则:
最多创建一个全局变量,让其他对象和函数位于其中。
// 两个全局变量 - 避免!! var name = "Tom"; function sayName() { alert(name); }
这段代码包含了两个全局变量:变量name和函数sayName()。可以创建一个包含两者的对象:
// 一个全局变量 - 推荐 var myApplication = { name: "Tom", sayName: function() { alert(this.name); } }
要按照所期望的对值进行检查,而非按照不被期望的那些(如null)。
function sortArray(values) { if (values != null) { // 避免!! values.sort(comparator); } }
在这个例子中,values参数应该是一个数组,我们应该检查它是不是一个数组,而不是检查它是否非null。我们可以按照下面的方式进行修改:
function sortArray(values) { if (values instanceof Array) { // 推荐! values.sort(comparator); } }
如果我们看到了和null进行比较的代码,尝试使用以下技术替换:
有关typeof,参见:JavaScript Reference > Operatores > typeof
使用常量,我们可以把数据从应用逻辑中分离出来。看一个例子:
function validate(value) { if (!values) { alert("Invalid value!"); location.href = "/errors/invalid.php"; } }
在这个例子中有两段数据:要显示给用户的信息以及URL。
通过将数据提取出来作为单独定义的常量的方式,可以将应用逻辑与数据修改隔离开来:
var Constants = { INVALID_VALUE_MSG: "Invalid value!", INVALID_VALUE_URL: "/errors/invalid.php" }; function validate(value) { if (!values) { alert(Constants.INVALID_VALUE_MSG); location.href = Constants.INVALID_VALUE_URL; } }
要注意的值的类型如下所示:
随着作用域中的作用链的数量的增加,访问当前作用域以外的变量的时间也在增加。
可能优化脚本性能最重要的就是注意全局查找。使用全局变量和函数肯定要比局部的开销更大,因为要涉及到作用域链上的查找。请看以下函数:
function updateUI() { var imgs = document.getElementByTagName("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 = "更新完成!"; }
上述代码包含了三个对全局变量document对象的引用,特别是for循环中的document 引用每次都要进行作用域链查找。
通过创建一个指向document 对象的局部变量,就可以通过限制一次全局查找来改进这个函数的性能:
function updateUI() { var doc = document; var imgs = doc.getElementByTagName("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 = "更新完成!"; }
现在的函数只有一次全局查找。将在一个函数中会多次用到的全局对象存储为局部变量总是没错的。
with语句主要用于消除额外的字符。但是with语句会创建自己的作用域,因此会增加其中执行的代码的作用域链的长度。
使用局部变量可以达到with语句同样的效果
function updateBody() { with(document.body) { alert(tagName); innerHTML = "Hello, world"; } }
function updateBody() { var body = document.body; alert(body.tagName); body.innerHTML = "Hello, world"; }
在上面的代码中,通过将 document.body 存储在局部变量中省去了额外的全局查找。在性能非常重要的地方要避免使用with语句。
字面量和存储在常量中的值,代码复杂度是O(1)。
var value = 5; var sum = 10 + value; alert(sum);
在JS中访问数组元素也是一个O(1)复杂度。
var values = [5, 10]; var sum = values[0] + values[1]; alert(sum);
访问对象上的属性复杂度是O(n)。对象上的任何属性查找都要比访问变量或者数组花费更长时间,因为必须在原型链中对拥有该名称的属性进行一次搜索。
var values = {first: 5, second: 10]; var sum = values.first + values.second; alert(sum);
注意获取单个值的多重属性查找:
var query = window.location.href.substring(window.location.href.indexOf("?"));
共有6次属性查找。可以把对象属性存储到局部变量。第一次访问该值会是O(n), 然后连续的访问都会是O(1)。
var url = window.location.href; var query = url.substring(url.indexOf("?"));
这个版本的代码只有4次属性查找。
一个基本的 for 循环
for (var i=0; i<values.length; i++) { process(values[i]); }
变量 i 从 0 递增到values数组中的元素总数。如果不考虑值的处理顺序,那么循环可以由 i++ 改成 i--, 如下所示:
for (var i=values.length-1; i>=0; i--) { process(values[i]); }
修改以后,每次循环过程中对终止条件的判断的复杂度由O(n)简化成了O(1)调用。
优化循环的步骤在很多语言中都是一样的:
JavaScript代码中语句的数量也会影响到所执行操作的速度。将多个单条语句组合在一起,可以减少脚本的整体的执行时间。
// 4 条语句的变量声明 var count = 5; var color = "blue"; var values = [1,2,3]; var now = new Date();
由于JavaScript中所有的变量都可以使用var来声明,因此前面的代码可以重写为一个语句:
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;
可以很容易地转换成使用字面量的形式:
var values = [123, 456, 789];
// 用 4 个语句创建和初始化对象 - 浪费 var person = new Object(); person.name = "Tom"; person.age = 31; person.sayName = function() { alert(this.name); }
对象同样可以很容易地转换成使用字面量的形式:
var person = { name: "Tom", age: 32, sayName: function() { alert(this.name); } }