用函数模板,写一个简单高效的 JSON 查询器的方法介绍

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');

(0)

相关推荐

  • javascript定义函数的方法

    JavaScript 使用关键字 function 定义函数. 函数可以通过声明定义,也可以是一个表达式. 函数声明 在之前的教程中,你已经了解了函数声明的语法 : function functionName(parameters) { 执行的代码 } 函数声明后不会立即执行,会在我们需要的时候调用到. 实例 function myFunction(a, b) { return a * b; } 函数表达式 JavaScript 函数可以通过一个表达式定义. 函数表达式可以存储在变量中: 实例

  • js中匿名函数的创建与调用方法分析

    本文实例分析了js中匿名函数的创建与调用方法.分享给大家供大家参考.具体实现方法如下: 匿名函数就是没有名字的函数了,也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数.最经常用作回调函数(callback)参数的值,很多新手朋友对于匿名函数不了解.这里就来分析一下. function 函数名(参数列表){函数体;} 如果是创建匿名函数,那就应该是: function(){函数体;} 因为是匿名函数,所以一般也不会有参数传给他. 为什么要创建匿名函数呢?在什么情况下会使用到匿

  • js函数调用常用方法详解

    来源 javascript语言精粹.这不是书上的源代码. js的函数调用会免费奉送两个而外的参数就是 this 和 arguments .arguments是参数组,他并不是一个真实的数组,但是可以使用.length方法获得长度. 书上有说4中调用方式: 方法调用模式 函数调用模式 构造器调用模式 apply调用模式 下面我们来看看一些实例更好理解. 1:方法调用模式 请注意this此时指向myobject. 复制代码 代码如下: /*方法调用模式*/ var myobject={ value:

  • jquery与js函数冲突的两种解决方法

    如果您还有别的要求,想继续使用原先的$(),同时还需要与别的类库不冲突的话,还有两种解决方法 其一: 复制代码 代码如下: jQuery.noConflict(); jQuery(function($) { $("p").click(function() //在函数内继续可以使用jquery类库的$()方法 { alert($(this).text()); }) }) var JsCOM_cr = $("cr"); // 在函数外面,照样可以使用JsCOM.js的$

  • JavaScript的各种常见函数定义方法

    本文详细讲述了JavaScript的各种常见函数定义方法,分享给大家供大家参考.具体分析如下: 首先看一下JavaScript最常见的四种函数定义: 用Function构造函数定义的函数,代码如下: var multiply = new Function('x', 'y', 'return x * y;'); 函数声明,这种方式也是最为常见的一种: function multiply(x, y) { return x * y; } 函数表达式,声明为匿名函数然后赋值给一变量,很常见的方式: va

  • 解决火狐浏览器下JS setTimeout函数不兼容失效不执行的方法

    今天检查自己用JQuery+AJAX+PHP做的网站后台登录检测,发现登陆成功后执行页面跳转函数这段JavaScript(JS)代码特效在IE和谷歌浏览器Chrome下都可以很好地执行,兼容性还不错.结果到了火狐(FireFox)浏览器下setTimeout这个JS内置函数不执行了,无效了,也没报错!打开FireBUG指望它能检测出JS的错误,结果没用...Javascript(JS)脚本代码在各浏览器下的兼容是一个很头疼的问题,经过一番调试和搜索,终于解决了setTimeout这个JS代码在火

  • javascript的函数、创建对象、封装、属性和方法、继承

    一,function 从一开始接触到js就感觉好灵活,每个人的写法都不一样,比如一个function就有N种写法 如:function showMsg(){},var showMsg=function(){},showMsg=function(){} 似乎没有什么区别,都是一样的嘛,真的是一样的吗,大家看看下面的例子 复制代码 代码如下: ///----------------------------------------------------------------------------

  • PHP使用json_encode函数时不转义中文的解决方法

    本文实例讲述了PHP使用json_encode函数时不转义中文的解决方法.分享给大家供大家参考.具体方法如下: json_encode函数对于gbk中的中文字符是不会转换的或直接转换成空格了,本文就来给各位整理一个关于json不转义中文问题处理技巧,相信对大家有所帮助. 如果你调用 PHP 自带的 json_encode() 函数, 碰到中文时, 中文会被转义掉. 例如: 复制代码 代码如下: echo json_encode(array('你好')); // 输出: ["\u4f60\u597

  • 一个非常全面的javascript URL解析函数和分段URL解析方法

    一.URL解析函数 复制代码 代码如下: <script>  /** *@param {string} url 完整的URL地址 *@returns {object} 自定义的对象 *@description 用法示例:var myURL = parseURL('http://abc.com:8080/dir/index.html?id=255&m=hello#top'); myURL.file='index.html' myURL.hash= 'top' myURL.host= 'a

  • JS中获取函数调用链所有参数的方法

    function getCallerArgument(){ var result = []; var slice = Array.prototype.slice; var caller = arguments.callee.caller; while(caller){ result = result.concat(slice.call(caller.arguments, 0)); caller = caller.arguments.callee.caller; } return result;

  • javascript中使用replaceAll()函数实现字符替换的方法

    而str.replace(/\-/g,"!")则可以全部替换掉匹配的字符(g为全局标志). replace() The replace() method returns the string that results when you replace text matching its first argument (a regular expression) with the text of the second argument (a string). If the g (glob

  • 日常收集整理的JavaScript常用函数方法

    函数就是包裹在花括号中的代码块,前面使用了关键词 function: function functionname() { 这里是要执行的代码 } 当调用该函数时,会执行函数内的代码. 可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用. 提示:JavaScript 对大小写敏感.关键词 function 必须是小写的,并且必须以与函数名称相同的大小写来调用函数. 字符串长度截取 functiocutstr(strlen{ vatemp, i

  • javascript trim函数在IE下不能用的解决方法

    javascript 的trim 函数在firefox 下面使用没有问题 <script language="javascript"> var test1 = " aa "; test1 = test1.toString(); test1 = test1.trim(); </script> 在火狐下这样用没有问题, 但是在IE下就报错 那么我们可以修改一下 String.prototype.trim=function(){return thi

  • js 把字符串当函数执行的方法

    并且用js去执行: function test(str){ alert(str); } window['test']('aaaaaaaaaaaaaaaaaaaaa'); [Ctrl+A 全选 注:如需引入外部Js需刷新才能执行] ------------------------------- 方法一... function test(str){ alert(str); } eval('test("aaaaaaaaaaaaaaaaaaa")'); [Ctrl+A 全选 注:如需引入外部J

  • js使用split函数按照多个字符对字符串进行分割的方法

    本文实例讲述了js使用split函数按照多个字符对字符串进行分割的方法.分享给大家供大家参考.具体分析如下: js中的split()函数可以对字符串按照指定的符号进行分割,但是如果字符串中存在多个分割符号,js的split()函数是否还可以胜任呢,答案是肯定的,js的split()函数可以通过正则表达式实现多分隔符的字符串分割,调用也很简单,下面是详细范例. 下面的代码可以通过js split方法对字符串按照逗号分割 var mystring = "a,b,c,d,e"; var my

  • JavaScript函数的4种调用方法详解

    在JavaScript中,函数是一等公民,函数在JavaScript中是一个数据类型,而非像C#或其他描述性语言那样仅仅作为一个模块来使用.函数有四种调用模式,分别是:函数调用形式.方法调用形式.构造器形式.以及apply形式.这里所有的调用模式中,最主要的区别在于关键字 this 的意义,下面分别介绍这个几种调用形式. 本文主要内容: 1.分析函数的四种调用形式2.弄清楚函数中this的意义3.明确构造函对象的过程4.学会使用上下文调用函数 一.函数调用形式 函数调用形式是最常见的形式,也是最

  • 多种方法实现当jsp页面完全加载完成后执行一个js函数

    方法1:如下程序,当页面完全加载后执行openTheIndexPage()方法 复制代码 代码如下: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> <title>Telecommunications Data Collection System</title> <script t

  • Javascript 判断是否存在函数的方法

    复制代码 代码如下: window.onload=function(){ try{ if(test&&typeof(test)=="function"){ test(); }else{ alert("不存在的函数"); } }catch(e){ } } function test(){ alert("函数执行--"); } 如果不存在,会抛出异常,所以要加try--catch.

  • JavaScript子窗口调用父窗口变量和函数的方法

    本文实例讲述了JavaScript子窗口调用父窗口变量和函数的方法.分享给大家供大家参考.具体如下: 示例1:子窗口是新打开的窗口 父窗口: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns=" http://www.w3.

随机推荐