Javascript 最佳实践

本篇指南分两部分,翻译自 Mozilla 的 Web 布道师 Christian Heilmann 的 PPT (需翻墙)

原文:
Javascript BEST PRACTICES PART 1
Javascript BEST PRACTICES PART 2
译者:涂鸦码龙

Make it Understandable(更易理解)

变量和函数选择容易理解,较短的单词命名。

不好的变量名:

1
x1 fe2 xbqne



也不好的变量名:

1
2
incrementerForMainLoopWhichSpansFromTenToTwenty
createNewMemberIfAgeOverTwentyOneAndMoonIsFull


避免用变量或函数名描述一个值。

在有的国家也许讲不通:

1
isOverEighteen()



工作良好:

1
isLegalAge()


Your code is a story - make your storyline easy to follow!
你的代码是一则故事 - 让故事情节更容易投入。

## 避免全局变量

全局变量是魔鬼

原因:其它后面的 JavaScript 代码随时会覆盖你的代码。

变通方案:使用闭包和模块模式


问题:所有全局变量都可以被访问;访问不受控制,页面任何东西都可以被覆盖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var current = null;
var labels = {
'home':'home',
'articles':'articles',
'contact':'contact'
};
function init(){
};
function show(){
current = 1;
};
function hide(){
show();
};

对象字面量:所有东西都被包含起来,但是通过对象名字可以访问。

问题:重复的模块名称导致代码庞大恼人。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
demo = {
current:null,
labels:{
'home':'home',
'articles':'articles',
'contact':'contact'
},
init:function(){
},
show:function(){
demo.current = 1;
},
hide:function(){
demo.show();
}
}

模块模式:你需要指定哪些是全局的,哪些不是 —— 转换两者语法。

问题:重复的模块名称,内部函数的不同语法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module = function(){
var labels = {
'home':'home',
'articles':'articles',
'contact':'contact'
};
return {
current:null,
init:function(){
},
show:function(){
module.current = 1;
},
hide:function(){
module.show();
}
}
}();

Revealing Module Pattern(揭示模块模式):暴露的全局变量和局部变量,保持语法一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module = function(){
var current = null;
var labels = {
'home':'home',
'articles':'articles',
'contact':'contact'
};
var init = function(){
};
var show = function(){
current = 1;
};
var hide = function(){
show();
}
return{init:init, show:show, current:current}
}();
module.init();

坚持一种严格的编码风格

浏览器是非常宽容的 JavaScript 解析器。但是,当你转换到另一个环境,或者移交给另一个开发者时,松懈的代码风格对自己很不利。Valid code is secure code(有效的代码是安全的代码)。

验证你的代码:http://www.jslint.com/

需要注释,不要过分注释

“Good code explains itself (好的代码会自我解释)”是傲慢的神话。

需要的时候加上注释 —— 但是不要长篇大论。

避免用单行注释 //。/* */ 更安全,当删除换行符时不会导致出错。

如果你使用注释调试,这是个不错的小技巧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module = function(){
var current = null;
/*
var init = function(){
};
var show = function(){
current = 1;
};
var hide = function(){
show();
}
// */
return{init:init, show:show, current:current}
}();

HTML 或 JavaScript 里的注释不应该给最终用户看到。理解 Development code is not live code (开发代码不是线上代码)

避免与其它技术混用

JavaScript 擅长计算,转换,访问外部资源(Ajax)和定义界面的交互行为(事件处理)。别的事情应该让别的技术完成。

例如:

当所有带“mandatory”class 的区域是空时,用红色边框标注一下。
1
2
3
4
5
6
7
8
9
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' && inputs.value === ''){
inputs[i].style.borderColor = '#f00';
inputs[i].style.borderStyle = 'solid';
inputs[i].style.borderWidth = '1px';
}
}
… 两个月下来:所有的样式必须遵循新公司的风格指南,不允许有边框,错误应该有个警示图标。怎么办呢?



我们不必通过改变 JavaScript 代码来改变外观和感觉。
1
2
3
4
5
6
7
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' && inputs.value === ''){
inputs[i].className+=' error';
}
}

使用 CSS 继承可以避免循环许多元素。

使用简洁的写法

一旦你习惯简洁写法,代码会更清爽,更易读。

这段代码

1
2
3
4
5
var lunch = new Array();
lunch[0]='Dosa';
lunch[1]='Roti';
lunch[2]='Rice';
lunch[3]='what the heck is this?';

等价于

1
2
3
4
5
6
var lunch = [
'Dosa',
'Roti',
'Rice',
'what the heck is this?'
];

这段代码

1
2
3
4
5
if(v){
var x = v;
} else {
var x =10;
}

等价于

1
var x = v || 10;

这段代码

1
2
3
4
5
6
var direction;
if(x > 100){
direction = 1;
} else {
direction = -1;
}

等价于

1
var direction = (x > 100) ? 1 : -1;

模块化

保持代码的模块化和专业化

我们很容易写一个函数做所有事,可是当扩展功能时,发现好几个函数做同样的事情。

为了避免此事,确保写较小的,通用辅助函数,完成一个特定的任务,而不是所有的方法。

