javascript設(shè)計模式之解釋器模式詳解
神馬是“解釋器模式”?
先翻開《GOF》看看Definition:
給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
在開篇之前還是要科普幾個概念:
抽象語法樹:
解釋器模式并未解釋如何創(chuàng)建一個抽象語法樹。它不涉及語法分析。抽象語法樹可用一個表驅(qū)動的語法分析程序來完成,也可用手寫的(通常為遞歸下降法)語法分析程序創(chuàng)建,或直接client提供。
解析器:
指的是把描述客戶端調(diào)用要求的表達(dá)式,經(jīng)過解析,形成一個抽象語法樹的程序。
解釋器:
指的是解釋抽象語法樹,并執(zhí)行每個節(jié)點(diǎn)對應(yīng)的功能的程序。
要使用解釋器模式,一個重要的前提就是要定義一套語法規(guī)則,也稱為文法。不管這套文法的規(guī)則是簡單還是復(fù)雜,必須要有這些規(guī)則,因?yàn)榻忉屍髂J骄褪前凑者@些規(guī)則來進(jìn)行解析并執(zhí)行相應(yīng)的功能的。
先來看看解釋器模式的結(jié)構(gòu)圖和說明:
AbstractExpression:定義解釋器的接口,約定解釋器的解釋操作。
TerminalExpression:終結(jié)符解釋器,用來實(shí)現(xiàn)語法規(guī)則中和終結(jié)符相關(guān)的操作,不再包含其他的解釋器,如果用組合模式來構(gòu)建抽象語法樹的話,就相當(dāng)于組合模式中的葉子對象,可以有多種終結(jié)符解釋器。
NonterminalExpression:非終結(jié)符解釋器,用來實(shí)現(xiàn)語法規(guī)則中非終結(jié)符相關(guān)的操作,通常一個解釋器對應(yīng)一個語法規(guī)則,可以包含其他的解釋器,如果用組合模式來構(gòu)建抽象語法樹的話,就相當(dāng)于組合模式中的組合對象??梢杂卸喾N非終結(jié)符解釋器。
Context:上下文,通常包含各個解釋器需要的數(shù)據(jù)或是公共的功能。
Client:客戶端,指的是使用解釋器的客戶端,通常在這里將按照語言的語法做的表達(dá)式轉(zhuǎn)換成為使用解釋器對象描述的抽象語法樹,然后調(diào)用解釋操作。
下面我們通過一個xml示例來理解解釋器模式:
首先要為表達(dá)式設(shè)計簡單的文法,為了通用,用root表示根元素,abc等來代表元素,一個簡單的xml如下:
<?xml version="1.0" encoding="UTF-8">
<root id="rootId">
<a>
<b>
<c name="testC">12345</c>
<d id="1">d1</d>
<d id="2">d2</d>
<d id="3">d3</d>
<d id="4">d4</d>
</b>
</a>
</root>
約定表達(dá)式的文法如下:
1.獲取單個元素的值:從根元素開始,一直到想要獲取取值的元素,元素中間用“/”分隔,根元素前不加“/”。比如,表達(dá)式“root/a/b/c”就表示獲取根元素下,a元素下,b元素下,c元素的值。
2.獲取單個元素的屬性的值:當(dāng)然是多個,要獲取值的屬性一定是表達(dá)式的最后一個元素的屬性,在最后一個元素后面添加“.”然后再加上屬性的名稱。比如,表達(dá)式“root/a/b/c.name”就表示獲取根元素下,a元素下,b元素下,c元素的name屬性的值。
3.獲取相同元素名稱的值,當(dāng)然是多個,要獲取值的元素一定是表達(dá)式的最后一個元素,在最后一個元素后面添加“$”。比如,表達(dá)式“root/a/b/d$”就表示獲取根元素下,a元素下,b元素下的多個d元素的值的集合。
4.獲取相同元素名稱的屬性的值,當(dāng)然也是多個:要獲取屬性值的元素一定是表達(dá)式的最后一個元素,在最后一個元素后面添加"$"。比如,表達(dá)式“root/a/b/d$.id$”就表示獲取根元素下,a元素下,b元素下的多個d元素的id屬性的值的集合。
上面的xml,對應(yīng)的抽象語法樹,可能的結(jié)構(gòu)如圖:
下面我們來看看具體的代碼:
1.定義上下文:
/**
* 上下文,用來包含解釋器需要的一些全局信息
* @param {String} filePathName [需要讀取的xml的路徑和名字]
*/
function Context(filePathName) {
// 上一個被處理元素
this.preEle = null;
// xml的Document對象
this.document = XmlUtil.getRoot(filePathName);
}
Context.prototype = {
// 重新初始化上下文
reInit: function () {
this.preEle = null;
},
/**
* 各個Expression公共使用的方法
* 根據(jù)父元素和當(dāng)前元素的名稱來獲取當(dāng)前元素
* @param {Element} pEle [父元素]
* @param {String} eleName [當(dāng)前元素名稱]
* @return {Element|null} [找到的當(dāng)前元素]
*/
getNowEle: function (pEle, eleName) {
var tempNodeList = pEle.childNodes;
var nowEle;
for (var i = 0, len = tempNodeList.length; i < len; i++) {
if ((nowEle = tempNodeList[i]).nodeType === 1)
if (nowEle.nodeName === eleName)
return nowEle;
}
return null;
},
getPreEle: function () {
return this.preEle;
},
setPreEle: function (preEle) {
this.preEle = preEle;
},
getDocument: function () {
return this.document;
}
};
在上下文中使用了一個工具對象XmlUtil來獲取xmlDom,下面我使用的是DOM3的DOMPaser,某些瀏覽器可能不支持:
// 工具對象
// 解析xml,獲取相應(yīng)的Document對象
var XmlUtil = {
getRoot: function (filePathName) {
var parser = new DOMParser();
var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
return xmldom;
}
};
下面就是解釋器的代碼:
/**
* 元素作為非終結(jié)符對應(yīng)的解釋器,解釋并執(zhí)行中間元素
* @param {String} eleName [元素的名稱]
*/
function ElementExpression(eleName) {
this.eles = [];
this.eleName = eleName;
}
ElementExpression.prototype = {
addEle: function (eleName) {
this.eles.push(eleName);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i])
this.eles.splice(i--, 1);
}
return true;
},
interpret: function (context) {
// 先取出上下文中的當(dāng)前元素作為父級元素
// 查找到當(dāng)前元素名稱所對應(yīng)的xml元素,并設(shè)置回到上下文中
var pEle = context.getPreEle();
if (!pEle) {
// 說明現(xiàn)在獲取的是根元素
context.setPreEle(context.getDocument().documentElement);
} else {
// 根據(jù)父級元素和要查找的元素的名稱來獲取當(dāng)前的元素
var nowEle = context.getNowEle(pEle, this.eleName);
// 把當(dāng)前獲取的元素放到上下文中
context.setPreEle(nowEle);
}
var ss;
// 循環(huán)調(diào)用子元素的interpret方法
for (var i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
// 返回最后一個解釋器的解釋結(jié)果,一般最后一個解釋器就是終結(jié)符解釋器了
return ss;
}
};
/**
* 元素作為終結(jié)符對應(yīng)的解釋器
* @param {String} name [元素的名稱]
*/
function ElementTerminalExpression(name) {
this.eleName = name;
}
ElementTerminalExpression.prototype = {
interpret: function (context) {
var pEle = context.getPreEle();
var ele = null;
if (!pEle) {
ele = context.getDocument().documentElement;
} else {
ele = context.getNowEle(pEle, this.eleName);
context.setPreEle(ele);
}
// 獲取元素的值
return ele.firstChild.nodeValue;
}
};
/**
* 屬性作為終結(jié)符對應(yīng)的解釋器
* @param {String} propName [屬性的名稱]
*/
function PropertyTerminalExpression(propName) {
this.propName = propName;
}
PropertyTerminalExpression.prototype = {
interpret: function (context) {
// 直接獲取最后的元素屬性的值
return context.getPreEle().getAttribute(this.propName);
}
};
先來看看如何使用解釋器獲取單個元素的值:
void function () {
var c = new Context();
// 想要獲取多個d元素的值,也就是如下表達(dá)式的值:“root/a/b/c”
// 首先要構(gòu)建解釋器的抽象語法樹
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var cEle = new ElementTerminalExpression('c');
// 組合
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(cEle);
console.log('c的值是 = ' + root.interpret(c));
}();
輸出: c的值是 = 12345
然后我們再用上面代碼獲取單個元素的屬性的值:
void function () {
var c = new Context();
// 想要獲取d元素的id屬性,也就是如下表達(dá)式的值:“a/b/c.name”
// 這個時候c不是終結(jié)了,需要把c修改成ElementExpression
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var cEle = new ElementExpression('c');
var prop = new PropertyTerminalExpression('name');
// 組合
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(cEle);
cEle.addEle(prop);
console.log('c的屬性name值是 = ' + root.interpret(c));
// 如果要使用同一個上下文,連續(xù)進(jìn)行解析,需要重新初始化上下文對象
// 比如,要連續(xù)的重新再獲取一次屬性name的值,當(dāng)然你可以重新組合元素
// 重新解析,只要是在使用同一個上下文,就需要重新初始化上下文對象
c.reInit();
console.log('重新獲取c的屬性name值是 = ' + root.interpret(c));
}();
輸出: c的屬性name值是 = testC 重新獲取c的屬性name值是 = testC
講解:
1.解釋器模式功能:
解釋器模式使用解釋器對象來表示和處理相應(yīng)的語法規(guī)則,一般一個解釋器處理一條語法規(guī)則。理論上來說,只要能用解釋器對象把符合語法的表達(dá)式表示出來,而且能夠構(gòu)成抽象的語法樹,就可以使用解釋器模式來處理。
2.語法規(guī)則和解釋器
語法規(guī)則和解釋器之間是有對應(yīng)關(guān)系的,一般一個解釋器處理一條語法規(guī)則,但是反過來并不成立,一條語法規(guī)則是可以有多種解釋和處理的,也就是一條語法規(guī)則可以對應(yīng)多個解釋器。
3.上下文的公用性
上下文在解釋器模式中起著非常重要的作用。由于上下文會被傳遞到所有的解釋器中。因此可以在上下文中存儲和訪問解釋器的狀態(tài),比如,前面的解釋器可以存儲一些數(shù)據(jù)在上下文中,后面的解釋器就可以獲取這些值。
另外還可以通過上下文傳遞一些在解釋器外部,但是解釋器需要的數(shù)據(jù),也可以是一些全局的,公共的數(shù)據(jù)。
上下文還有一個功能,就是可以提供所有解釋器對象的公共功能,類似于對象組合,而不是使用繼承來獲取公共功能,在每個解釋器對象中都可以調(diào)用
4.誰來構(gòu)建抽象語法樹
在前面的示例中,是自己在客戶端手工構(gòu)建抽象語法樹,是很麻煩的,但是在解釋器模式中,并沒有涉及這部分功能,只是負(fù)責(zé)對構(gòu)建好的抽象語法樹進(jìn)行解釋處理。后面會介紹可以提供解析器來實(shí)現(xiàn)把表達(dá)式轉(zhuǎn)換成為抽象語法樹。
還有一個問題,就是一條語法規(guī)則是可以對應(yīng)多個解釋器對象的,也就是說同一個元素,是可以轉(zhuǎn)換成多個解釋器對象的,這也就意味著同樣一個表達(dá)式,是可以構(gòu)成不用的抽象語法樹的,這也造成構(gòu)建抽象語法樹變得很困難,而且工作量非常大。
5.誰負(fù)責(zé)解釋操作
只要定義好了抽象語法樹,肯定是解釋器來負(fù)責(zé)解釋執(zhí)行。雖然有不同的語法規(guī)則,但是解釋器不負(fù)責(zé)選擇究竟用哪個解釋器對象來解釋執(zhí)行語法規(guī)則,選擇解釋器的功能在構(gòu)建抽象語法樹的時候就完成了。
6.解釋器模式的調(diào)用順序
1)創(chuàng)建上下文對象
2)創(chuàng)建多個解釋器對象,組合抽象語法樹
3)調(diào)用解釋器對象的解釋操作
3.1)通過上下文來存儲和訪問解釋器的狀態(tài)。
對于非終結(jié)符解釋器對象,遞歸調(diào)用它所包含的子解釋器對象。
解釋器模式的本質(zhì):*分離實(shí)現(xiàn),解釋執(zhí)行*
解釋器模使用一個解釋器對象處理一個語法規(guī)則的方式,把復(fù)雜的功能分離開;然后選擇需要被執(zhí)行的功能,并把這些功能組合成為需要被解釋執(zhí)行的抽象語法樹;再按照抽象語法樹來解釋執(zhí)行,實(shí)現(xiàn)相應(yīng)的功能。
從表面上看,解釋器模式關(guān)注的是我們平時不太用到的自定義語法的處理;但從實(shí)質(zhì)上看,解釋器模式的思想然后是分離,封裝,簡化,和很多模式是一樣的。
比如,可以使用解釋器模式模擬狀態(tài)模式的功能。如果把解釋器模式要處理的語法簡化到只有一個狀態(tài)標(biāo)記,把解釋器看成是對狀態(tài)的處理對象,對同一個表示狀態(tài)的語法,可以有很多不用的解釋器,也就是有很多不同的處理狀態(tài)的對象,然后再創(chuàng)建抽象語法樹的時候,簡化成根據(jù)狀態(tài)的標(biāo)記來創(chuàng)建相應(yīng)的解釋器,不用再構(gòu)建樹了。
同理,解釋器模式可以模擬實(shí)現(xiàn)策略模式的功能,裝飾器模式的功能等,尤其是模擬裝飾器模式的功能,構(gòu)建抽象語法樹的過程,自然就對應(yīng)成為組合裝飾器的過程。
解釋器模式執(zhí)行速度通常不快(大多數(shù)時候非常慢),而且錯誤調(diào)試比較困難(附注:雖然調(diào)試比較困難,但事實(shí)上它降低了錯誤的發(fā)生可能性),但它的優(yōu)勢是顯而易見的,它能有效控制模塊之間接口的復(fù)雜性,對于那種執(zhí)行頻率不高但代碼頻率足夠高,且多樣性很強(qiáng)的功能,解釋器是非常適合的模式。此外解釋器還有一個不太為人所注意的優(yōu)勢,就是它可以方便地跨語言和跨平臺。
解釋器模式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
1.易于實(shí)現(xiàn)語法
在解釋器模式中,一條語法規(guī)則用一個解釋器對象來解釋執(zhí)行。對于解釋器的實(shí)現(xiàn)來講,功能就變得比較簡單,只需要考慮這一條語法規(guī)則的實(shí)現(xiàn)就可以了,其他的都不用管。 2.易于擴(kuò)展新的語法
正是由于采用一個解釋器對象負(fù)責(zé)一條語法規(guī)則的方式,使得擴(kuò)展新的語法非常容易。擴(kuò)展了新的語法,只需要創(chuàng)建相應(yīng)的解釋器對象,在創(chuàng)建抽象語法樹的時候使用這個新的解釋器對象就可以了。
缺點(diǎn):
不適合復(fù)雜的語法
如果語法特別復(fù)雜,構(gòu)建解釋器模式需要的抽象語法樹的工作是非常艱巨的,再加上有可能會需要構(gòu)建多個抽象語法樹。所以解釋器模式不太適合復(fù)雜的語法。使用語法分析程序或編譯器生成器可能會更好一些。
何時使用?
當(dāng)有一個語言需要解釋執(zhí)行,并且可以將該語言中的句子表示為一個抽象語法樹的時候,可以考慮使用解釋器模式。
在使用解釋器模式的時候,還有兩個特點(diǎn)需要考慮,一個是語法相對應(yīng)該比較簡單,太負(fù)責(zé)的語法不適合使用解釋器模式玲玲一個是效率要求不是很高,對效率要求很高的,不適合使用。
前面介紹了如何獲取單個元素的值和單個元素屬性的值,下面來看看如何獲取多個元素的值,還有多個元素中相擁名稱的值,還有前面的測試都是人工組合好的抽象語法樹,我們也順便實(shí)現(xiàn)以下簡單的解析器,把符合前面定義的語法的表達(dá)式,轉(zhuǎn)換成為前面實(shí)現(xiàn)的解釋器的抽象語法樹: 我就直接把代碼貼出來了:
// 讀取多個元素或?qū)傩缘闹?br /> (function () {
/**
* 上下文,用來包含解釋器需要的一些全局信息
* @param {String} filePathName [需要讀取的xml的路徑和名字]
*/
function Context(filePathName) {
// 上一個被處理的多個元素
this.preEles = [];
// xml的Document對象
this.document = XmlUtil.getRoot(filePathName);
}
Context.prototype = {
// 重新初始化上下文
reInit: function () {
this.preEles = [];
},
/**
* 各個Expression公共使用的方法
* 根據(jù)父元素和當(dāng)前元素的名稱來獲取當(dāng)前元素
* @param {Element} pEle [父元素]
* @param {String} eleName [當(dāng)前元素名稱]
* @return {Element|null} [找到的當(dāng)前元素]
*/
getNowEles: function (pEle, eleName) {
var elements = [];
var tempNodeList = pEle.childNodes;
var nowEle;
for (var i = 0, len = tempNodeList.length; i < len; i++) {
if ((nowEle = tempNodeList[i]).nodeType === 1) {
if (nowEle.nodeName === eleName) {
elements.push(nowEle);
}
}
}
return elements;
},
getPreEles: function () {
return this.preEles;
},
setPreEles: function (nowEles) {
this.preEles = nowEles;
},
getDocument: function () {
return this.document;
}
};
// 工具對象
// 解析xml,獲取相應(yīng)的Document對象
var XmlUtil = {
getRoot: function (filePathName) {
var parser = new DOMParser();
var xmldom = parser.parseFromString('<root id="rootId"><a><b><c name="testC">12345</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a></root>', 'text/xml');
return xmldom;
}
};
/**
* 元素作為非終結(jié)符對應(yīng)的解釋器,解釋并執(zhí)行中間元素
* @param {String} eleName [元素的名稱]
*/
function ElementExpression(eleName) {
this.eles = [];
this.eleName = eleName;
}
ElementExpression.prototype = {
addEle: function (eleName) {
this.eles.push(eleName);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i]) {
this.eles.splice(i--, 1);
}
}
return true;
},
interpret: function (context) {
// 先取出上下文中的當(dāng)前元素作為父級元素
// 查找到當(dāng)前元素名稱所對應(yīng)的xml元素,并設(shè)置回到上下文中
var pEles = context.getPreEles();
var ele = null;
var nowEles = [];
if (!pEles.length) {
// 說明現(xiàn)在獲取的是根元素
ele = context.getDocument().documentElement;
pEles.push(ele);
context.setPreEles(pEles);
} else {
var tempEle;
for (var i = 0, len = pEles.length; i < len; i++) {
tempEle = pEles[i];
nowEles = nowEles.concat(context.getNowEles(tempEle, this.eleName));
// 找到一個就停止
if (nowEles.length) break;
}
context.setPreEles([nowEles[0]]);
}
var ss;
// 循環(huán)調(diào)用子元素的interpret方法
for (var i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
return ss;
}
};
/**
* 元素作為終結(jié)符對應(yīng)的解釋器
* @param {String} name [元素的名稱]
*/
function ElementTerminalExpression(name) {
this.eleName = name;
}
ElementTerminalExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var ele = null;
if (!pEles.length) {
ele = context.getDocument().documentElement;
} else {
ele = context.getNowEles(pEles[0], this.eleName)[0];
}
// 獲取元素的值
return ele.firstChild.nodeValue;
}
};
/**
* 屬性作為終結(jié)符對應(yīng)的解釋器
* @param {String} propName [屬性的名稱]
*/
function PropertyTerminalExpression(propName) {
this.propName = propName;
}
PropertyTerminalExpression.prototype = {
interpret: function (context) {
// 直接獲取最后的元素屬性的值
return context.getPreEles()[0].getAttribute(this.propName);
}
};
/**
* 多個屬性作為終結(jié)符對應(yīng)的解釋器
* @param {String} propName [屬性的名稱]
*/
function PropertysTerminalExpression(propName) {
this.propName = propName;
}
PropertysTerminalExpression.prototype = {
interpret: function (context) {
var eles = context.getPreEles();
var ss = [];
for (var i = 0, len = eles.length; i < len; i++) {
ss.push(eles[i].getAttribute(this.propName));
}
return ss;
}
};
/**
* 以多個元素作為終結(jié)符的解釋處理對象
* @param {[type]} name [description]
*/
function ElementsTerminalExpression(name) {
this.eleName = name;
}
ElementsTerminalExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var nowEles = [];
for (var i = 0, len = pEles.length; i < len; i++) {
nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
}
var ss = [];
for (i = 0, len = nowEles.length; i < len; i++) {
ss.push(nowEles[i].firstChild.nodeValue);
}
return ss;
}
};
/**
* 多個元素作為非終結(jié)符的解釋處理對象
*/
function ElementsExpression(name) {
this.eleName = name;
this.eles = [];
}
ElementsExpression.prototype = {
interpret: function (context) {
var pEles = context.getPreEles();
var nowEles = [];
for (var i = 0, len = pEles.length; i < len; i++) {
nowEles = nowEles.concat(context.getNowEles(pEles[i], this.eleName));
}
context.setPreEles(nowEles);
var ss;
for (i = 0, len = this.eles.length; i < len; i++) {
ss = this.eles[i].interpret(context);
}
return ss;
},
addEle: function (ele) {
this.eles.push(ele);
return true;
},
removeEle: function (ele) {
for (var i = 0, len = this.eles.length; i < len; i++) {
if (ele === this.eles[i]) {
this.eles.splice(i--, 1);
}
}
return true;
}
};
void function () {
// "root/a/b/d$"
var c = new Context('Interpreter.xml');
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var dEle = new ElementsTerminalExpression('d');
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(dEle);
var ss = root.interpret(c);
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d的值是 = ' + ss[i]);
}
}();
void function () {
// a/b/d$.id$
var c = new Context('Interpreter.xml');
var root = new ElementExpression('root');
var aEle = new ElementExpression('a');
var bEle = new ElementExpression('b');
var dEle = new ElementsExpression('d');
var prop = new PropertysTerminalExpression('id');
root.addEle(aEle);
aEle.addEle(bEle);
bEle.addEle(dEle);
dEle.addEle(prop);
var ss = root.interpret(c);
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d的屬性id的值是 = ' + ss[i]);
}
}();
// 解析器
/**
* 解析器的實(shí)現(xiàn)思路
* 1.把客戶端傳遞來的表達(dá)式進(jìn)行分解,分解成為一個一個的元素,并用一個對應(yīng)的解析模型來封裝這個元素的一些信息。
* 2.根據(jù)每個元素的信息,轉(zhuǎn)化成相對應(yīng)的解析器對象。
* 3.按照先后順序,把這些解析器對象組合起來,就得到抽象語法樹了。
*
* 為什么不把1和2合并,直接分解出一個元素就轉(zhuǎn)換成相應(yīng)的解析器對象?
* 1.功能分離,不要讓一個方法的功能過于復(fù)雜。
* 2.為了今后的修改和擴(kuò)展,現(xiàn)在語法簡單,所以轉(zhuǎn)換成解析器對象需要考慮的東西少,直接轉(zhuǎn)換也不難,但要是語法復(fù)雜了,直接轉(zhuǎn)換就很雜亂了。
*/
/**
* 用來封裝每一個解析出來的元素對應(yīng)的屬性
*/
function ParserModel() {
// 是否單個值
this.singleValue;
// 是否屬性,不是屬性就是元素
this.propertyValue;
// 是否終結(jié)符
this.end;
}
ParserModel.prototype = {
isEnd: function () {
return this.end;
},
setEnd: function (end) {
this.end = end;
},
isSingleValue: function () {
return this.singleValue;
},
setSingleValue: function (oneValue) {
this.singleValue = oneValue;
},
isPropertyValue: function () {
return this.propertyValue;
},
setPropertyValue: function (propertyValue) {
this.propertyValue = propertyValue;
}
};
var Parser = function () {
var BACKLASH = '/';
var DOT = '.';
var DOLLAR = '$';
// 按照分解的先后記錄需要解析的元素的名稱
var listEle = null;
// 開始實(shí)現(xiàn)第一步-------------------------------------
/**
* 傳入一個字符串表達(dá)式,通過解析,組合成為一個抽象語法樹
* @param {String} expr [描述要取值的字符串表達(dá)式]
* @return {Object} [對應(yīng)的抽象語法樹]
*/
function parseMapPath(expr) {
// 先按照“/”分割字符串
var tokenizer = expr.split(BACKLASH);
// 用來存放分解出來的值的表
var mapPath = {};
var onePath, eleName, propName;
var dotIndex = -1;
for (var i = 0, len = tokenizer.length; i < len; i++) {
onePath = tokenizer[i];
if (tokenizer[i + 1]) {
// 還有下一個值,說明這不是最后一個元素
// 按照現(xiàn)在的語法,屬性必然在最后,因此也不是屬性
setParsePath(false, onePath, false, mapPath);
} else {
// 說明到最后了
dotIndex = onePath.indexOf(DOT);
if (dotIndex >= 0) {
// 說明是要獲取屬性的值,那就按照“.”來分割
// 前面的就是元素名稱,后面的是屬性的名字
eleName = onePath.substring(0, dotIndex);
propName = onePath.substring(dotIndex + 1);
// 設(shè)置屬性前面的那個元素,自然不是最后一個,也不是屬性
setParsePath(false, eleName, false, mapPath);
// 設(shè)置屬性,按照現(xiàn)在的語法定義,屬性只能是最后一個
setParsePath(true, propName, true, mapPath);
} else {
// 說明是取元素的值,而且是最后一個元素的值
setParsePath(true, onePath, false, mapPath);
}
break;
}
}
return mapPath;
}
/**
* 按照分解出來的位置和名稱來設(shè)置需要解析的元素名稱
* @param {Boolean} end [是否最后一個]
* @param {String} ele [元素名稱]
* @param {Boolean} propertyValue [是否取屬性]
* @param {Object} mapPath [設(shè)置需要解析的元素名稱,還有該元素對應(yīng)的解析模型的表]
*/
function setParsePath(end, ele, propertyValue, mapPath) {
var pm = new ParserModel();
pm.setEnd(end);
// 如果帶有“$”符號就說明不是一個值
pm.setSingleValue(!(ele.indexOf(DOLLAR) >= 0));
pm.setPropertyValue(propertyValue);
// 去掉"$"
ele = ele.replace(DOLLAR, '');
mapPath[ele] = pm;
listEle.push(ele);
}
// 開始實(shí)現(xiàn)第二步-------------------------------------
/**
* 把分解出來的元素名稱根據(jù)對應(yīng)的解析模型轉(zhuǎn)換成為相應(yīng)的解釋器對象
* @param {Object} mapPath [分解出來的需解析的元素名稱,還有該元素對應(yīng)的解析模型]
* @return {Array} [把每個元素轉(zhuǎn)換成為相應(yīng)的解釋器對象后的數(shù)組]
*/
function mapPath2Interpreter(mapPath) {
var list = [];
var pm, key;
var obj = null;
// 一定要按照分解的先后順序來轉(zhuǎn)換成解釋器對象
for (var i = 0, len = listEle.length; i < len; i++) {
key = listEle[i];
pm = mapPath[key];
// 不是最后一個
if (!pm.isEnd()) {
if (pm.isSingleValue())
// 是一個值,轉(zhuǎn)化
obj = new ElementExpression(key);
else
// 是多個值,轉(zhuǎn)化
obj = new ElementsExpression(key);
} else {
// 是最后一個
// 是屬性值
if (pm.isPropertyValue()) {
if (pm.isSingleValue())
obj = new PropertyTerminalExpression(key);
else
obj = new PropertysTerminalExpression(key);
// 取元素的值
} else {
if (pm.isSingleValue())
obj = new ElementTerminalExpression(key);
else
obj = new ElementsTerminalExpression(key);
}
}
list.push(obj);
}
return list;
}
// 開始實(shí)現(xiàn)第三步-------------------------------------
/**
* 構(gòu)建抽象語法樹
* @param {[type]} list [把每個元素轉(zhuǎn)換成為相應(yīng)的解釋器對象后的數(shù)組]
* @return {[type]} [description]
*/
function buildTree(list) {
// 第一個對象,也是返回去的對象,就是抽象語法樹的根
var returnReadXMLExpr = null;
// 定義上一個對象
var preReadXmlExpr = null;
var readXml, ele, eles;
for (var i = 0, len = list.length; i < len; i++) {
readXml = list[i];
// 說明是第一個元素
if (preReadXmlExpr === null) {
preReadXmlExpr = readXml;
returnReadXMLExpr = readXml;
// 把元素添加到上一個對象下面,同時把本對象設(shè)置成為oldRe
// 作為下一個對象的父節(jié)點(diǎn)
} else {
if (preReadXmlExpr instanceof ElementExpression) {
ele = preReadXmlExpr;
ele.addEle(readXml);
preReadXmlExpr = readXml;
} else if (preReadXmlExpr instanceof ElementsExpression) {
eles = preReadXmlExpr;
eles.addEle(readXml);
preReadXmlExpr = readXml;
}
}
}
return returnReadXMLExpr;
}
return {
// 公共方法
parse: function (expr) {
listEle = [];
var mapPath = parseMapPath(expr);
var list = mapPath2Interpreter(mapPath);
return buildTree(list);
}
};
}();
void function () {
// 準(zhǔn)備上下文
var c = new Context('Interpreter.xml');
// 通過解析其獲取抽象語法樹
var readXmlExpr = Parser.parse('root/a/b/d$.id$');
// 請求解析,獲取返回值
var ss = readXmlExpr.interpret(c);
console.log('------------parsing--------------');
for (var i = 0, len = ss.length; i < len; i++) {
console.log('d的屬性id的值是 = ' + ss[i]);
}
console.log('---------------parsed--------------');
// 如果要使用同一個上下文,連續(xù)進(jìn)行解析,需要重新初始化上下文對象
c.reInit();
var readxmlExpr2 = Parser.parse('root/a/b/d$');
var ss2 = readxmlExpr2.interpret(c);
console.log('------------parsing--------------');
for (i = 0, len = ss2.length; i < len; i++) {
console.log('d的值是 = ' + ss2[i]);
}
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr3 = Parser.parse('root/a/b/c');
var ss3 = readxmlExpr3.interpret(c);
console.log('------------parsing--------------');
console.log('c的name屬性值是 = ' + ss3);
console.log('---------------parsed--------------');
c.reInit();
var readxmlExpr4 = Parser.parse('root/a/b/c.name');
var ss4 = readxmlExpr4.interpret(c);
console.log('------------parseing--------------');
console.log('c的name屬性值是 = ' + ss4);
console.log('---------------parsed--------------');
}();
// 這樣就實(shí)現(xiàn)了類似XPath的部分功能
// 沒錯,就類似于jQuery選擇器的部分功能
}());
輸出: d的值是 = d1
d的值是 = d2
d的值是 = d3
d的值是 = d4
d的屬性id的值是 = 1
d的屬性id的值是 = 2
d的屬性id的值是 = 3
d的屬性id的值是 = 4
------------parsing--------------
d的屬性id的值是 = 1
d的屬性id的值是 = 2
d的屬性id的值是 = 3
d的屬性id的值是 = 4
---------------parsed--------------
------------parsing--------------
d的值是 = d1
d的值是 = d2
d的值是 = d3
d的值是 = d4
---------------parsed--------------
------------parsing--------------
c的name屬性值是 = 12345
---------------parsed--------------
------------parseing--------------
c的name屬性值是 = testC
---------------parsed--------------
- JavaScript設(shè)計模式之單例模式實(shí)例
- 學(xué)習(xí)JavaScript設(shè)計模式(單例模式)
- JavaScript實(shí)現(xiàn)設(shè)計模式中的單例模式的一些技巧總結(jié)
- JavaScript設(shè)計模式之單例模式詳解
- js設(shè)計模式之單例模式原理與用法詳解
- JS 設(shè)計模式之:單例模式定義與實(shí)現(xiàn)方法淺析
- javascript設(shè)計模式 – 單例模式原理與應(yīng)用實(shí)例分析
- JavaScript 設(shè)計模式 安全沙箱模式
- JavaScript設(shè)計模式之觀察者模式(發(fā)布者-訂閱者模式)
- JavaScript 設(shè)計模式之組合模式解析
- 常用的Javascript設(shè)計模式小結(jié)
- JavaScript設(shè)計模式---單例模式詳解【四種基本形式】
相關(guān)文章
微信小程序-圖片、錄音、音頻播放、音樂播放、視頻、文件代碼實(shí)例
本篇文章主要介紹了微信小程序-圖片、錄音、音頻播放、音樂播放、視屏、文件代碼實(shí)例,有興趣的可以了解一下。2016-11-11uni-app多環(huán)境配置實(shí)現(xiàn)自動部署的方式詳解
前后端分離開發(fā)模式中,無論前后端都有可能區(qū)分不同的環(huán)境配置,下面這篇文章主要給大家介紹了關(guān)于uni-app多環(huán)境配置實(shí)現(xiàn)自動部署的相關(guān)資料,需要的朋友可以參考下2022-06-06瀏覽器環(huán)境下JavaScript腳本加載與執(zhí)行探析之defer與async特性
defer和async特性相信是很多JavaScript開發(fā)者"熟悉而又不熟悉"的兩個特性,從字面上來看,二者的功能很好理解,分別是"延遲腳本"和"異步腳本"的作用2016-01-01gulp-htmlmin壓縮html的gulp插件實(shí)例代碼
這篇文章主要介紹了gulp-htmlmin壓縮html的gulp插件實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-06-06JavaScript 學(xué)習(xí)筆記(七)字符串的連接
javascript 字符串的連接效率問題,需要的朋友可以參考下。2009-12-12Js數(shù)組對象如何根據(jù)多個key值進(jìn)行分類
這篇文章主要介紹了Js數(shù)組對象如何根據(jù)多個key值進(jìn)行分類,每周從 npm 下載?lodash.groupBy?的次數(shù)在 150 萬到 200 萬之間,很高興看到 JavaScript 填補(bǔ)了這些空白,讓我們的工作變得更加輕松,需要的朋友可以參考下2024-02-02