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}];// demo1var f1 = createInstance('e.age<16');alert(f1(student));    //1个// demo2var f2 = createInstance('e.name!="Jack" && e.age>=14');alert(f2(student));    //2个

  

  注意createInstance()的参数中,有个叫e的对象,它是在tmplCount模版中定义的,指代遍历时的具体元素。返回的f1f2就是tmplCount模板的两个实例。最终调用的f1f2函数中,已经内嵌了我们的表达式语句,就像我们事先写了两个同样功能的函数一样,所以在遍历的时候直接运行表达式,而不用回调什么的,效率大幅提升。

  

  其实说白了,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语句。它的实现见智见仁,但尽可能简单,不要过度分析语法。

  具体代码查看: http://files.cnblogs.com/index-html/jsonselect.js
  出于演示,目前只实现部分基本功能。以后还可以加上 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}//...];

测试:heros.select('  ')查询
    // 查询:力量,敏捷 都超过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');

利用函数模板技术,写一个简单高效的 JSON 查询器相关推荐

  1. 写一个简单高效的 JSON 查询器

    JSON可谓是JavaScript的亮点,它能用优雅简练的代码实现Object和Array的初始化.同样是基于文本的数据定义,它比符号分隔更有语义,比XML更简洁.因此越来越多的JS开发中,使用它作为 ...

  2. C# 写一个简单的TXT小说阅读器

    目的 上班的时候看小说不是要上那些网站看,就是要下载txt用那些文本阅读器看,很是麻烦.就想做一个简单的txt小说阅读器.要求不高,只要能打开txt文档并显示章节目录,并且能够点击跳转至章节处即可. ...

  3. 利用cookie和jsp写一个简单的登录判断的网页,并获取上次的登录的时间。

    要求:因为只是简单制作,没必要连数据库,直接用确定值去判断.然后就是第一次登录的时候需要获取到登录的时间,然后在下次登录的时候将上次获取到的时间输出. 思路:首先第一个,第一次登录的jsp(姑且当做注 ...

  4. 用c语言写一个简单的名人名言播放器

    由于上传不了太多代码,我只放了一部分代码块,用的switch语句,case跳转到首页或者下一页,没一页都做成函数包装起来,每一行用一个sleep延时处理.这是个笨方法,也可以用c调用文件调用文件,把搜 ...

  5. 函数模板案例_利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序 排序规则从大到小,排序算法为选择排序 分别利用char数组和int数组进行测试

    案例描述: 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序 排序规则从大到小,排序算法为选择排序 分别利用char数组和int数组进行测试 #include <iostream& ...

  6. 利用python写一个简单的双色球彩票系统

    利用python写一个简单的双色球彩票系统 1.设置每次买的号码一样 写一个双色球彩票系统,系统可以随机产生一组数据,一组彩票数据有六位数,这六位数的的取值范围是0和1. 一张彩票是两块钱,用户可以选 ...

  7. 如何搭建python框架_从零开始:写一个简单的Python框架

    原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...

  8. 构建自己的购物搜索引擎一:写一个简单的

    记得2010年10月9号,淘宝全网搜索引擎一淘网上线,当时不怎么关注,只是在网站上看到过新闻而己,前两个月,觉得是时候走确定自己以后要走的方向了,于是决定以后加入到搜索的行列中,此时开始关注一淘网的技 ...

  9. python123程序设计题说句心里话_用c++写一个简单的计算器程序

    // 050305.cpp : 定义控制台应用程序的入口点. // // 050304.cpp : 定义控制台应用程序的入口点. // //四则运算 #include "stdafx.h&q ...

最新文章

  1. VMware 虚拟化编程(13) — VMware 虚拟机的备份方案设计
  2. matlab中的cellstr的用法,matlab中的cell array, cellstr()和char()的用法
  3. 人脸检测中,如何构建输入图像金字塔
  4. OpenCV中GPU模块使用
  5. inline「一」:从 image 底部白边初识 line-height
  6. 求Sn = a+aa+aaa+...+aaaaaa(n个a),其中a是一个数字,n代表a的位数,例如 2+22+222+2222+22222(此时n=5),n由键盘输入。
  7. Bootstrap 分页导航
  8. matlab 小波滤波器,matlab小波滤波器使用
  9. ADB server didn't ACK问题
  10. win 2008 R2 域服务器策略同步异常解决方案。
  11. cesium接入加载倾斜摄影(cesium篇.16)
  12. c语言 实现参数值双向传递,基于C语言函数参数传递规律的探讨
  13. mac ps安装 服务器无响应,Mac程序无响应?六个方法教你如何退出无响应的程序...
  14. 二级路由器配置网址无法访问的解决方法
  15. android迅雷下载搭建开发环境
  16. 赛前采访里皮表示:希望队员用真心踢球。
  17. 网络营销推广怎么做(更全面的网络营销知识点)
  18. HDU 1083 Coures(二分图匹配)
  19. web前端期末大作业:旅游网页设计与实现——个人旅游博客(4页)HTML+CSS
  20. 微信小程序之地图组件

热门文章

  1. 从 0 到 1 搭建 webpack2+vue2 自定义模板详细教程
  2. numpy中的zeros函数
  3. EC03-DNC4G通信模块的使用
  4. 外汇基础概念--汇率
  5. Mysql查询没有学完所有课程的学生的学号和姓名
  6. WebService安装中的典型错误(0x8007000d)-WIN7操作系统64位
  7. 请你谈谈Redis主从复制的理解?
  8. Splunk KV Store
  9. OpenCV - C++实战(04) — 简单的图像运算
  10. Linux-3 常用工具