博主信息
Sky
博文
291
粉丝
0
评论
0
访问量
7050
积分:0
P豆:617

第五章 UI层松耦合

2021年10月06日 19:11:39阅读数:38博客 / Sky

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";// HTML5element.classList.add("reveal");

有一种需要使用JavaScript计算位置,然后重新定位的功能,这个时候CSS不具备计算的能力,这个时候可以在CSS中写好了大部分代码,并设置好这个计算属性的默认值,然后是用JavaScript改变这个属性即可

将JavaScript从HTML中抽离

不要再HTML元素上绑定事件,这样在代码出现问题的时候,我们就可以去JavaScript文件中找对应的问题。这一点看起来无关紧要。但是“可预见性”会带来更快的调试和开发,并确定(并非猜测)从何入手调试bug,这会让问题解决得更快、代码总体质量更高。

将HTML从JavaScript中抽离

从服务器加载

第一种方法就是将模板放在远程服务器,使用XMLHttpRequest对象来获取外部标签。相对比多页应用,这种方法对单页应用带来了更多的便携。这种方法(从服务器中获取模板)很容易造成XSS漏洞,需要服务器对模板文件做适当的转义处理,比如<和>以及双引号等,当然前端也应当给出与之匹配的渲染规则,总之这种方法需要一揽子前后端的转码和解码策略来尽可能的封堵XSS漏洞

当你需要注入大段的HTML标签到页面中时,使用远程调用的方式加载标签是非常有帮助的。出于性能的原因,将大量没用的标签存放在内存或者DOM中是很糟糕的做法。对于少量的标签段,你可以考虑采用客户端模板。

简单客户端模板

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

<li><a href="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/%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="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/%s">%s</a></li>--><li><a href="https://www.cnblogs.com/item/1">Frist item</a></li><li><a href="https://www.cnblogs.com/item/2">Second item</a></li><li><a href="https://www.cnblogs.com/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="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/%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");

这里去掉了模板文中的前导空格。之所以会出现这个多余的前导空格,买QQ号平台地图是因为模板文本总是在<script>起始标签的下一行。如果将模板原样注入,则会在<div>中创建一个文本节点,这个文本节点的内容就是个空格,而最后加入ul里的不是li,而是空格

复杂客户端模板

上几节中介绍的模板格式非常的简单,并无太多转义。如果你想用一些更健壮的模板,则可以考虑诸如【Handlebars】

所提出的解决方案。Handlebars是专为浏览器端JavaScript设计的完整的客户端模板系统。

Handlebars中的占位符为{{}},我们将上面的模板转化为Handlebars版本:

<li><a href="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/{{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="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/{{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转义,转义操作也是格式化的一部分。转义是为了增强模板的安全性,并确保简单的文本值不会破环你的标签结构。比如,字符串&会自动转义为&amp;。

现在将他们合并为一个单独的函数中。

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="https://www.cnblogs.com/wangzhaoyv/archive/2020/12/26/{{url}}">{{text}}</a></li>{{/each}}</ul>{{/if}}

if,作为判断,如果items里有数据才开始渲染,each循环

// 返回一个空字符串var result = template({
items: []
});// 返回包含两个记录的HTML列表var result = template({
item:[
{
text:"First item",
url:"https://www.cnblogs.com/item/1"},
{
text:"Second item",
url:"https://www.cnblogs.com/item/2"}
]
})


版权申明:本博文版权归博主所有,转载请注明地址!如有侵权、违法,请联系admin@php.cn举报处理!

全部评论

文明上网理性发言,请遵守新闻评论服务协议

条评论