【问题标题】:Best way to populate data on DOM using jQuery and Node使用 jQuery 和 Node 在 DOM 上填充数据的最佳方法
【发布时间】: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; };

标签: javascript jquery node.js dom socket.io


【解决方案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】:

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

      1. 您通过多次操作文档来更新您的表格。一次操作更新 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(''));
            }
        }
        
      2. 您添加到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 步骤,只需直接遍历您的响应数据并在同一步骤中更新表。

        【讨论】:

          猜你喜欢
          • 2020-01-08
          • 2012-04-04
          • 1970-01-01
          • 1970-01-01
          • 2010-10-05
          • 2015-06-14
          • 2012-04-17
          • 1970-01-01
          • 2011-11-29
          相关资源
          最近更新 更多