使用 jQuery 和 Node 在 DOM 上填充数据的最佳方法
Posted
技术标签:
【中文标题】使用 jQuery 和 Node 在 DOM 上填充数据的最佳方法【英文标题】:Best way to populate data on DOM using jQuery and Node 【发布时间】:2014-02-13 18:47:41 【问题描述】:我正在使用 Node 的 Socket.io
将数据从服务器推送到客户端浏览器。
在客户端,我使用 jQuery 在 DOM 中填充返回的行。
在我用来填充 Socket.io 返回的数据的代码 sn-p 下方。
var OverSpeedAlerts = [];
var TripCancellation = [];
var GeofenceInOutAlerts = [];
var ScheduleOverstay = [];
var UnSchduledOverstay = [];
var SkippedBusStop = [];
var TripDelayAlert = [];
var SkippedUnplannedAlert = [];
var DelayStartEndAlert = [];
var RouteDeviatedAlert = [];
var MultipleBusEntry = [];
声明原型:
Array.prototype.inArray = function (comparer)
for (var i = 0; i < this.length; i++)
if (comparer(this[i])) return true;
return false;
;
// adds an element to the array if it does not already exist using a comparer
// function
Array.prototype.pushIfNotExist = function (element, comparer)
if (!this.inArray(comparer))
this.unshift(element);
;
处理从socket接收到的数据:
if (typeof io !== 'undefined')
var pushServer = io.connect('http://SomeIP:3000');
pushServer.on('entrance', function (data)
var rows = data.message;
var NumberOfRows = rows.length;
$('#notifications').html(NumberOfRows);
// console.log(rows);
OverSpeedAlerts = [];
TripCancellation = [];
GeofenceInOutAlerts = [];
ScheduleOverstay = [];
UnSchduledOverstay = [];
SkippedBusStop = [];
TripDelayAlert = [];
SkippedUnplannedAlert = [];
DelayStartEndAlert = [];
RouteDeviatedAlert = [];
var MultipleBusEntry = [];
for (var i = 0; i < rows.length; i++)
if (rows[i].alert_type == 'overspeed')
OverSpeedAlerts.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'trip_cancellation')
TripCancellation.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Geofence-In' || rows[i].alert_type === 'Geofence-Out')
GeofenceInOutAlerts.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Scheduled-Overstay')
ScheduleOverstay.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Unscheduled-Overstay')
UnSchduledOverstay.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Skipped Unplanned' || rows[i].alert_type == 'Skipped-Busstop')
SkippedBusStop.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Delay Start' || rows[i].alert_type == 'Delay End')
TripDelayAlert.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Route Deviated')
RouteDeviatedAlert.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
else if (rows[i].alert_type == 'Multiple Bus Entry')
MultipleBusEntry.pushIfNotExist(rows[i], function (e)
return e.device_id === rows[i].device_id && e.alert_gen_date_time === rows[i].alert_gen_date_time;
);
CreateOverSpeedGrid();
CreateTripCancellation();
CreateGeofenceGrid();
CreateScheduleOverstayGrid();
CreateUnSchduledOverstayGrid();
CreateTripDelayGrid();
CreateSkippedBusStop();
CreateRouteDeviationAlert();
CreateMultipleBusEntry();
);
pushServer.on('end', function (socket)
);
上述功能之一如下。 Rest 是在 DOM 的其他部分填充数据的类似函数。
function CreateOverSpeedGrid()
$('#tabs ul').find('a:contains("Overspeed Alerts")').html('OverSpeed Alerts(' + OverSpeedAlerts.length + ')');
if (OverSpeedAlerts.length != 0)
$('#notifyOverspeed table').html('');
$('#notifyOverspeed table').append('<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'); //new Date([UNIX Timestamp] * 1000);
for (var i = 0; i < OverSpeedAlerts.length; i++)
$('#notifyOverspeed table').append('<tr> <td>' + OverSpeedAlerts[i].depot_name + '</td> <td>' + OverSpeedAlerts[i].route_name + '</td> <td>' + OverSpeedAlerts[i].schedule_no + '</td> <td>' + OverSpeedAlerts[i].trip_number + ' </td> <td>' + OverSpeedAlerts[i].trip_direction + '</td><td> ' + OverSpeedAlerts[i].alert_sub + ' </td> <td title="' + ConvertToValidTooltip(OverSpeedAlerts[i].alert_msg) + '" style="text-decoration:underline;cursor:pointer;"> ' + "Place mouse pointer to view message" + ' </td> <td>' + new Date(OverSpeedAlerts[i].alert_gen_date_time * 1000) + ' </td> </tr>');
上面的代码工作正常。但问题是,由于每 10 秒从套接字接收到如此多的推送消息,浏览器无法处理如此多的数据并挂起。
有没有更好的办法??
【问题讨论】:
你为什么不过滤你发送的数据,这样你就不会收到这么多? Array.prototype.inArray = function (item) return this.indexOf(item) !== -1; ; 【参考方案1】:昂贵的操作是 DOM 操作。
您可以通过一次性提供 DOM 字符串来改进它们,而无需多次调用 append。 选择 DOM 节点的方式也很重要。
一个例子:
function CreateOverSpeedGrid()
// Use an ID if possible. It's the fastest way to select DOM nodes. After you can use css classes, or dom custom attributes (data-***). Using contains is very slow.
$('#overspeed_alerts').html('OverSpeed Alerts(' + OverSpeedAlerts.length + ')');
if (OverSpeedAlerts.length != 0)
// Fast HTML string: using an array is better than concatenating multiple strings.
var content = ['<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'];
for (var i = 0; i < OverSpeedAlerts.length; i++)
// optimize by accessing your item in array only once.
var alert = OverSpeedAlerts[i];
content.push('<tr> <td>',
alert.depot_name,
'</td> <td>',
alert.route_name,
'</td> <td>',
alert.schedule_no,
'</td> <td>',
alert.trip_number,
'</td> <td>,
alert.trip_direction,
'</td><td> ',
alert.alert_sub,
'</td><td title="',
ConvertToValidTooltip(alert.alert_msg),
'" style="text-decoration:underline;cursor:pointer;">Place mouse pointer to view message </td><td>',
new Date(alert.alert_gen_date_time * 1000),
'</td> </tr>'];
// Do not select multplie time your table node: it better to store it in a variable.
$('#notifyOverspeed table').html(content.join(''));
此解决方案中只有两个 DOM 访问,而不是您的第一个实现中的 N+4(N 是 OverSpeedAlerts 数组的长度。
您还可以改进pushIfNotExist
方法,我认为这也非常耗时。
我建议在您的数组之外使用哈希,并将其用作 "alert_gen_date_time"
和 "device_id"
之间的连接的哈希键:
// use hash in addition to your array
OverSpeedAlerts = [];
var existingOverSpeedAlerts = ;
for (var i = 0; i < rows.length; i++)
// optimize access to row
var row = rows[i];
if (row.alert_type == 'overspeed')
// join device_id and alert_gen_date_time
var key = row.device_id + '_' + row.alert_gen_date_time;
// existence check ! very efficient.
if (!(key in existingOverSpeedAlerts ))
// does not exist yet: adds it, and update your hash.
existingOverSpeedAlerts[key] = row;
OverSpeedAlerts.push(row);
...
有了这个,不再需要检查警报是否已经在数组中,因为语言会为您测试它。不再需要inArray
和pushIfNotexist
了!
您还可以根据 alert_type 动态选择哈希值来分解代码。它不会让你的代码变得更快,但只会更易读(这也很有价值!)
类似:
if (typeof io !== 'undefined')
var pushServer = io.connect('http://SomeIP:3000');
pushServer.on('entrance', function (data)
var rows = data.message;
var NumberOfRows = rows.length;
$('#notifications').html(NumberOfRows);
var alerts = ;
var keys = ;
// reuse NumberOfRows here
for (var i = 0; i < NumberOfRows ; i++)
// optimize access to row
var row = rows[i];
var type = row.alert_type;
// add here specificities relative type aggregation
if (type === 'Geofence-In' || type === 'Geofence-Out')
type = 'Geofence-Inout';
else if (type === 'Skipped Unplanned' || type === 'Skipped-Busstop')
type = 'SkippedBusStop';
else if (type === 'Delay Start' || type === 'Delay End')
type = 'TripDelayAlert';
// first alert of a kind !
if (!(type in alerts))
// init your alert specific array and key hash
alerts[row.alert_type] = [];
keys[row.alert_type] = ;
// join device_id and alert_gen_date_time
var key = row.device_id + '_' + row.alert_gen_date_time;
// existence check ! very efficient.
if (!(key in keys[row.alert_type]))
// does not exist yet: adds it, and update your hash.
keys[row.alert_type][key] = row;
alerts[row.alert_type].push(row);
// And now displayal
DisplayAlerts(alerts)
...
function DisplayAlerts(alerts)
for (var key in alerts)
var array = alerts[key];
// My hypothesis is that rendering of a given alert type is inside a node with the kind as css ID.
$('#'+key+' .caption').html('Alerts(' + array.length + ')');
if (array.length != 0)
// Fast HTML string: using an array is better than concatenating multiple strings.
var content = ['<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'];
for (var i = 0; i < OverSpeedAlerts.length; i++)
// optimize by accessing your item in array only once.
var alert = array[i];
content.push('<tr> <td>',
alert.depot_name,
'</td> <td>',
alert.route_name,
'</td> <td>',
alert.schedule_no,
'</td> <td>',
alert.trip_number,
'</td> <td>,
alert.trip_direction,
'</td><td> ',
alert.alert_sub,
'</td><td title="',
ConvertToValidTooltip(alert.alert_msg),
'" style="text-decoration:underline;cursor:pointer;">Place mouse pointer to view message </td><td>',
new Date(alert.alert_gen_date_time * 1000),
'</td> </tr>'];
// Do not select multplie time your table node: it better to store it in a variable.
$('#' + key + ' table').html(content.join(''));
编码愉快!
【讨论】:
【参考方案2】:这还没有完全完成(在 dom 更新时缺少一些重复的输入) 我已经消除了您对数组原型的需求,并为列表中的所有项目创建了一个 lookupCache
updatedom 方法是您拥有的方法的修改版本,您必须重新编写您使用的其他方法。
在代码的大多数部分,我添加了一些关于发生了什么以及原因的解释。 dom 更新部分仍然可以更快,但我希望只渲染新部分并更新整个 dom 就足够了,同时仍然使代码易于理解并保持(相对)小:D,它相当多。 ..
您提供的代码中有一件令人困惑的事情。最初您声明列表(var OverSpeedAlerts 等),但随后在接收到的数据的处理方法中将它们重新设置为空数组。是不是: 1.您打算保留以前的dom元素但尝试优化 2.您打算用新数据替换所有内容 3. 只将新数据添加到现有数据中(这就是这段代码所做的)
无论哪种情况,代码中的 cmets 都会说明如果您愿意,可以在何处以及如何优化现有代码。 (其中大部分将是单个 dom 更新)但过滤将特别有助于大列表。
var io /*some lib*/, pushServer, alertTypes, alterTypesMapping, $notifications, lookupCache;
//i use the -Length fields to store the "previous" length, so i know if the dom needs updating at all
// and what part is new, no need to re-render perfectly valid html
alertTypes =
OverSpeedAlerts: [],
OverSpeedAlertsLength: 0,
TripCancellation: [],
TripCancellationLength: 0,
GeofenceInOutAlerts: [],
GeofenceInOutAlertsLength: 0,
ScheduleOverstay: [],
ScheduleOverstayLength: 0,
UnSchduledOverstay: [], //scheduled? sorry ide with spelling check
UnSchduledOverstayLength: 0,
SkippedBusStop: [],
SkippedBusStopLength: 0,
TripDelayAlert: [],
TripDelayAlertLength: 0,
SkippedUnplannedAlert: [],
SkippedUnplannedAlertLength: 0,
DelayStartEndAlert: [],
DelayStartEndAlertLength: 0,
RouteDeviatedAlert: [],
RouteDeviatedAlertLength: 0
;
//mapping from types to their lists (some types map to the same list)
alterTypesMapping =
'overspeed': 'OverSpeedAlerts',
'trip_cancellation': 'TripCancellation',
'Geofence-In': 'GeofenceInOutAlerts',
'Geofence-Out': 'GeofenceInOutAlerts',
'Scheduled-Overstay': 'ScheduleOverstay',
'Unscheduled-Overstay': 'UnSchduledOverstay',
'Skipped Unplanned': 'SkippedBusStop',
'Delay Start': 'TripDelayAlert',
'Delay End': 'TripDelayAlert',
'Route Deviated': 'RouteDeviatedAlert',
'Multiple Bus Entry': 'MultipleBusEntry'
;
//cache dom lookup
$notifications = $('#notifications');
//we serialize the relevant message parts into an unique id, used for de-duping
//<id> => <alert_type>|<device_id>|<alert_gen_date_time>
lookupCache = ;
function process_data (data)
var i, l, rows, id;
rows = data.message;
l = rows.length;
//update dom row count
$notification.html(l);
for (i=0; i<l; ++i) //caching length in l, ++i is faster than i++
id = rows[i].alert_type + '|' + rows[i].device_id + '|' + rows[i].alert_gen_date_time;
if (!lookupCache[id])
lookupCache[id] = 1; //set it to truthy so next time around its there
//not in cache push it to the relevant list
//you used unshift here, that's essentially moving all other elements in the list one spot and
//adding the new one at index 0 (speed O(n) eg increases with more elements in the list)
//instead you can push the new element to the end, (speed O(1) constant speed)
// and when iterating the list doing that in reverse
alertTypes[alterTypesMapping[rows[i].alert_type]].push(rows[i]);
updateDom();
function updateDom ()
var keys, i, l, html;
//here we check all length fields in the alertTypes and see if the actual list length
//is greater than their -Length
//if so we update the relevant dom
keys = Object.keys(alertTypes);
for (i=0, l=keys.length; i<l; ++i)
//skip the -Length keys
if (keys[i].match('Length'))
continue;
//please add a data-type="<type>" to the a's, so much better to lookup by attribute instead of text matching content
$('#tabs ul a[data-type="' + keys[i] + '"]').html(keys[i] + '(' + alertTypes[keys[i] + 'Length'] + ')');
//since well only update the dom, i presume at this point there is a dom with the table with headers
//(use thead and th for this please)
//(and use tbody for a table's body)
//now we iterate the new elements (from list.length back to key-Length)
//j starts at the length of the list, and ends at m, the previous length
//counting backwards
html = [];
for (j=alertTypes[keys[i]].length, m=alertTypes[keys[i] + 'Length']; j>m; --j)
//array join is almost always faster than string concatenation
//since strings are not mutable in js (eg. you create a new string every +)
html.push([
'<tr>',
'<td>',
alertTypes[keys[i]].depot_name,
'</td>',
'<td>',
alertTypes[keys[i]].route_name,
'</td>',
'<td>',
alertTypes[keys[i]].schedule_no,
'</td>',
'<td>',
alertTypes[keys[i]].trip_number,
'</td>',
'<td>',
alertTypes[keys[i]].trip_direction,
'</td>',
'<td>',
alertTypes[keys[i]].alert_sub,
'</td>',
'<td ',
'title="',
ConvertToValidTooltip(alertTypes[keys[i]].alert_msg),
'" style="text-decoration:underline;cursor:pointer;">Place mouse pointer to view message',
'</td>',
'<td>',
new Date(OverSpeedAlerts[i].alert_gen_date_time * 1000),
'</td>',
'</tr>'
].join(''));
//and finally we update the key-Length so next time well only add what is newer than what we are about to add
alertTypes[kesy[i] + 'Length'] = alertTypes[keys[i]].length;
//get the dom element we have to update
$('#' + keys[i] + ' tbody').prepend(html.join(''));
if (io !== undefined) //no need for typeof when checking undefined, check undefined directly with equality (eg. undefined === undefined)
pushServer = io.connect('http://SomeIP:3000');
pushServer.on('entrance', process_data);
【讨论】:
【参考方案3】:我在这段代码中看到了以下问题:
您通过多次操作文档来更新您的表格。一次操作更新 DOM 对性能更好。关于这个有一个Google article。所以像:
function CreateOverSpeedGrid()
$('#tabs ul').find('a:contains("Overspeed Alerts")').html('OverSpeed Alerts(' + OverSpeedAlerts.length + ')');
if (OverSpeedAlerts.length != 0)
var html = [];
html.push('<tr class="ui-widget-header"> <th> Depot </th> <th> Route </th> <th> Schedule </th> <th> Trip Number </th><th>Trip Direction</th> <th> Alert Summary</th> <th> Alert Details </th> <th> Generated On </th> </tr>'); //new Date([UNIX Timestamp] * 1000);
for (var i = 0; i < OverSpeedAlerts.length; i++)
html.push('<tr> <td>' + OverSpeedAlerts[i].depot_name + '</td> <td>' + OverSpeedAlerts[i].route_name + '</td> <td>' + OverSpeedAlerts[i].schedule_no + '</td> <td>' + OverSpeedAlerts[i].trip_number + ' </td> <td>' + OverSpeedAlerts[i].trip_direction + '</td><td> ' + OverSpeedAlerts[i].alert_sub + ' </td> <td title="' + ConvertToValidTooltip(OverSpeedAlerts[i].alert_msg) + '" style="text-decoration:underline;cursor:pointer;"> ' + "Place mouse pointer to view message" + ' </td> <td>' + new Date(OverSpeedAlerts[i].alert_gen_date_time * 1000) + ' </td> </tr>');
// Change the rows in one operation.
$('#notifyOverspeed table').html(html.join(''));
您添加到Array
的inArray
方法必须扫描整个 数组,然后才能确定某个元素不在数组中。
理想情况下,此过滤将在发送端完成。 这样最好。但是,也许您正在使用无法从源头过滤的第三方数据,所以...
有一种方法可以做得更好。如果顺序很重要,您仍然可以使用数组来存储您的对象。然后您可以使用Object.create(null)
创建的对象作为关联数组only 来记录您是否看到过某个对象。所以像:
var OverSpeedAlerts = [];
var OverSpeedAlertsSeen = Object.create(null);
for (var i = 0; i < rows.length; i++)
var row = rows[i];
var key = row.device_id + row.alert_gen_date_time;
if (row.alert_type == 'overspeed' && !OverSpeedAlertsSeen[key])
OverSpeedAlertsSeen[key] = true;
OverSpeedAlerts.push(row);
我不止一次使用过这种方法,但上面的代码没有经过测试。错别字或大脑可能潜伏在那里。在此示例中,为所有类型的警报计算一次密钥。查看您的代码,您的所有警报似乎都在 device_id
和 alert_gen_date_time
上进行了比较,因此为数组中的每个项目生成一次密钥是正确的。
如何生成它们的密钥取决于row.device_id
和row.alert_gen_date_time
的可能值。您可能需要一个分隔符以避免可能的歧义。例如,在我处理的一个案例中,我必须组合字母表中所有字母都有效的值。因此,如果没有分隔符,就无法区分 "ab" + "cd"
和 "a" + "bcd"
和 "abc" + "d"
。我使用了一个不能出现在值中的分隔符来构建密钥:所以"ab@cd"
、"a@bcd"
、"abc@d"
。
也可以使用关联数组的关联数组进行检查,但我不相信它会显着提高速度。这肯定会使代码更复杂。
我可以想到可以进行其他更改以提高速度,但我认为它们不会带来实质性的收益。
【讨论】:
【参考方案4】:不知道您的应用程序的详细信息,我将假设您需要在同一界面中的所有数据,而不是能够将其拆分为我不知道的选项卡或页面或什么有你。
如果您发现它的数据过多,大概是您的消息不会如此快速地循环,以至于每 10 秒就会有一批全新的数据...您可能有更长寿命的消息坐在那里,被删除并重新-每 10 秒创建一次。
如果您改为更改它以使每个更新仅包含 更改,例如每个列表的新总数,然后要添加的行和要删除的行,您可能会显着提高性能,然后您可以每隔 5 分钟(或 15 分钟或 60 分钟)运行一次完整更新,无论你认为你的应用可以容忍什么)只是为了确保你不会失去同步。
这几乎是视频压缩中使用的一种方法,只是记录每一帧的变化,然后每隔一段时间使用一个关键帧进行纠错。
如果您这样做,您可以消除您的 pushifnotexists
步骤,只需直接遍历您的响应数据并在同一步骤中更新表。
【讨论】:
以上是关于使用 jQuery 和 Node 在 DOM 上填充数据的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章