使用 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);
       
    
    ...

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

您还可以根据 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(''));
        
    
    

    您添加到ArrayinArray 方法必须扫描整个 数组,然后才能确定某个元素不在数组中。

    理想情况下,此过滤将在发送端完成。 这样最好。但是,也许您正在使用无法从源头过滤的第三方数据,所以...

    有一种方法可以做得更好。如果顺序很重要,您仍然可以使用数组来存储您的对象。然后您可以使用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_idalert_gen_date_time 上进行了比较,因此为数组中的每个项目生成一次密钥是正确的。

    如何生成它们的密钥取决于row.device_idrow.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 上填充数据的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

在node.js中的Webscraper,JS修改了DOM

使用 Node NOT io.js 在 Mocha 测试中使用 jQuery

nodejs概要

node环境配置安装(nvm)

JQuery中的DOM操作

使用browserify时如何在AngularJS中包含jQuery?