本篇文章参考书籍《JavaScript设计模式》–张容铭
前言
本章节会比较简短,介绍一下解释器模式,这是行为型设计模式里面的最后一个了,敲黑板,好好听课的同学还记我们总过学习过那些种类的设计模式不?
给大家复习一下哈。
首先是 创建型设计模式 ,代表有工厂相关的方法,建造者,原型,单例等;
然后是 结构型设计模式 ,代表的有,装饰者,适配器,外观等;
接着是 行为型设计模式 ,代表有代表有观察者,命令,迭代器等;
解释器模式
对于一种语言,给出其文法表示形式,并定义一种解释器,通过使用这种解释器来解释语言中定义的句子 。
举个例子,当我们点击页面种一个 button 的时候,想知道这个元素的 Xpath (元素在页面中所处的路径),这个与事件冒泡类似,只不过在这个路径中还要关心一下同一层级中,当前元素前面的兄弟元素。比如下面的结构:
<div class="wrap">
<div class="link-inner">
<a href='#'>link</a>
</div>
<div class="button-inner">
<button>text</button>
</div>
</div >
要获取 button 下那个对于 calss 为 wrap 的 div 元素的 Xpath 路径,那么可以表示成 DIV>DIV2>BUTTON 然后记录以作统计,再比如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button>text</button>
</body>
</html>
获取 button 相对于整个页面文档的 Xpath 路径为 HTML>BODY|HEAD>BUTTON
大家看到这里,可能会有点蒙,我们上面定义了一种文法,什么是文法呢? 类似普通话里的“什么什么吗?”,“什么什么啊!”,文法是用来定义一组语言规则的,上面我们想获取“元素在页面中所处的路径”,就是一组规则,我们需要做的就是书写一个这种规则的解释器。
那么针对这一条规则,我们应该怎么来写解释器呢?
首先观察规则,找到相似点,1.右边第一个元素都是目标元素,2.左边第一个元素是容器元素,3.很像事件流里的冒泡阶段,4.注意兄弟元素之间如果有相同的,要增加数字区分,没有相同的,就增加新类型。
//同级兄弟元素遍历
function getSublingName(node) {
//如果存在兄弟元素
if(node.previousSibling) {
var name = '', //返回兄弟元素名称字符串
count = 1, //紧邻兄弟元素中相同名称元素个数
nodeName = node.nodeName, //原始节点名称
sibling = node.previousSibling; //前一个兄弟元素
//如果存在前一个兄弟元素
while(sibling) {
//如果节点为元素,并且节点与前一个兄弟元素类型相同,并且前一个兄弟元素名称存在
if(sibling.nodeType == 1 && sibling.nodeType == node.nodeType && sibling.nodeName) {
//如果节点名称和前一个兄弟元素名称相同
if(nodeName == sibling.nodeName) {
//节点名称后面添加计数
name += ++count;
} else {
//重置相同紧邻节点名称节点个数
count = 1;
//追加新的节点名称
name += '|' + sibling.nodeName.toUpperCase();
}
}
//向前获取前一个兄弟元素
sibling = sibling.previousSibling;
}
return name;
} else {
//否则不存在兄弟元素,返回
return '';
}
}
有了这个方法,我们在实现冒泡遍历整个文档树的时候,处理每一层的元素节点就方便多了,下面我们实现一下:
//Xpath 解释器
var Interpreter = (function() {
//获取兄弟元素名称
function getSublingName(node) {
//...
}
//参数1 node: 目标节点 参数2 wrap: 容器节点
return function(node, wrap) {
//路径数组
var path = [].
//如果不存在容器节点,默认为 document
wrap = wrap || document;
//如果当前(目标)节点等于容器节点
if(node === wrap) {
//容器节点为元素
if(wrap.nodeType ==1) {
//路径数组中输入容器节点名称
path.push(wrap.nodeName.toUpperCase());
}
//返回最终路径数组结果
return path;
}
//如果当前节点的父节点不等于容器节点
if(node.parentNode !== wrap) {
//对当前节点的父节点执行遍历操作
path = arguments.callee(node.parentNode, wrap);
}
//如果当前节点的父元素节点与容器节点相等
else {
//容器节点为元素
if(wrap,nodeType == 1) {
//路径数组中输入容器节点名称
path.push(wrap.nodeName.toUpperCase());
}
}
//获取元素的兄弟元素名称统计
var sublingsNames = getSublingName(node);
//如果节点为元素
if(node.nodeType == 1) {
//输入当前元素节点名称及其前面兄弟元素统计
path.push(node.nodeName.toUpperCase() + sublingsNames);
}
//返回最终路径数组结果
return path;
}
}) ()
有了这个解释器之后,不管什么样的页面,都可以直接获取到他的 Xpath 路径了。
本节就到此结束,下一节我们学习一个新的大类,技巧型设计模式。
(最近打算开一个新的系列,《跟我一起读–微信小程序》,筹备的差不多了,本系列可能会更新慢一点,各位有问题可以直接评论回复,或者私信给我哈)