编写可维护的JavaScript---UI层的松耦合

一. UI层的松耦合

1. 将JavaScript从CSS中抽离

在IE8和早版本的浏览器有一个特性,CSS表达式:

1
2
3
#box{
width: expression(document.body.offsetWidth + "px");
}

浏览器会以高频率重复计算CSS表达式,严重影响性能,且很难维护,所以要避免使用CSS表达式
值得庆幸的是IE9不再支持CSS表达式了。

2. 将CSS从JavaScript中抽离

可以同过脚本修改样式,最常见的是在js中修改DOM元素的style属性:

1
2
3
element.style.color = "red";
element.style.left = "10px";
element.style.top = "20px";

这样的方法带来的最大的问题就是可维护性问题,所以最佳的方法是操作CSS的className

1
2
3
4
5
6
7
8
9
10
11
12
//css:
.default{
color:red;
left:10px;
top:10px;
}

//js:
//原生方法:
element.className += " default";
//jQuery:
$(element).addClass("default");

由于CSS的className可以成为CSS和JavaScript之间的通信桥梁,在页面中JavaScript可以随意添加或者删除元素的className。而className所定义的样式在CSS中,CSS中的样式是可以随时修改的,但是不需要更新JavaScript代码,实现了CSS的松耦合。

3. 将JavaScript从HTML中抽离

将脚本嵌入到HTML中运行:

1
<button onclick="doSomething()">click me</button>

这种方法在之前是很流行的,但却是两个UI层(JavaScript和HTML)的深耦合;
缺点是:第一,在点击按钮的时候doSomething函数必须存在;第二,可维护性低。
最佳的做法是JavaScript代码包含在外部文件中,在页面中通过<script>标签来引用

4. 将HTML从JavaScript中抽离

在JavaScript中可以通过HTML的innerHTML属性赋值:

1
2
var div = document.getElementById('test');
div.innerHTML = "<h3>Error></h3><p>Invalid email address.</p>";

这样做的缺点是:第一,它增加了跟踪文本和结构性问题的复杂度;第二,可维护性低。

降低HTML和JavaScript之间的耦合的方法:

1)从服务器加载:
将模板放置于远程服务器,使用XMLHttpRequest对象来获取外部标签。这种方法会对单页面应用带来更多的便捷。
例如,点击一个按钮弹出一个对话框:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function loadDialog( name, oncomplete ){
var xhr = new XMLHttpRequest();
xhr.open('get','js/dialog/' + name, true);

xhr.onreadystatechange = function(){

if( xhr.readystate == 4 && xhr.status == 200 ){

var div = document.getElementById('dialog-holder');
div.innerHTML = xhr.responseText;
oncomplete();
} else {
//处理错误
}
}
}

2)简单客户端模板:
客户端模板是一些带“插槽”的标签片段,这些“插槽”会被JavaScript程序替换为数据以保证模板的完整可用。

1
2
3
4
5
6
7
8
9
10
11
<li><a href="%s">%s</a></li>
// %s为占位符,这个位置的文本会被程序替换;

function sprintf( text ){
var i =1, args = arguments;
return text.replace( /%s/g, function(){
return ( i < args.length ) ? args[i+1] : "";
});
}
// 用法
var result = sprintf( templateText, "/item/4", "Fourth item" );

通常我们将模板定义在其他标签之间,直接存放在HTML中,这样就可以被JavaScript读取,用以下两种方法之一即可做到:
方法一: 是在HTML注释中包含模板文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ul id="mylist"><!--<li id="item%s"><a href="%s">%s</a></li>-->
<li><a href="/item/1">first item </a></li>
<li><a href="/item/2">second item </a></li>
<li><a href="/item/3">third item </a></li>
</ul>

function addItem( url, text ){
var mylist = document.getElementById("mylist"),
templateText = mylist.firstChild.nodeValue,
result = sprintf( template, url, text );
div.innerHTML = result;
mylist.insertAduacentHTML( "beforeend", result );
}

// 用法
addItem( "/item/4", "fourth item");

方法二: 是使用一个带有自定义type属性的<script>元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/x-my-template" id="list-item">
<li><a href="%s">%s</a></li>
</script>

function addItem( url, text ){
var mylist = document.getElementById("mylist"),
script = document.getElementById('list-item'),
templateText = script.text,
result = sprintf( template, url, text ),
div = document.createElement("div");

div.innerHTML = result.replace( /^\s*/, "");
mylist.appendChild( div.firstChild );
}

// 用法
addItem( "/item/4", "fourth item");

3)复杂客户端模板:

例如handlebars提供的解决方案:
handlebars是专为浏览器JavaScript设计的完整的客户端模板系统;
首先必须将Handlebars类库引入到页面,这个类库会创建一个名为Handlebars的全局变量,用来将模板文本编译为一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script type="text/x-handlebars-template" id="list-item">
<li><a href="{{url}}">{{text}}</a></li>
</script>
// 双花括号是占位符

function addItem( url, text ){
var mylist = document.getElementById("mylist"),
script = document.getElementById('list-item'),
templateText = script.text,
template = Handlebars.compile( script.text ),
div = document.createElement("div"),
result;

result = template({
text : text,
url : url
});

div.innerHTML = result;
mylist.appendChild( div.firstChild );
}

// 用法
addItem( "/item/4", "fourth item");

Handlebars 模板还支持一些简单的逻辑和循环。

[参考资料]:
编写可维护的JavaScript,Nicholas C. Zakas 著,李晶 郭凯 张散集 译, Copyright 2012 Nicholas Zakas,978-7-115-31008-8

baishiwen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!