再后来,你可以使用 revealing module pattern(揭示模块模式)暴露一些方法,创建一套 API 扩展主要功能。

好的代码应该易于扩展,不需要重写核心。

渐进增强

避免创建许多 JavaScript 依赖代码

DOM 操作又慢又昂贵。

当禁用 JavaScript 时,依赖 JavaScript 的元素要仍然可用。

允许配置和转化

代码也许会变,代码结构不应该是松散的。

包括标签,CSS 类,ID 和默认配置。

把这些放进一个配置对象,保持公开,我们很容易维护,并且可以自由定制。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
carousel = function(){
var config = {
CSS:{
classes:{
current:'current',
scrollContainer:'scroll'
},
IDs:{
maincontainer:'carousel'
}
},
labels:{
previous:'back',
next:'next',
auto:'play'
},
settings:{
amount:5,
skin:'blue',
autoplay:false
}
};
function init(){
};
function scroll(){
};
function highlight(){
};
return {config:config,init:init}
}();

避免严重嵌套

多层嵌套的代码很不易读。

循环嵌套循环是个坏主意,需要小心几个迭代器变量(i, j, k, l, m…)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function renderProfiles(o){
var out = document.getElementById('profiles');
for(var i=0;i<o.members.length;i++){
var ul = document.createElement('ul');
var li = document.createElement('li');
li.appendChild(document.createTextNode(o.members[i].name));
var nestedul = document.createElement('ul');
for(var j=0;j<o.members[i].data.length;j++){
var datali = document.createElement('li');
datali.appendChild(
document.createTextNode(
o.members[i].data[j].label + ' ' +
o.members[i].data[j].value
)
);
nestedul.appendChild(detali);
}
li.appendChild(nestedul);
}
out.appendChild(ul);
}

使用专门的工具方法避免重度的循环嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function renderProfiles(o){
var out = document.getElementById('profiles');
for(var i=0;i<o.members.length;i++){
var ul = document.createElement('ul');
var li = document.createElement('li');
li.appendChild(document.createTextNode(data.members[i].name));
li.appendChild(addMemberData(o.members[i]));
}
out.appendChild(ul);
}
function addMemberData(member){
var ul = document.createElement('ul');
for(var i=0;i<member.data.length;i++){
var li = document.createElement('li');
li.appendChild(
document.createTextNode(
member.data[i].label + ' ' +
member.data[i].value
)
);
}
ul.appendChild(li);
return ul;
}

优化循环

JavaScript 循环可能非常慢。

不要让 JavaScript 每次迭代都读取数组的长度,用变量保存长度的值。

不好:

1
2
3
4
5
6
7
var names = ['George',
'Ringo',
'Paul',
'John'];
for(var i=0;i<names.length;i++){
doSomethingWith(names[i]);
}

好:

1
2
3
4
5
6
7
var names = ['George',
'Ringo',
'Paul',
'John'];
for(var i=0,j=names.length;i<j;i++){
doSomethingWith(names[i]);
}

重度计算代码放到循环外边,包括正则表达式,首要的是 DOM 操作。

你可以在循环里创建 DOM 节点,但是避免插入文档对象。

DOM 访问最小化

如果可以避免,不要访问 DOM。

原因:比较慢,有各种各样的浏览器问题

解决方案:编写或使用一个辅助方法,批量把数据集转换成 HTML 。

所有的数据在一个方法中处理,最后把结果一次性加入到 DOM 中去。

不要屈服于浏览器的奇怪表现

依赖浏览器稀奇古怪的行为,不如希望它一切正常。

避免使用一些奇技淫巧,分析问题的根源。

很多时候,你会发现你需要额外的功能,是因为你的界面规划不好。

不要信任任何数据

好的代码不会信任收到的任何数据。

  • 不要相信 HTML 文档
    任何用户都可以干预它,比如通过 Firebug 。
  • 不要相信你的函数接收的数据格式永远正确。
    用 typeof 测试,然后再处理它。
  • 不要预期 DOM 中的元素是可用的。
    改变它们之前测试一下,确保它们如你的预期。
  • 不要妄图使用 JavaScript 保护一些东西。
    JavaScript 代码很容易被破解 :)

通过 JavaScript 添加功能,而不是添加内容

如果你发现 JavaScript 里面有过多的 HTML ,你可能做错了什么。

创建使用 DOM 不是很方便,一般用 innerHTML ,这样很难追踪 HTML 的代码质量。

如果的确有大量的界面,可以通过 Ajax 加载静态的 HTML 文档界面。

这种方式 HTML 好维护,并且支持定制。

站在巨人的肩膀上

JavaScript 很有趣,不过针对所有浏览器写 JavaScript 不那么有趣… 所以从一个好框架入手。

JavaScript 框架专门用于堵浏览器的漏洞,让浏览器行为和你的代码更符合预期。

好的框架帮助你写代码,帮你节省了一些支持主流浏览器的开销。

开发代码不等于线上代码

线上代码为机器所写,开发环境代码为人而写。

  • 构建流程包含核对,最小化和优化代码。
  • 不要过早优化。
  • 如果我们减少了编码时间,可以有更多时间完善向机器码的转换。

参考资料:
Learning JavaScript Design Patterns