使用jQuery提高搜索JSON对象的性能

Improve performance of searching JSON object with jQuery

本文关键字:JSON 对象 性能 搜索 jQuery 高搜索 使用      更新时间:2023-09-26

如果SO某个地方已经回答了这个问题,请原谅我。我已经搜索过了,似乎这是一个相当具体的案例。

下面是JSON的一个例子(注意:这是非常精简的-这是动态加载的,目前有126条记录):

var layout = {
"2":[{"id":"40","attribute_id":"2","option_id":null,"design_attribute_id":"4","design_option_id":"131","width":"10","height":"10",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"},
    {"id":"41","attribute_id":"2","option_id":"115","design_attribute_id":"4","design_option_id":"131","width":"2","height":"1",
            "repeat":"0","top":"0","left":"0","bottom":"4","right":"2","use_right":"0","use_bottom":"0","apply_to_options":"0"},
    {"id":"44","attribute_id":"2","option_id":"118","design_attribute_id":"4","design_option_id":"131","width":"10","height":"10",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"}],
"5":[{"id":"326","attribute_id":"5","option_id":null,"design_attribute_id":"4","design_option_id":"154","width":"5","height":"5",
            "repeat":"0","top":"0","left":"0","bottom":"0","right":"0","use_right":"0","use_bottom":"0","apply_to_options":"0"}]
};

我需要匹配正确的价值观组合。这是我目前使用的功能:

function drawOption(attid, optid) {
    var attlayout = layout[attid];
    $.each(attlayout, function(k, v) {
        // d_opt_id and d_opt_id are global scoped variable set elsewhere
        if (v.design_attribute_id == d_att_id
            && v.design_option_id == d_opt_id  
            && v.attribute_id == attid 
            && ((v.apply_to_options == 1 || (v.option_id === optid)))) {
                // Do stuff here
        }
    });
}

问题是,我可能会迭代10-15个布局(唯一的attid),任何给定的布局(attid)都可能有多达50种可能性,这意味着这个循环正在大量运行。

考虑到必须匹配的多个标准,AJAX调用会更好地工作吗?(这个JSON是通过PHP动态创建的,所以我可以制作一个PHP函数,它可能会更有效地做到这一点),还是我完全错过了如何在JSON对象中查找项的内容?

一如既往,欢迎任何关于改进代码的建议!

编辑:
我很抱歉没有说清楚,但这个问题的目的是找到一种提高性能的方法。这个页面有很多javascript,我知道这个位置的性能比它可能的要低。

首先,你应该衡量,只有在真正存在性能问题时才采取行动。你需要精确的数字,比如200毫秒或80%的时间都花在那里。"它跑了很多"并不意味着什么。浏览器可以很快循环。

正如其他人所提到的,您可以改进常量因子,比如使用原生for循环而不是jQuery.each。在这种情况下,查询全局变量对您没有太大帮助。

如果你真的想提高效率,你应该找到一个比O(n)更好的算法。假设您只使用这些数据来查找与特定标准匹配的元素,则可以使用JS对象作为散列来获得O(1)性能。

仅举一个具体案例:

var layout = {
  "2": { "2,,4,131,10,0": ["40", "93"], "2,115,4,131,0": ["41"] },
  "4": { ... },
  ...
};

在php中生成这个输出非常容易,然后只需使用查找来查找与特定条件匹配的id。

IMHO,一个简单的哈希图索引可能效果最好。这确实需要您提前对数据进行循环,但索引可以很容易地附加和缓存。

一旦生成了索引,对于查找,它应该是O(1),并且将处理每个键的多个条目。

    var layout = {
    "2":[[...], ...],
    "5":[[...], ...]
    };
    var index = {};
    function makeKey(data) {
        return data.join('_');
    }
    for(var l in layout) {
        var cur = layout[l];
        for(var i in cur) {
            var item = cur[i];
            var key = makeKey([item.p1, item.p2, ...]);
            index[key] = index[key] || [];
            index[key].push(item);
        }
    }
    function find(attid, optid) {
        var key = makeKey([attid, optid, 1, d_att_id, ...]);
        return index[key]; //this is an array of results
    }

我的第一个建议是,如果你想挤出每一点性能,就停止使用$.each。jQuery.each比传统的循环做得更多。在浏览器的调试器(即Safari的/Chrome的web开发工具)运行的情况下,看看这个jsFiddle,然后逐步执行,直到jQuery完全返回执行。

作为参考,小提琴的代码是:

var myArr = [{"foo":"bar"},{"answerswer":42}];
debugger;
$.each(myArr, function(k, v) {
    console.log(k + ': ');
    console.dir(v);
});​

现在运行第二个版本:

var myArr = [{"foo":"bar"},{"answerswer":42}],
    i, j;
debugger;
for (i = 0, j = myArr.length; i < j; i += 1) {
    console.log('myArr[' + i + ']: ');
    console.dir(myArr[i]);
}​

请注意,在第二个版本中执行的操作要少得多。所以这是一个非常小的性能提升。

其次,尽可能多地消除本地范围之外的查找。即使缓存对全局变量(boo!)的引用,也可以节省大量时间,因为这个循环可能会执行数百次。所以不是:

function foo(a, b) {
    if (a === globalA && b === globalB) {
        // Do stuff
    }
}

你会做:

function foo(a, b) {
    var gA = globalA,
        gB = globalB;
    if (a === gA && b === gB) {
        // Do stuff
    }
}

至于基于对象成员的条件配对,我看不出还有什么可以改进的。您正在检查的对象属性是顶级的,并且您正在查看每个对象的本地实例(因此范围链查找很短)。

在不知道这应该如何运作的情况下,这些是我能提出的最好的建议。然而,我可以猜测,您从更简单的JSON数据开始的想法将是一个很大的改进。如果您知道布局及其约束是什么,那么向服务器请求特定的详细信息就意味着您不必检查那么多条件。您可以简单地向服务器询问您实际需要实现的细节,并在这些细节中循环(以更新DOM或其他什么)。

我看到您正在通过5个字段:v.design_attribute_id、v.design_option_id、v.attribute_id、v.apply_to_options、v.option_id。

您可以在对象中添加一个称为"key"的额外字段,该字段是这些字段中值的组合。

这里有一个的例子

       {
            "key": "4_131_2_0_0" //i picked 0 to represent null, but you can pick any number                                    
            "id": "40",
            "attribute_id": "2",
            "option_id": null,
            "design_attribute_id": "4",
            "design_option_id": "131",
            "width": "10",
            "height": "10",
            "repeat": "0",
            "top": "0",
            "left": "0",
            "bottom": "0",
            "right": "0",
            "use_right": "0",
            "use_bottom": "0",
            "apply_to_options": "0"
        }

但请注意,必须规范化每个值的长度。这意味着,如果一个对象optionId为1,另一个对象optionsID为566,则必须表示密钥字符串中的第一个optionId为001。

使用此字段,您可以在将数组返回到客户端之前对服务器端的数组进行排序。

然后,您可以使用二进制搜索来查找客户端上的值。

使用位于此处的二进制搜索实现http://www.nczonline.net/blog/2009/09/01/computer-science-in-javascript-binary-search/

你的搜索功能看起来像

function drawOption(attid, optid) {
    var attlayout = layout[attid];
    var needle = d_att_id + "_" + d_opt_id + "_" + attid + "_" + optid; //remember to normalize  length if you have to
    var idx = binarySearch(attlayout,needle);
    var item;
    if(idx !== -1){
        item = attlayout[idx];
        //do something 
    }
}

您可以尝试使用这种复合密钥思想的另一种方法是让服务器返回由映射的一个大对象中的布局对象attid、v.design_attribute_id、v.design_option_、v.attribute_id、v.apply_to_options、v.option_id

然后你可以在O(1)时间内查到。它看起来有点像

 function drawOption(attid, optid) {
    var needle = attid + "_" + d_att_id + "_" + d_opt_id + "_" + attid + "_" + optid; //remember to normalize  length if you have to
    var item = layout[needle];
    if(typeof item !== "undefined"){
        //do something 
    }
}

在尝试改进代码时,最好使用firebug评测来检查哪些函数需要花费时间。您可以点击firebug控制台面板中的配置文件按钮进行配置文件,然后运行您的代码,或者在代码中使用firebug的配置文件命令

从您给出的代码中,只能给出一些改进点。

  1. $.each与本机循环解决方案相比速度较慢。为了最好循环解决方案,查看JsPref测试
  2. 最好将JSON更改为使用数组而不是对象文字。据说检索值的速度更快

我以前有过这样的问题的经验,我的js对象数组由8000条记录和更多记录组成。我的经验不是关于性能,而是关于代码的可读性、可维护性和可扩展性。

因此,我在两年前开发了一个JS对象查询库:JSOQLhttp://code.google.com/p/jsoql/

它的工作原理与SQL类似,允许您使用与SQL类似的语法查询对象的js数组。

示例用法是这样的,我真的记不清了,但你可以在下载选项卡中下载示例用法

new JSQOL().Select([field1, field2, field3 ...]).From({ ... }) .Where(fn) .Offset(int) .Limit(int) .Get();

注:{…}是您的对象数组,或者它本身就是一个对象。

希望它能有所帮助,如果你需要更多信息,可以给我发信息。

它不会在任何地方都起作用,但你的问题听起来像是可以用网络工作者解决的

如果你没有网络工作者,我会考虑的另一件事是尽量不阻止ui过长。如果你能把它分成大约40ms的比特,然后在几毫秒后的下一次chunck中设置Timeout,用户将获得更愉快的体验。这需要一点技巧,但当某个时间超过50到100ms时,用户会开始注意到一些东西。

你考虑过使用jQuery grep函数吗?

jQuery grep

并以json对象数组上的jquery grep为例。

这里有一种技术可能会以使用更多内存为代价获得更好的性能。

我将让我的代码示例变得简单,只是为了说明这个概念。

首先,您需要将JSON数据预处理到一些充当索引的附加数组中。以下是预处理后最终阵列的样子示例:

var layouts_by_attribute = {
    // attribute_id => array(layouts)
    2: [40, 41, 44],
    5: [326]
};
var layouts_by_design_attribute_id = {
    // design_attribute_id => array(layouts)
    4: [40, 41, 44, 326]
};

按属性查找布局现在非常快速:

function findByAttribute(attribute_id) {
    return layouts = layouts_by_attribute[attribute_id];
}
function findByDesignAttribute(design_attribute_id) {
    return layouts = layouts_by_design_attribute[design_attribute_id];
}