JSON可謂是JavaScript的亮點,它能用優雅簡練的程式碼實作Object和Array的初始化。同樣是基於文字的資料定義,它比符號分隔更有語義,比XML更簡潔。因此在越來越多的JS開發中,使用它作為資料的傳輸和儲存。
JS陣列內建了不少有用的方法,方便我們對資料的查詢和篩選。例如我們有一堆資料:
var heros = [
var heros = [
// 名============攻=====防=======力量====敏捷=====智力====
{ name:'冰室女巫', DP:38, AP:1.3, Str:16, Agi:16, Int:21},
{name:'沉默術士', DP:39, AP:1.1, StrStr: 17, Agi:16, Int:21},
{name:'娜迦海妖', DP:51, AP:6.0, Str:21, Agi:21, Int:18},
:'賞金獵人', DP:39, AP:4.0, Str:17, Agi:21, Int:16},
{name:'劇毒術士', DP:45, AP:3.1, Str: 18, Agi:22, Int:15},
{name:'光之守衛', DP:38, AP:1.1, Str:16, Agi:15, Int:22},
'煉金術士', DP:49, AP:0.6, Str:25, Agi:11, Int:25}
//...
];
...
];
複製程式碼
程式碼如下:
var match = heros.filter(function(e) {
return e.DP > 40 && e.AP });
回傳得到一個陣列,包括符合條件的2個結果。
比起手工去寫循環判斷,filter方法為我們提供了很大的方便。但它是基於函數回呼的,所以每次使用必須寫一個function,對於簡單的查詢很是累贅,而且使用回呼效率也大大降低。但這是也沒辦法的,想簡單必然要犧牲一定性能。 如果能使用比這更簡單的語句,並且完全擁有程式碼展開時效率,該有是多麼完美的事。
先來想像下,要是能將上面的程式碼寫成這樣,並且
查詢速度和手寫的遍歷判斷一樣:
複製程式碼
程式碼如下:
var match = heros.select('@DP>40 AND @AP
看起來有點像SQL,連文法都換了?這樣豈不是要寫一個詞法分析,語意解釋等等等等一大堆的腳本引擎的功能了,沒個幾千上萬行程式碼都搞不定,而且效率肯定更糟了。 。 。如果想到那麼複雜,那麼你還沒深刻的理解腳本的精髓。但凡是腳本語言,都有運行時動態解釋代碼的接口,例如vbs的execute();js的eval(),new Function(),甚至創建一個<script>動態寫入代碼。
<P>顯然,如果能將另一種語言,翻譯成js程式碼,那麼就可直接交給宿主來執行了! <P>
<P>例如上面select中的字符,我們簡單的將"@"替換成"e.", "AND"替換成"&&",於是就成了一個合法的js表達式,完全可以交給eval來執行。
<P>所以我們要做的,就是將原始語句翻譯成js語句來執行。並且為了提高效率,將翻譯好的js表達式內聯到一個上下文環境,產生一個可執行的函數體,而不是每次遍歷中都依靠回調來判斷。 <STRONG>
<br>於是,函數模版就要派上用場了。 <br>
<BR>函數模版簡介<div class="codetitle"><span><a style="CURSOR: pointer" data="84492" class="copybut" id="copybut84492" onclick="doCopy('code84492')">在C 裡面,有宏和類模版這麼東西,可以讓一些計算在編譯階段就完成了,大幅提升了運行時代碼的性能。雖然腳本沒有嚴格意義上的編譯,但在第一次執行的時候會解析並充分的最佳化,這是目前主流瀏覽器相互競爭點。所以,我們要將重複eval的程式碼,鑲嵌到事先提供的樣板函數裡:一個準備就緒,就差表達式計算的函數:<U>複製程式碼<🎜 ><🎜><🎜> 程式碼如下:<🎜><div class="codebody" id="code84492"><BR> /**<BR> * 模版: tmplCount<BR> * 功能: 統計arr陣列中符合$express表達式的數量<BR> */<BR> function tmplCount(arr) {<BR> var count = 0;
<P> for(var i = 0; i < arr.length; i ) {<BR> var e = arr[i];
<P> if($express) {<BR> }<BR> return count;<BR> }<BR><BR><BR>上面就是一個範本函數,遍歷參數arr []並統計符合$express的數量。除了if(...)內的表達式外,其他都已經準備就緒了。字元$express也可以換成其他標識,只要不和函數內其他字元衝突即可。
<BR>當我們需要實例化時,首先透過tmplCount.toString()將函數轉成字串格式,然後將其中的$express替換成我們想要的表達式,最後eval這串字符,得到一個Function類型的變量,一個模板函數的實例就產生了!
<P>我們簡單的示範下:<P><STRONG><BR><div class="codetitle"><span>複製程式碼<a style="CURSOR: pointer" data="65929" class="copybut" id="copybut65929" onclick="doCopy('code65929')"><U> 程式碼如下:<div class="codebody" id="code65929"> 程式碼如下:<BR><BR> <BR> /**<BR> * 函數: createInstance<BR> * 參數: exp<BR> * 回傳一個Function,模版tmplCount的例<BR> */<BR> function createInstance(exp)<BR> {<BR> .replace ('$express', exp);<BR><BR> // 防止匿名函數直接eval封包錯誤<br> var fn = eval('0,' code);<br>
<BR> // 回傳範本實例 return fn;<P> }<BR>
<BR> // 測試參數<P> var student = [<BR> {name: 'Jane' {name: 'Adam', age: 18}<BR> ];<BR>
<BR> // demo1<BR> var f1 = createInstance('e.age<16');<BR> alert(f1(student)); 11個
<P> // demo2<BR> var f2 = createInstance('e.name!="Jack" && e.age>=14');<BR> alert(f2(student)); ><P>注意createInstance()的參數中,有個叫e的對象,它是在tmplCount模版中定義的,指代遍歷時的具體元素。傳回的f1,f2就是tmplCount模板的兩個實例。在最終調用的f1,f2函數中,已經內嵌了我們的表達式語句,就像我們事先寫了兩個同樣函數的函數一樣,所以在遍歷的時候直接運行表達式,而不用回調什麼的,效率大幅提升。 <BR>
<BR><BR>
<BR>其實說穿了,tmplCount的存在只是為了提供這個函數的字串而已,本身從來就不會被呼叫。事實上用字串的形式定義也一樣,只不過用函數書寫比較直觀,方便測試。
<P>值得注意的是,如果腳本後期需要壓縮優化,那麼tmplCount模板絕對不能參與,否則對應的"e."和"$express"都有可能改變。 <IMG alt="" src="http://files.jb51.net/file_images/article/201304/201304171146157.png">
<P>JSON基本查詢功能<P>
函數範本的用處和實作介紹完了,再來回頭看之前的JSON查詢語言。我們只要將類似sql的語句,翻譯成js表達式,並且產生一個函數模板實例。對於相同的語句,我們可以進行緩存,避免每次都翻譯。 <P>
<STRONG>首先我們實作查詢器的範本:<P><P><STRONG><BR>複製程式碼<div class="codetitle"><span><a style="CURSOR: pointer" data="48175" class="copybut" id="copybut48175" onclick="doCopy('code48175')">複製程式碼<U> 程式碼<div class="codebody" id="code48175"><BR> var __proto = Object.prototype;
<P> //<BR> // 範本: __tmpl<BR> // 參數: $C<BR> // 說明: 記錄並傳回_list物件中符合$C的元素集合< var __tmpl = function(_list) {<BR> var _ret = [];<BR> var _i = -1;<BR>
<BR> for(var _k in _list) { var _e = _list[_k]< proto[_k]) {<P> if($C)<BR> _ret[ _i] = _e;<br> return _ret;<br>
<BR> }.toString();<BR><BR><BR><BR>然後開始寫Object的select方法:<P><BR><BR><STRONG><BR><div class="codetitle"><> 🎜><span><a style="CURSOR: pointer" data="74878" class="copybut" id="copybut74878" onclick="doCopy('code74878')"> 程式碼如下:<U> // // select方法實作 //<div class="codebody" id="code74878"> // select方法實作<BR> //<BR> __proto.select = function(exp) {<BR> if(!exp)<BR> return [];<br>
<br> var fn = __cache[exp];<BR>
<BR> try { if(!fn) {<P> //解釋表達式 code = __tmpl.replace('$C', code); 到模版<P>
<BR> fn = __cache[exp] = __compile(code); //實例化函數<BR>
<BR> return fn(this); catch(e) { return [];<P> 其中__cache表實作了查詢語句的快取。對於重複的查詢,效能可以極大的提升。 <BR><P><BR><BR>複製碼<BR><BR><BR> 代碼如下:<BR><BR> function 🎜><BR><div class="codetitle"> function 🎜><span><a style="CURSOR: pointer" data="94768" class="copybut" id="copybut94768" onclick="doCopy('code94768')"> 0,' arguments[0]);<U> } __compile之所以單獨寫在一個空函數裡,就是為了eval的時候有個盡可能乾淨的上下文環境。
<div class="codebody" id="code94768">__interpret是整個系統的重中之重,負責將查詢語句翻譯成js語句。它的實現見智見仁,但盡可能簡單,不要過度分析語法。 <BR>
<BR>具體程式碼檢視:<BR>jsonselect.rar<BR>出於演示,目前只實現部分基本功能。以後還可以再加上 LIKE,BETWEEN,ORDER BY 等等常用的功能。 <BR>
<P>Demo<P><A href="http://xiazai.jb51.net/201304/yuanma/jsonselect.rar" target=_blank><BR>複製程式碼<P><STRONG><BR> 程式碼如下:<div class="codebody" id="code75283"><BR>var heros = [<BR> // 名============攻=====防=======力量====敏捷== ===智力====<BR> {name:'冰室女巫', DP:38, AP:1.3, Str:16, Agi:16, Int:21},<BR> ', DP:39, AP:1.1, Str:17, Agi:16, Int:21},<BR> {name:'娜迦海妖', DP:51, AP:6.0, Str:21, Agigi: 21, Int:18},<BR> {name:'賞金獵人', DP:39, AP:4.0, Str:17, Agi:21, Int:16},< ', DP:45, AP:3.1, Str:18, Agi:22, Int:15},<BR> {name:'光之守衛', DP:38, AP:1.1, Str:16, Agi:15 , Int:22},<BR> {name:'煉金術士', DP:49, AP:0.6, Str:25, Agi:11,Int:25}<> <BR><BR><BR><BR><BR><div class="codetitle">複製程式碼<span><a style="CURSOR: pointer" data="60993" class="copybut" id="copybut60993" onclick="doCopy('code60993')"><U> 程式碼如下: 程式碼如下:<div class="codebody" id="code60993"><BR><BR>敏捷都超過20的<BR> // 結果:娜迦海妖 var match = heros.select('@Str>20 AND @Agi>20');<P>
<BR> // 查詢:「士」結尾的<BR> // 結果:沉默術士,劇毒術士,煉金術士 var match = heros.select('right(@name,1)="士" ');<P>
<BR> // 查詢:生命值超過500的<BR> // 結果:煉金術士<BR> var match = heros.select('100 @Str*19 > 500');<🜎 ><BR></script>