XieYang-blog

初学 javascript 的 24 条最佳实践

注:本文多次用到 Firebug 的 console 对象,请参考 Firebug Console API。关于 Firebug 的更详细介绍,请猛击这里。

使用 “===” 代替 “==”

JavaScript 里有两种不同的相等运算符:=== | !== 和 == | != 。相比之下,前者更值得推荐。请尽量使用前者。

“如果两边的操作数具有相同的类型和值,=== 返回true,!== 返回 false。” —— JavaScript语言精粹


不过,如果使用 == 和 != ,在操作不同数据类型时, 你可能会遇到一些意想不到的问题。在进行相等判断前,JavaScript 会试图将它们转换为字符串、数字或 Boolean 量。

Eval = Bad(避免使用eval函数,参考

起初不太熟悉时,“eval” 让我们能够访问 JavaScript 的编译器(译注:这看起来很强大)。从本质上讲,我们可以将字符串传递给 eval 作为参数,把字符串作为 javascript 代码执行,返回结果。

这不仅大幅降低脚本的性能(译注:JIT 编译器无法预知字符串内容,而无法预编译和优化),而且这也会带来巨大的安全风险,因为这样付给要执行的文本太高的权限,千万别用!

省略未必省事

从技术上讲,你可以省略大多数花括号和分号。大多数浏览器都能正确理解下面的代码:

1
2
if (someVariableExists)
x = false

然后,如果像下面这样:

1
2
if (someVariableExists)
x = false anotherFunctionCall();

有人可能会认为上面的代码等价于下面这样:

1
2
3
4
if (someVariableExists) {
x = false;
anotherFunctionCall();
}

不幸的是,这种理解是错误的。实际上的意思如下:

1
2
3
4
if (someVariableExists) {
x = false;
}
anotherFunctionCall();

你可能注意到了,上面的缩进容易给人花括号的假象。无可非议,这是一种可怕的实践,应不惜一切代价避免。仅有一种情况下,即只有一行的时候,花括号是可以省略的,但这点也是饱受争议的。

1
if (2 + 2 === 4) return 'nicely done';

建议:

很可能,有一天你需要在 if 语句块中添加更多的语句。这样的话,你必须重写这段代码。底线——省略是雷区。到底还得加上。

使用JSLint

JSLint 是由大名鼎鼎的道格拉斯(Douglas Crockford)编写的调试器。简单的将你的代码粘贴进JSLint中,它会迅速找出代码中明显的问题和错误。

“JSLint扫面输入的源代码。如果发现一个问题,它返回一条描述问题和一个代码中的所在位置的消息。问题并不一定是语法错误,尽管通常是这样。JSLint还会查看一些编码风格和程序结构问题。这并不能保证你的程序是正确的。它只是提供了另一双帮助发现问题的眼睛。” ——JSLing 文档


部署脚本之前,运行JSLint,只是为了确保你没有做出任何愚蠢的错误。

将脚本放在页面的底部

在本系列前面的文章里已经提到过这个技巧,我粘贴信息在这里。

请记住 —— 我们要千方百计保证客户端的页面载入速度尽可能的快。而脚本没载入完成,浏览器就没法加载页面的剩余部分。

如果你的JS文件只是添加一些额外功能 —— 例如,为点击某链接绑定事件——那大可以等页面加载基本完成后再做。把JS文件放到页面最后,body的结束标签之前,这样做最好了。

建议

1
2
3
4
5
        <p>And now you know my favorite kinds of corn. </p> 
<script type="text/javascript" src="path/to/file.js"></script>
<script type="text/javascript" src="path/to/anotherFile.js"></script>
</body>
</html>

避免在For语句内声明变量

当需要执行冗长的for语句时,不要让JavaScript引擎每次都重复那些没有必要的操作。例如:

糟糕

1
2
3
4
5
for (var i = 0; i < someArray.length; i++) {
var container = document.getElementById('container');
container.innerHtml += 'my number: ' + i;
console.log(i);
}

这段代码每次都重新定义数组长度,每次都在遍历DOM寻找container元素 —— 太傻了!

建议

1
2
3
4
5
var container = document.getElementById('container');
for (var i = 0, len = someArray.length; i < len; i++) {
container.innerHtml += 'my number: ' + i;
console.log(i);
}

构建字符串的最优方法

当你需要遍历数组或对象的时候,不要总想着 “for” 语句,要有创造性,总能找到更好的办法,例如,像下面这样。

1
2
var arr = ['item 1', 'item 2', 'item 3', ...];
var list = '<ul><li>' + arr.join('</li><li>') + '</li></ul>';

我不是你心中神,但请你相信我(不信你自己测试) —— 这是迄今为止最快的方法!

使用原生代码(如 join()),不管系统内部做了什么,通常比非原生快很多。 —— James Padolsey, james.padolsey.com

减少全局变量

只要把多个全局变量都整理在一个名称空间下,将显著降低与其他应用程序、组件或类库之间产生糟糕的相互影响的可能性。 —— Douglas Crockford

1
2
3
4
5
6
var name = 'Jeffrey';
var lastName = 'Way';

function doSomething() {...}

console.log(name); // Jeffrey -- 或 window.name

更好的做法

1
2
3
4
5
6
var DudeNameSpace = {
name: 'Jeffrey',
lastName: 'Way',
doSomething: function () {...}
}
console.log(DudeNameSpace.name); // Jeffrey

注:这里只是简单命名为 “DudeNameSpace”,实际当中要取更合理的名字。

给代码添加注释

可能一开始你会觉得并无必要,但相信我,你将来会主动想要尽可能写好代码的注释的。当你几个月后再回看某项目时,结果却发现很难想起当时写某句东西时脑子在想的什么了,是不是很让人沮丧呢?或者,如果有同事要修订你的代码呢?一定,一定要为你代码里的重要部分加上注释。

1
2
3
4
// 循环数组,输出每项名字(译者注:这样的注释似乎有点多余吧)
for (var i = 0, len = array.length; i < len; i++) {
console.log(array[i]);
}

试试渐进增强

一定要记得为未启用 JavaScript 的情况提供替代方案。大家可能会认为,“大部分我的访客都启用了JavaScript的,我才不用担心”。这样的话,你可就大错特错了!

你有没有试过看看禁用JavaScript后你那漂亮的滑动器都成啥样了?(你可以下载 Web Developer ToolBar 轻松完成这项任务。)禁用之后你的网站可能就彻底失去了可用性!经验之谈:开发初期总是按照没有JavaScript来设计你的网站,之后再进行渐进地功能增强,小心翼翼地改变你地布局。

不要给 “setInterval” 或 “setTimeout” 传递字符串参数

考虑下面的代码:

1
setInterval("document.getElementById('container').innerHTML += 'My new number: ' + i", 3000);

不仅执行不高效,而且和 eval 函数有着同样的高风险。千万不要把字串传递给 setInterval 和 setTimeout。恰当的做法是,传递一个函数名:

1
setInterval(someFunction, 3000);

不要使用 “with” 语句

初识之下,“with” 语句似乎还挺好用的,它用于设置代码在特定对象中的作用域。其基本用法是提供深入到对象中处理元素的快速写法。例如:

1
2
3
4
with (being.person.man.bodyparts) {
arms = true;
legs = true;
}

而不是像下面这样:

1
2
being.person.man.bodyparts.arms = true;
being.person.man.bodyparts.legs = true;

不幸的是,测试表明,若你要为对象插入新成员,with的表现非常糟糕,它的执行速度非常缓慢。替代方案是声明一个变量:

1
2
3
var o = being.person.man.bodyparts;
o.arms = true;
o.legs = true;

使用 {} 代替 new Ojbect()

在JavaScript有多种方式能新建对象,最传统的方法是 new 语句,如下:

1
2
3
4
5
6
var o = new Object();
o.name = 'Jeffrey';
o.lastName = 'Way';
o.someFunction = function () {
console.log(this.name);
}

不过,这一方法读起来却比较糟糕。我强烈建议你采用下面这种在文字样式上更为强健的写法:

更好的做法

1
2
3
4
5
6
7
var o = {
name: 'Jeffrey',
lastName: 'Way',
someFunction: function () {
console.log(this.name);
}
};

注意,如果你只是想创建一个空对象,{} 更好。

1
var o = {};

“对象字面量使我们能够编写更具特色的代码,而且相对简单的多。不需要直接调用构造函数或维持传递给函数的参数的正确顺序,等” —— dyn-web.com

使用 [] 代替 new Array()

这同样适用于创建一个新的数组。

例如:

1
2
3
var a = new Array();
a[0] = "Joe";
a[1] = 'Plumber';

更好的做法:

1
var a = ['Joe', 'Plumber'];

“javascript 程序中常见的错误是在需要对象的时候使用数组,而需要数组的时候却使用对象。规则很简单:当属性名是连续的整数时,你应该使用数组。否则,请使用对象” —— Douglas Crockford

定义多个变量时,省略 var 关键字,用逗号代替

1
2
3
var someItem = 'some string'; 
var anotherItem = 'another string';
var oneMoreItem = 'one more string';

更好的做法

1
2
3
var someItem = 'some string', 
anotherItem = 'another string',
oneMoreItem = 'one more string';

…不言自明。我不知道这样做能否提升代码执行速度,但是确实让你的代码干净许多。

谨记,不要省略分号

从技术上讲,大多数浏览器允许你省略分号。

1
2
3
var someItem = 'some string' function doSomething() { 
return 'something'
}

之前已经说过,这样做会造成潜在的更大、更难以发现的问题:

更好的做法

1
2
3
var someItem = 'some string'; function doSomething() { 
return 'something';
}

“For in” 语句

遍历对象时,你可能会发现你还需要获取方法函数。所以遇到这种情况时,请一定记得给你的代码包一层 if 语句,用以过滤信息。

1
2
3
4
5
6
7
for (key in object) {
if (object.hasOwnProperty(key)) {
...
then
do something...
}
}

参考 JavaScript:语言精粹,道格拉斯(Douglas Crockford)。

使用 Firebug 的 “timer” 功能优化你的代码

想要轻松地快速了解某项操作的用时吗?使用Firebug的timer功能来记录结果好了。

1
2
3
4
5
6
function TimeTracker() {
console.time("MyTimer");
for (x = 5000; x > 0; x--) {
}
console.timeEnd("MyTimer");
}

阅读,阅读,反复阅读

虽然我是 Web 开发博客(就像这个!)的超级粉丝,但吃饭和睡觉前除了看书好像也别无选择~ 在你的床头柜上摆一本 Web 开发的好书吧!下列书单都是我的最爱:

Object-Oriented JavaScriptJavaScript面向对象编程指南 pdf
JavaScript:The Good PartsJavaScript语言精粹 修订版 pdf
Learning jQuery 1.3jQuery基础教程 第4版 pdf
Learning JavaScriptJavaScript学习指南 pdf

读了他们……多次。我仍将继续!

自执行函数

相比于调用函数,让函数在页面载入或者某一父函数被调用时自动执行,是十分简单方便的做法。你只需要把你的函数包在父辈之内,然后添上一个额外的括号,本质上这括号就触发了你定义的函数(了解更多)。

1
2
3
4
5
6
(function doSomething() {
return {
name: 'jeff',
lastName: 'way'
};
})();

原生代码永远比库快

诸如 jQuery 和 Mootools 这样的 JavaScript 库,能为你写代码的过程省下不少时间——尤其是当需要 AJAX 操作时。不过你可得记住,只要你的代码写得恰当,原生 JavaScript 总是会比利用代码库的写法执行得快一些。

jQuery 的 “each” 方法对于循环操作十分便利,但是使用原生态的for语句总归会快很多。

Crockford(道格拉斯)的 JSON.Parse

尽管 JavaScript 2 会内建 JSON 处理器,但写这篇文章之时,我们还是需要自己实现。Douglas Crockford,JSON 的创建者,已经为我们创作出能直接使用的处理器了。查看相关信息

导入这段代码,你就能新建 JSON 全局对象,然后处理你的 .json 文件。

1
2
3
4
5
var response = JSON.parse(xhr.responseText);
var container = document.getElementById('container');
for (var i = 0, len = response.length; i < len; i++) {
container.innerHTML += '<li>' + response[i].name + ' : ' + response[i].email + '</li>';
}

移除 “language” 属性

很多年前,language还是每段script标签必备属性:

1
2
3
<script type="text/javascript" language="javascript"> 
...
</script>

然而,这个属性早已被弃用,所以请移除(译者注:html5 中已废弃,但如果你喜欢,你仍然可以添加)。

本文为翻译文章,原文为“24 JavaScript Best Practices for Beginners”,2009年

关于#20 的补充,下面是译者认为的一些好书,有兴趣的读者可以留言讨论

  1. javascript模式(和上面JavaScript面向对象编程指南同一作者,这本书更好)
  2. javascript设计模式
  3. 编写可维护的javascript(尼古拉斯新书)
  4. 高性能javascript(尼古拉斯 已绝版)
  5. javascript语言精髓与编程实践
  6. javascript高级程序设计(尼古拉斯)
🐶 您的支持将鼓励我继续创作 🐶