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

在web开发中,用户界面中是由三个彼此隔离又相互作用的层定义的:

  • HTML用来定义页面的数据和语义。
  • CSS用来给页面添加样式,创建视觉特征。
  • JavaScript用来给页面添加行为,使其更具交互性。

很多设计模式就是为了解决紧耦合的问题。如果两个组件耦合太紧,则说明一个组件和另一个组件直接相关,这样的话,如果修改一个组件的逻辑,那么另外一个组件的逻辑也要修改,这是很致命的。

当你做到修改一个组件而不需要更改其他的组件时,就做到了松耦合。对于多人大型系统来说,有很多人参与维护代码,松耦合对于代码可维护性来说至关重要。

需要注意的是:在一起工作的额组件无法达到“无耦合”。在所有系统中,组件之间总要共享一些信息来完成各自的工作。这很好理解,我们的目标是确保对一个组件的修改不会经常性地影响其他部分。

1. 将CSS从JavaScript中抽离出来

有时候,保持CSS和JavaScript之间清晰的分离是很有挑战的。这两门语言相互协作得很不错,所以我们经常讲CSS和JavaScript混在一起写。

// 不好的写法
element.style.color = "red";

如上,这种方法是有问题的,因为样式信息是通过JavaScript而非CSS来承载的。当出现了样式问题,你通常首先会去查找CSS,知道你精疲力竭得排除了所有可能性,才会去JavaScript中查找样式信息。

讲CSS从JavaScript中抽离出来意味着所有的样式信息都应当保持在CSS中。当需要哦通过JavaScript来修改元素样式的时候,最佳的方法是操作CSS的className,比如,我想再页面中显示一个对话框,在CSS中的样式定义是像下面这样的。

.reveal {
    color: red;
    left: 10px;
    right: 10px;
    visibility: visible;
}

然后在JavaScript中像这样将样式添加至元素。

// 好的写法 — jQuery
$(element).addClass("reveal");

有一种使用style属性的情形是可以接受的:当你需要给页面中的元素作定位,使其相对于另一个元素或整个页面重新定位。这种计算是无法再CSS 中完成的,因此这时是可以使用style.top、style.left等来对元素进行定位的,在CSS中定义这个元素的默认属性,而在JavaScript中修改这些默认值。

2. 将JavaScript从HTML中抽离

很多人学习JavaScript之初都是将脚本嵌入到HTML中来运行,这种写法在2000年的时候非常流行。HTML代码中放满了onclick和其他时间处理程序,很多元素都包含这样的属性。尽管这种代码在多数场景下是正常工作的,但却是两个UI层的深耦合,因此这种写法是有一些问题的。

// 好的写法
function doSomething () {
    // code
}

var btn = document.getElementById('action-btn');
btn.addEventListener('click', doSomething, false);

这种方法的优势在于,函数doSomething()的定义和事件处理程序的绑定都是在一个文件中完成的,如果函数名称需要修改,则只需要修改一个文件;如果发生点击时想额外做一些动作,也只需要一处做修改。

3. 将HTML从JavaScript中抽离

正如我们需要将JavaScript从HTML中抽离一样,最好也将 HTML从JavaScript中抽离。就像上文提到的,当需要调试一个文本或结构性的问题时,更希望从HTML开始调试,而不是忙活了半天,发现出问题的部分在JavaScript中的HTML语句中。

方法1:从服务器加载

第一种方法是将模板放置于远程服务器,使用XMLHttpRequest对象来获取外部标签。相比于多页应用,这种方法对单页应用带来更多的便捷。例如,点击一个链接,希望弹出一个新的对话框,代码可能如下:

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("dlg-holder");
            div.innerHTML = xhr.responseText;
            oncomplete();
        }
    }
}

这里没有将HTML字符串嵌入在JavaScript里,而是向服务器发起请求获得字符串,这样可以让HTML代码以最合适的方式注入到页面中。当你需要注入大段HTML标签到页面中时,使用远程调用的方式来加载标签是非常有帮助的。出于性能的原因,将 大量没有用的标签存放于内存或DOM中是很糟糕的做法。

方法2:简单客户端模板

客户端模板是一些带插槽的标签片段,这些插槽会被JavaScript程序替换为数据以保证模板的完整可用。比如,一段用来添加数据项的模板看起来就像下面这样:

<li><a href="%s">%s</a></li>

这段模板中包含%s这个占位符,这个位置的文本会被替换掉,JavaScript程序会将这些占位符替换为真实数据,然后将结果注入DOM。

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

将模板文件传入JavaScript是这个过程的重要一环。本质上讲,你一点也不希望JavaScript中嵌入模板文本,而是将模板放置于他处。通常我们将模板定义在其他标签之间,直接存放于HTML页面里,这样可以被JavaScript读取,用两种方法即可做到:一种是在HTML注释中包含模板文本。注释是和元素及文本一样的DOM节点,因此可以通过JavaScript将其提取出来。

<ul id="myList">
    <!--<li id="item%s"><a href="%s">%s</a></li>-->
    <li id="item1"><a href="item1">First Item</a></li>
    <li id="item2"><a href="item2">Second item</a></li>
</ul>

提取步骤如下:

var obj = document.getElementById('myList');
var templateText = Obj.firstChild.nodeValue;

另一种方法是使用一个带有自定义type属性的<script>元素,浏览器会默认将<script>元素中的内容识别为JavaScript代码,但你可以通过给type赋值为浏览器不识别的类型,来告诉浏览器这不是一段JavaScript脚本,比如:

<script type="test/x-my-template" id="list-item">
    <li id="item%s"><a href="%s">%s</a></li>
</script>

你可以通过<script>标签的text属性来提取文本

var obj = document.getElementById('list-item');
var templateText = Obj.text

最后在将所提取出来的模板文本通过innerHtml方法注入到HTML文件中。

方法3. 复杂客户端模板

可以考虑使用健壮的模板类库,Handlebars是专为浏览器JavaScript设计的完整的客户端模板系统,有技术文档可查阅,此处不再赘述!

handlebars.js : http://handlebarsjs.com/expressions.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,722评论 1 92
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,281评论 25 707
  • 前端开发面试题 <a name='preface'>前言</a> 只看问题点这里 看全部问题和答案点这里 本文由我...
    自you是敏感词阅读 747评论 0 3
  • 云计算时代对开源产品的选择很容易陷入一个误区,常常用商业化方式进行判断,选择最“热门畅销”的产品,而忽视了自身需求...
    余何_众神的大师兄阅读 400评论 1 1
  • 学习的最好境界是管理自己 学习的最高境界是善待他人
    morningfz阅读 84评论 0 0