用函數模板,寫一個簡單高效的 JSON 查詢器的方法介紹

來源:互聯網
上載者:User

JSON可謂是JavaScript的亮點,它能用優雅簡練的代碼實現Object和Array的初始化。同樣是基於文本的資料定義,它比符號分隔更有語義,比XML更簡潔。因此越來越多的JS開發中,使用它作為資料的傳輸和儲存。

JS數組內建了不少有用的方法,方便我們對資料的查詢和篩選。例如我們有一堆資料:
複製代碼 代碼如下:
var heros = [
        // 名============攻=====防=======力量====敏捷=====智力====
        {name:'冰室女巫', DP:38, AP:1.3, Str:16, Agi:16, Int:21},
        {name:'沉默術士', DP:39, AP:1.1, Str:17, Agi:16, Int:21},
        {name:'娜迦海妖', DP:51, AP:6.0, Str:21, Agi:21, Int:18},
        {name:'賞金獵人', 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},
        {name:'鍊金術士', DP:49, AP:0.6, Str:25, Agi:11, Int:25}
        //...
    ];

要查詢攻擊大於40並且防禦小於4的英雄,我們可以用Array的filter方法:

複製代碼 代碼如下:
 var match = heros.filter(function(e) {
        return e.DP > 40 && e.AP < 4;
    });

返回得到一個數組,包括合格2個結果。

相比手工去寫迴圈判斷,filter方法為我們提供了很大的方便。但它是基於函數回調的,所以每次使用必須寫一個function,對於簡單的查詢很是累贅,而且使用回調效率也大大降低。但這是也沒有辦法的,想簡單必然要犧牲一定效能。 如果能使用比這更簡單的語句,並且完全擁有代碼展開時效率,該有是多麼完美的事。

先來想象下,要是能將上面的代碼寫成這樣,並且查詢速度和手寫的遍曆判斷一樣:
複製代碼 代碼如下:
    var match = heros.select('@DP>40 AND @AP<4');

看上去有點像SQL,連文法都換了?這樣豈不是要寫一個詞法分析,語義解釋等等等等一大堆的指令碼引擎的功能了,沒個幾千上萬行代碼都搞不定,而且效率肯定更糟了。。。如果想到那麼複雜,那麼你還沒深刻的理解指令碼的精髓。但凡是指令碼語言,都有運行時動態解釋代碼的介面,例如vbs的execute();js的eval(),new Function(),甚至建立一個<script>動態寫入代碼。

顯然,要是能將另一種語言,翻譯成js代碼,那麼就可直接交給宿主來執行了!

例如上面select中的字元,我們簡單的將"@"替換成"e.", "AND"替換成"&&",於是就成了一個合法的js運算式,完全可以交給eval來執行。

所以我們要做的,就是將原始語句翻譯成js語句來執行。並且為了提高效率,將翻譯好的js運算式內聯到一個上下文環境,產生一個可執行檔函數體,而不是每次遍曆中都依靠回調來判斷。

於是,函數模版就要派上用場了。

函數模版簡介

在C++裡面,有宏和類模版這麼個東西,可以讓一些計算在編譯階段就完成了,大幅提升了運行時代碼的效能。指令碼雖然沒有嚴格意義上的編譯,但在第一次執行的時候會解析並充分的最佳化,這是目前主流瀏覽器相互競爭點。所以,我們要將重複eval的代碼,鑲嵌到事先提供的樣板函數裡:一個準備就緒,就差運算式計算的函數:
複製代碼 代碼如下:
 /**
     * 模版: tmplCount
     * 功能: 統計arr數組中符合$express運算式的數量
     */
    function tmplCount(arr) {
        var count = 0;

        for(var i = 0; i < arr.length; i++) {
            var e = arr[i];

            if($express) {
                count++;
            }
        }
        return count;
    }

上面就是一個模板函數,遍曆參數arr[]並統計符合$express的數量。除了if(...)內的運算式外,其他都已經準備就緒了。字元$express也可以換成其他標識,只要不和函數內其他字元衝突即可。

當我們需要執行個體化時,首先通過tmplCount.toString()將函數轉成字串格式,然後將其中的$express替換成我們想要的運算式,最後eval這串字元,得到一個Function類型的變數,一個模板函數的執行個體就產生了!

