- A+
UI层松耦合
当你能够做到修改一个组件而不需要去更改其他组件时,你就做到了松耦合。对于多人大型系统来说,很多人参与维护代码,松耦合对于代码可维护性来说至关重要。你绝对希望开发人员在修改某部分代码时不会破环其他人的代码。
当一个大系统的每个组件的内容有了限制,就做到了松耦合。本质上讲,每个组件需要保持足够廋身来确保松耦合。组件知道的越少,就越有利于形成整个系统。
有一点需要注意:在一起工作的组件无法达到“无偶合”。在所有系统中,组件之间总要共享一些信息来完成各自的工作,这很好理解,我们的目标是确保对一个组件的修改不会经常性地影响其他部分。
如果一个Web UI是松耦合的,则很容易测试。和文本或者结构相关问题,通过查找HTML即可定位。当发生了样式相关的问题你就知道出现在CSS中。最后,对于那些行为相关的问题,你直接去JavaScript中找问题所在,这种能力是Web界面的可维护性的核心部分
将JavaScript从CSS中抽离
这个内容比较老旧了,但是还是提一下,IE8和更早的浏览器中有一个让人爱少恨多的 功能,即CSS表达式。CSS表达式允许你将JavaScript直接插入到CSS中,这样可以在CSS代码中直接执行运算或其他操作,不过现在浏览器淘汰了这个功能,作者在这个位置踩过很深的坑,有兴趣可以了解一下哦!!
将CSS从JavaScript中抽离
有时候,保持CSS和JavaScript之间清晰的分离是很有挑战的,这两门语言相互协作得很不错,所以我们常常将样式数据和JavaScript混在一起写。通过脚本修改样式最流行的一种方法是,直接修改DOM元素的style属性。style属性是一个对象,包含了可以读取和修改的CSS属性。比如,你可以像下面这样修改元素里的文本颜色。
// 不好的写法 element.style.color = 'red';
我们经常看到使用这种方法修改多个样式属性的代码端,比如:
// 不好的写法 element.style.color = 'red'; element.style.left = '10px'; element.style.top = '100px'; element.style.visibility = 'visible';
这种方法是有问题的,因为样式信息是通过JavaScript而非CSS来承载的。当出现样式问题的时候,通常是先去排除CSS问题,知道最后才会来排除JavaScript的问题。
开发者修改style对象还有一种方式,给cssText属性赋值整个CSS字符串,看下 面这个例子。
// 不好的写法 element.style.cssText = "color:red; left:10px; top:100px; visibility:visible";
使用cssText属性只是一次性设置多个值的一中快捷写法,这种模式还是有上一个问题存在
推荐
这里推荐是使用修改元素的className属性
.reveal{ color: red; left: 10px; top: 100px; visibility: visible; }
然后使用JavaScript添加至元素上
// 原生方法 element.className += " reveal"; // HTML5 element.classList.add("reveal");
有一种需要使用JavaScript计算位置,然后重新定位的功能,这个时候CSS不具备计算的能力,这个时候可以在CSS中写好了大部分代码,并设置好这个计算属性的默认值,然后是用JavaScript改变这个属性即可
将JavaScript从HTML中抽离
不要再HTML元素上绑定事件,这样在代码出现问题的时候,我们就可以去JavaScript文件中找对应的问题。这一点看起来无关紧要。但是“可预见性”会带来更快的调试和开发,并确定(并非猜测)从何入手调试bug,这会让问题解决得更快、代码总体质量更高。
将HTML从JavaScript中抽离
从服务器加载
第一种方法就是将模板放在远程服务器,使用XMLHttpRequest对象来获取外部标签。相对比多页应用,这种方法对单页应用带来了更多的便携。这种方法(从服务器中获取模板)很容易造成XSS漏洞,需要服务器对模板文件做适当的转义处理,比如<
和>
以及双引号等,当然前端也应当给出与之匹配的渲染规则,总之这种方法需要一揽子前后端的转码和解码策略来尽可能的封堵XSS漏洞
当你需要注入大段的HTML标签到页面中时,使用远程调用的方式加载标签是非常有帮助的。出于性能的原因,将大量没用的标签存放在内存或者DOM中是很糟糕的做法。对于少量的标签段,你可以考虑采用客户端模板。
简单客户端模板
客户端模板是一些带“插槽”的标签片段,这些“插槽"会被JavaScript程序替换为数据以保证模板的完整可用。比如,一段用来添加数据项的模板看起来像下面这样。
<li><a href="%s">%s</a></li>
这段模板中包含的%s
占位符,这个位置的文本会被程序替换掉(这个格式和C语言中的sprintf()一模一样)。JavaScript程序会将这些占位符替换为真实数据,然后将结果注入DOM。
function sprintf(text) { var i = 1, args = arguments; return text.replace(/%/g, function() { return (i < args.length) ? args[i++] : ""; }) } // 用法 var result = sprintf(templateText, "/item/4", "Fourth item");
将模板文本传入JavaScript是这个过程中的重要一环。本质上讲,你一点也不希望在JavaScript中嵌入模板文本,而是将这个模板放置于他处。通常我们将模板定义在其他标签之间,直接存放在HTML中,这样可以被JavaScript读取,用两种方法都可以做到
-
在HTML注释中包含模板文本(注释是和其他元素及文本一样的DOM节点,因此可以通过JavaScript将其提取出来)
<ul id="myList"> <!--<li id="item%s"><a href="%s">%s</a></li>--> <li><a href="/item/1">Frist item</a></li> <li><a href="/item/2">Second item</a></li> <li><a href="/item/3">Third item</a></li> </ul>
这段文档中,注释作为列表的第一个子节点,被恰当的放置于上下文中,下面的这段JavaScript代码则可以将模板文本从注释中提取出来
var myList = document.getElementById("myList"), templateText = myList.firstChild.nodeValue;
紧接着就将其格式化后插入DOM中。通过下面这个函数可以完成这些操作
function addItem() { var myList = document.getELementById("myList"), templateText = myList.fristChild.nodeValue, result = sprintf(template, url, text); div.innerHtml = result; myList.insertAdjacentHTML("beforeend", result); } // 用法 addItem("/item/4", "Fourth item");
我们给这个方法传入一些数据信息,用他们来处理模板文本,并用
insertAdjacentHTML()
将结果注入HTML。这一步操作将HTML字符串转换为一个DOM节点,并将它作为子节点插入到<ul>
里 -
使用自定义
type
属性的<sript>
元素。浏览器会默认地将<sript>
元素中的内容识别为JavaScript代码,但是你可以通过给type
赋值为浏览器不识别的类型,来告诉浏览器这不是一段JavaScript脚本<script type="text/x-my-template" id = "list-item"> <li> <a href="%s">%s</a> </li> </script>
你可以使用
<sript>
便签的text属性来提取模板文本。var script = document.getElementById("list-item"), templateText = script.text;
这样addItem()函数就会变成这样
function addItem() { 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");
这里去掉了模板文中的前导空格。之所以会出现这个多余的前导空格,是因为模板文本总是在
<script>
起始标签的下一行。如果将模板原样注入,则会在<div>
中创建一个文本节点,这个文本节点的内容就是个空格,而最后加入ul里的不是li,而是空格
复杂客户端模板
上几节中介绍的模板格式非常的简单,并无太多转义。如果你想用一些更健壮的模板,则可以考虑诸如【Handlebars】
所提出的解决方案。Handlebars是专为浏览器端JavaScript设计的完整的客户端模板系统。
Handlebars中的占位符为{{}}
,我们将上面的模板转化为Handlebars版本:
<li><a href="{{url}}">{{ text }}</a></li>
在Handlebars模板中,占位符都标记为一个名称,以便可以在JavaScript中设置其映射。Handlebars建议将模板嵌入HTML页面中,并使用type属性为“text/x-handlebars-template”的script标签来表示。
<script type="text/x-my-template" id = "list-item"> <li> <a href="{{url}}">{{text}}</a> </li> </script>
要想使用这个模板,你首先必须将Handlebars类库引入到页面,这个类库会创建一个名为Handlebars的全局变量,用来将模板文本编译为一个函数。
var script = document.getElementById("list-item"), templateText = script.text, template = Handlebars.compile(script.text);
这时候,template包含一个函数,当执行这个函数的时候,返回一个格式化好的字符串。你只需要将name和url属性的对象传入
var result = template({ name:"Fourth item", url:"/item/4" })
参数会自动做HTML转义,转义操作也是格式化的一部分。转义是为了增强模板的安全性,并确保简单的文本值不会破环你的标签结构。比如,字符串&
会自动转义为&
。
现在将他们合并为一个单独的函数中。
function addItem() { 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({ name:"Fourth item", url:"/item/4" }) div.innerHtml = result; myList.appendChild(div.firstChild); } // 用法 addItem("/item/4", "Fourth item");
这个简单的例子并未真正体现Handlebars的灵活性。除了简单的占位符替换之外,Handlebars模板同样支持一些简单的逻辑和循环。
{{#if items}} <ul> {{#each items}} <li><a href="{{url}}">{{text}}</a></li> {{/each}} </ul> {{/if}}
if,作为判断,如果items里有数据才开始渲染,each循环
// 返回一个空字符串 var result = template({ items: [] }); // 返回包含两个记录的HTML列表 var result = template({ item:[ { text:"First item", url:"/item/1" }, { text:"Second item", url:"/item/2" } ] })