使用jQuery和Node填充DOM数据的最佳方法

我使用Node的Socket.io将数据从服务器推送到客户端浏览器。 在客户端上,我使用jQuery来填充DOM中的返回行。

在我用来填充由Socket.io返回的数据的代码片段下面。

 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); } }; 

处理从套接字接收的数据:

 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) { }); } 

其中一个function如下 。 其余部分是在DOM的其他部分填充数据的类似function。

 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秒接收到如此多的推送消息,浏览器无法处理这么多的数据并挂断。

有没有更好的方法来做到这一点?

我在代码中看到以下问题:

  1. 通过多次操作文档来更新表格。 在一个操作中更新DOM更好。 有一篇关于这个的Google文章 。 所以像这样:

     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('')); } } 
  2. 添加到ArrayinArray方法在确定元素不在数组中之前,必须扫描整个数组。

    理想情况下,这个过滤将在发送端完成。 这将是最好的。 但是,也许你使用的第三方数据不能在源头过滤,所以…

    有一种方法可以做得更好。 如果顺序是重要的,你仍然可以使用数组来存储你的对象。 然后,您可以使用Object.create(null)创build的对象作为关联数组来logging您是否看到过某个对象。 所以像这样:

     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); } } 

    我不止一次地使用了这个方法,但是上面的代码没有经过testing。 错别字或智力可能潜伏在那里。 在这个例子中,对于所有types的警报,密钥被计算一次。 看看你的代码,看起来好像所有的警报都在device_idalert_gen_date_time上进行了比较,所以为数组中的每个项目生成一次密钥是正确的。

    如何生成密钥取决于row.device_idrow.alert_gen_date_time的可能值。 您可能需要一个分隔符来避免可能的含糊之处。 例如,在我工作的一个案例中,我不得不把所有的字母都是有效的值合并起来。 所以没有分隔符就没有办法将"ab" + "cd" "a" + "bcd""abc" + "d" "a" + "bcd"区分开来。 我使用了一个分隔符,它们不能出现在构造键的值上:所以"ab@cd""a@bcd""abc@d"

    也可以使用关联数组的关联数组来做检查,但是我不相信会产生重大的速度改进。 这肯定会使代码更复杂。

我可以想到其他可以改善速度的变化,但我认为他们不能提供实质性的收益。

不知道你的应用程序的细节,我会假设你需要在同一个界面的所有数据,而不是能够分裂之间,我不知道,标签或网页或你有什么。

如果你发现数据太多了,那么你的消息可能不会如此迅速地循环,以至于每隔10秒就会有一批新的数据……你可能已经有更长时间的信息了,每10秒就会被删除和重新创build秒。

如果您改变它,以便每个更新只包含更改 ,例如每个列表的新总数,然后添加行并删除行,则可能会显着提高性能,然后可以每次更新一次,我不知道,5分钟(或15分钟,或60分钟,无论你觉得你的应用程序可以容忍),以确保你不会失去同步。

这几乎是video压缩中使用的方法之一,只是logging帧间的变化,然后每隔一段时间使用一个关键帧进行纠错。

如果你这样做,你可以消除你的pushifnotexists步骤,直接循环直接通过你的响应数据,并在同一步骤中更新表。

这还没有完全完成(dom更新中缺less一些重复的input)我已经消除了对数组原型的需求,并为列表中的项目allready做了lookupCache

updatedom方法是您所使用的方法的改变版本,您必须重新编写您使用的其他方法。

在代码的大部分部分我增加了一些什么正在发生和为什么。 dom更新部分仍然可以做得更快,但是我认为只有更新dom部分并且更新dom才是足够的,同时仍然使得代码可以理解并且保持相对小巧:D,它相当多。 ..

你提供的代码中有一个令人困惑的东西。 Initailly你声明的列表(var OverSpeedAlerts等),但然后在处理方法中接收到的数据重新设置它们为空数组。 是否:1.你打算保留以前的dom元素,但试图优化2.你的意图,用新的数据replace所有的东西3.只添加新的数据到现有的数据(这是这个代码的作用)

无论是哪种情况,代码中的注释都会解释您在哪里以及如何优化现有代码。 (大部分将是单一的更新寿),但过滤将有助于特别是在大名单。

 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); } 

昂贵的操作是DOM操作。

您可以一次性改进DOM提供您的DOMstring,而无需多次追加。 您selectDOM节点的方式也非常重要。

一个例子:

 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('')); } } 

在此解决scheme中只有两个DOM访问,而不是第一个实现中的N + 4(N是OverSpeedAlerts数组的长度。

你也可以改进pushIfNotExist方法,这在我看来也是非常耗时的。 我build议除了你的数组之外,还要使用一个散列,并用作"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); } } ... 

有了这个,就不再需要检查警报是否已经在数组中,因为语言会为您testing。 不需要inArraypushIfNotexist了!

您还可以通过dynamicselect您的散列来分解代码,具体取决于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('')); } } 

快乐的编码!