我們簡單的示範下:
複製代碼 代碼如下:
 /**
     * 函數: createInstance
     * 參數: exp
     *      一段js運算式字串,用來替換tmplCount模板的$express
     * 返回:
     *      返回一個Function,模版tmplCount的執行個體
     */
    function createInstance(exp)
    {
        // 替換模板內的運算式
        var code = tmplCount.toString()
                    .replace('$express', exp);

        // 防止匿名函數直接eval報錯
        var fn = eval('0,' + code);

        // 返回模板執行個體
        return fn;
    }


    // 測試參數
    var student = [
        {name: 'Jane', age: 14},
        {name: 'Jack', age: 20},
        {name: 'Adam', age: 18}
    ];

    // demo1
    var f1 = createInstance('e.age<16');
    alert(f1(student));    //1個

    // demo2
    var f2 = createInstance('e.name!="Jack" && e.age>=14');
    alert(f2(student));    //2個

注意createInstance()的參數中,有個叫e的對象,它是在tmplCount模版中定義的,指代遍曆時的具體元素。返回的f1,f2就是tmplCount模板的兩個執行個體。最終調用的f1,f2函數中,已經內嵌了我們的運算式語句,就像我們事先寫了兩個同樣功能的函數一樣,所以在遍曆的時候直接運行運算式,而不用回調什麼的,效率大幅提升。

其實說白了,tmplCount的存在僅僅是為了提供這個函數的字串而已,其本身從來不會被調用。事實上用字串的形式定義也一樣,只不過用函數書寫比較直觀,方便測試。

值得注意的是,如果指令碼後期需要壓縮最佳化,那麼tmplCount模板絕對不能參與,否則對應的"e."和"$express"都有可能發生變化。

JSON基本查詢功能

函數模板的用處和實現介紹完了,再來回頭看之前的JSON查詢語言。我們只需將類似sql的語句,翻譯成js運算式,並且產生一個函數模板執行個體。對於相同的語句,我們可以進行緩衝,避免每次都翻譯。

首先我們實現查詢器的模板:
複製代碼 代碼如下:
 var __proto = Object.prototype;

    //
    // 模板: __tmpl
    // 參數: $C
    // 說明: 記錄並返回_list對象中匹配$C的元素集合
    //
    var __tmpl = function(_list) {
        var _ret = [];
        var _i = -1;

        for(var _k in _list) {
            var _e = _list[_k];

            if(_e && _e != __proto[_k]) {
                if($C)
                    _ret[++_i] = _e;
            }
        }
        return _ret;

    }.toString();

然後開始寫Object的select方法:
複製代碼 代碼如下:
 //
    // select方法實現
    //
    var __cache = {};

    __proto.select = function(exp) {
        if(!exp)
            return [];

        var fn = __cache[exp];

        try {
            if(!fn) {
                var code = __interpret(exp);            //解釋運算式
                code = __tmpl.replace('$C', code);      //應用到模版

                fn = __cache[exp] = __compile(code);    //執行個體化函數
            }

            return fn(this);                            //查詢當前對象
        }
        catch(e) {
            return [];
        }
    }

其中__cache表實現了查詢語句的緩衝。對於重複的查詢,效能可以極大的提升。
複製代碼 代碼如下:
    function __compile() {
        return eval('0,' + arguments[0]);
    }

 __compile之所以單獨寫在一個空函數裡,就是為了eval的時候有個儘可能乾淨的上下文環境。

__interpret是整個系統的重中之重,負責將查詢語句翻譯成js語句。它的實現見智見仁,但儘可能簡單,不要過度分析文法。

具體代碼查看:jsonselect.rar
出於示範,目前只實現部分準系統。以後還可以加上 LIKE,BETWEEN,ORDER BY 等等常用的功能。

Demo
複製代碼 代碼如下:
var heros = [
        // 名============攻=====防=======力量====敏捷=====智力====
        {name:'冰室女巫', DP:38, AP:1.3, Str:16, Agi:16, Int:21},
        {name:'沉默術士', DP:39, AP:1.1, Str:17, Agi:16, Int:21},
        {name:'娜迦海妖', DP:51, AP:6.0, Str:21, Agi:21, Int:18},
        {name:'賞金獵人', 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},
        {name:'鍊金術士', DP:49, AP:0.6, Str:25, Agi:11, Int:25}
        //...
    ];

複製代碼 代碼如下:
 // 查詢:力量,敏捷 都超過20的
    // 結果:娜迦海妖
    var match = heros.select('@Str>20 AND @Agi>20');

    // 查詢:“士”結尾的
    // 結果:沉默術士,劇毒術士,鍊金術士
    var match = heros.select('right(@name,1)="士" ');

    // 查詢:生命值 超過500的
    // 結果:鍊金術士
    var match = heros.select('100 + @Str*19 > 500');

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.