当你不知道属性是什么时,有没有办法在javascript中干净地使用隐藏类?

时间:2014-05-21 09:59:05

标签: javascript json google-chrome optimization

我有一个(GIS)项目,它向客户显示大量客户数据(数千条记录)。在必要/可能/需要的地方,我们使用服务器端分页/过滤/数据操作,但在某些情况下,以JSON格式将数据发送到客户端并让其浏览器进行过滤是最有效的。

数据量很大,因此我们将其格式化以节省带宽和解析时间 - 而不是单个对象,我们首先发送包含属性名称的结构,然后发送单个平面数组中的值。在客户端上,我们在其他处理发生之前将其重建为更传统的json对象。例如:

{attrNames:["foo","bar"],values:[1,2,3,4,...]) -> [{foo:1,bar:2},{foo:3,bar:4},...]

执行此操作的代码看起来有点像这样:

function toObjectArray(attrNames, values){
    var ret = [];
    var index = 0;
    var numAttrNames = attrNames.length;
    var numValues = values.length;
    while(index < numValues){
        var obj = {};
        for(var a = 0; a < numAttrNames; a++){
            obj[attrNames[a]] = values[index++];
        }
        ret.push(obj);
    }
    return ret;
}

鉴于属性可能会根据客户数据而改变,有没有办法利用V8等现代javascript引擎中的隐藏类进行此转换?我已经做了一些类似于我们的用例(http://jsfiddle.net/N6CrK/1/)的微基准测试,其中使用json使得使用隐藏类比如上构建对象快几个数量级。我可以使用“eval”创建对象来获得一些提升,但这感觉很难看(这在js小提琴中得到了证明)。有没有更好的办法?也许使用Object.create的某些变体,或类似的东西?

3 个答案:

答案 0 :(得分:2)

你的意思是这样的吗?

function toHiddenObjectArray(attrNames, attrValues){

    var numAttrNames = attrNames.length,
        numValues = attrValues.length;

    function Data( values ) {
        for(var v = 0; v < numAttrNames; v++) {
            this[attrNames[v]] = values[v];
        }
    }

    var ret=[];
    for( var i=0; i<numValues ; i+=numAttrNames ) {
        ret.push( new Data( attrValues.slice(i,i+numAttrNames) ) );
    }

    return ret;
}

你可以在这里检查我们的小提琴:http://jsfiddle.net/B2Bfs/(带有一些比较代码)。它应该使用相同的&#34; Hidden Class&#34; (即Data)。不知道它有多快!

但是,如果你真的想让你的代码无阻塞,为什么不加载页面,然后通过AJAX请求数据,然后在得到响应时运行所有代码。

答案 1 :(得分:1)

  

我可以使用“eval”创建对象来获得一些提升,但这感觉很难看

使用Function构造函数的方式不那么难看。此外,可以通过立即将值分配给属性来进行进一步的优化,而不是使用null初始化它们,然后再次遍历attrs数组,就像adHoc那样。你只需将你在响应中获得的每一行(数组?字符串?byte-whatever?)作为参数传递给工厂。

此外,我已将factory函数的创建移出create函数,因此只会实例化一个函数(并在对其进行足够调用后进行优化)。
测试循环中花费的时间相当于getTotal,因此我以类似的方式对其进行了优化。在测试优化解决方案时不使用getTotalAdHoc可以大大缩短测量时间(您也可以使用getTotalOptimum进行测试)。

var factory = new Function("arr", "return{"+attrs.map(function(n, i){
    return n+":arr["+i+"]";
}).join(",")+"};");
var getSum = new Function("o","return "+attrs.map(function(n, i){
    return "o."+n;
}).join("+")+";");

updated jsfiddle

我还没有尝试将完整的循环移动到生成的代码中,这可以避免一些函数调用,但我不认为这是必要的。

答案 2 :(得分:1)

出于某种原因,我刚刚回忆起这个问题...而我刚刚想出了一个比使用eval更脏的解决方案,但这会带来巨大的速度提升。它的缺点是代码与使用eval时的维护相似。

基本思路是:在接收属性名称时,生成功能代码以解析JavaScript中的以下数据,并将其添加到<script>标记中<head>

是的,那不是很脏吗? : - )

如果性能对您来说至关重要,它肯定会对您有所帮助......这是您的microbenchmak的修改版本,证明了它:http://jsfiddle.net/N6CrK/17/


关于代码的一些评论......

两个函数createWithGeneratedFunctiongetTotalWithGeneratedFunction只是生产代码可以使用的包装函数。他们所做的就是确保设置了生成函数的<script>,然后调用它。

function createWithGeneratedFunction(numValues){
    makeSureScriptsAreSetUp()
    return createWithGeneratedFunctionAdded(numValues);
}

function getTotalWithGeneratedFunction(objs){
    makeSureScriptsAreSetUp()
    return getTotalWithGeneratedFunctionAdded(objs);
}

实际的主力是makeSureScriptsAreSetUp及其创建的功能。我将逐行完成它:

function makeSureScriptsAreSetUp() {
    if(scriptIsSetUp)
        return;

如果已经设置了所需的<script>标记,则此函数将直接返回,因为它不再有任何关系。

    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    var theFunctions = "";

这准备创建所需的功能。 theFunctions变量将填入将放入<script>代码内容的代码。

    theFunctions = 
        "function createWithGeneratedFunctionAdded(numValues) {" +
        "    var ret = [];" +
        "    var value = 0;" +
        "    for(var i = numValues; i-- > 0;) {" +
        "        ret.push({";
    for(var attr in attrs) {
        theFunctions +=
        "            " + attrs[attr] + ": value++,";
    }
    theFunctions +=
        "        });" +
        "    }" +
        "    return ret;" +
        "}" +
        "";

这样就完成了解析函数的代码。显然它只是解析&#34;此微基准测试中的数字0到numValues。但是用value++之类的内容替换TheObjectThatTheClientSentMe.values[value++]会让您非常接近您在问题中列出的内容。 (显然,将value重命名为index会非常有意义。)

    theFunctions +=
        "function getTotalWithGeneratedFunctionAdded(objs) {" +
        "    var ret = 0;" +
        "    for(var i = objs.length; i-- > 0;) {" +
        "        var obj = objs[i];" +
        "        ret += 0";
    for(var attr in attrs) {
        theFunctions +=
        "            + obj." + attrs[attr];
    }
    theFunctions +=
        "        ;" +
        "    }" +
        "    return ret;" +
        "}";

这样就完成了处理功能的代码。由于您似乎需要多个处理函数,尤其是这些代码在编写和维护时可能会变得有些难看。

    script.text = theFunctions;
    head.appendChild(script);
    scriptIsSetUp = true;
}

最后,我们只需将<script>标记内容设置为我们刚刚创建的代码。然后将该标记添加到<head>,Chrome的隐藏类魔法将会发生,并且会使代码非常快。


关于可扩展性:如果必须在同一页面上从服务器查询不同的属性/值集,您可能希望为每个解析/处理方法设置唯一的名称。例如,如果您第一次收到attrs = ["foo","bar"]和下一个attrs = ["foo","bar","baz"],则可以将下划线连接的属性名称数组连接到生成的函数名称。

例如,您可以将createWithGeneratedFunctionAdded用于第一个属性/值集,而将createWithGeneratedFunctionAdded_foo_bar用于第二个属性/值集,而不是使用createWithGeneratedFunctionAdded_foo_bar_baz。然后可以将attr参数添加到包装函数中,该函数将用于为eval生成正确的代码行(是的,这里邪恶eval将返回)以触发正确的生成函数。显然,attr函数也需要makeSureScriptsAreSetUp参数。