我有一个(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的某些变体,或类似的东西?
答案 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("+")+";");
我还没有尝试将完整的循环移动到生成的代码中,这可以避免一些函数调用,但我不认为这是必要的。
答案 2 :(得分:1)
出于某种原因,我刚刚回忆起这个问题...而我刚刚想出了一个比使用eval
更脏的解决方案,但这会带来巨大的速度提升。它的缺点是代码与使用eval
时的维护相似。
基本思路是:在接收属性名称时,生成功能代码以解析JavaScript中的以下数据,并将其添加到<script>
标记中<head>
如果性能对您来说至关重要,它肯定会对您有所帮助......这是您的microbenchmak的修改版本,证明了它:http://jsfiddle.net/N6CrK/17/
关于代码的一些评论......
两个函数createWithGeneratedFunction
和getTotalWithGeneratedFunction
只是生产代码可以使用的包装函数。他们所做的就是确保设置了生成函数的<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
参数。