DataTables对接Ajax和WebSocket双数据源实现表格列实时更新问题
问题分析与优化实现
原代码无法运行的核心原因
- 表格行匹配逻辑错误:
rows({ search: data.s })是全局模糊搜索,会匹配所有列内容,无法精准匹配symbol+pair的组合,而且每次遍历全表性能极差 - 初始化时序问题:WebSocket初始化在AJAX请求外部,可能AJAX还没完成表格初始化,WebSocket推送就已触发,调用
DataTable()会抛出未初始化错误 - 数据更新后未重绘:修改cell数据后没有调用
draw()方法,页面不会渲染最新值 - 重复获取实例性能损耗:每次WebSocket推送都重新获取DataTable实例,产生不必要的性能开销
优化实现方案
核心优化点是提前建立交易对-行索引的映射表,WebSocket推送时直接O(1)查找对应行,不需要每次遍历全表,性能提升非常明显。
完整修改后代码
首先定义全局变量存储表格实例和映射表:
// 全局变量,存储表格实例和交易对到行索引的映射 let tenantsTable = null; let pairRowMap = new Map();
然后修改AJAX回调部分:
abp.ajax({ method: 'POST', url: abp.appPath + 'Portfolio/GetSignalsByPeriod/', data: JSON.stringify(PeriodId), dataType: 'json', contentType: 'application/json', }).done(function (result) { // 初始化映射表:拼接symbol+pair作为key,行索引作为value result.forEach((row, index) => { const fullPair = `${row.symbol}${row.pair}`.toUpperCase(); // 统一大写避免匹配问题 pairRowMap.set(fullPair, index); }); tenantsTable = $('#TenantsTable').DataTable({ searching: true, ordering: true, data: result, columns: [ { data: 'symbol' }, { data: 'pair' }, { data: 'signal' }, { data: 'signalDate', render: function (data) { return moment(new Date(data)).format('DD/MM/YYYY - h:mm:ss'); } }, { data: 'signalPrice' }, { data: 'lastPrice', // 绑定字段方便后续更新 defaultContent: '-' }, { data: 'change', defaultContent: '-' }, { data: 'sltp', defaultContent: '-' } ], buttons: [ { name: 'refresh-usdt', text: 'USDT', action: function (e, dt) { dt.column(1).search("USDT").draw(); } }, { name: 'refresh-btc', text: 'BTC', action: function (e, dt) { dt.column(1).search("BTC").draw(); } }, ] }); // 表格初始化完成后再初始化WebSocket,避免时序问题 initWebSocket(); })
最后是WebSocket部分的实现:
function initWebSocket() { const url = 'wss://stream.binance.com:9443/ws/!ticker@arr'; const ws = new WebSocket(url); ws.onmessage = (event) => { const dataWs = JSON.parse(event.data); // 批量更新所有匹配行,最后统一重绘,减少重绘次数提升性能 dataWs.forEach(ticker => { const fullPair = ticker.s.toUpperCase(); // 映射表中存在对应交易对才处理 if (pairRowMap.has(fullPair)) { const rowIndex = pairRowMap.get(fullPair); const rowData = tenantsTable.row(rowIndex).data(); // 更新三个字段的值,Binance ticker字段:c为最新成交价、P为涨跌幅百分比 rowData.lastPrice = ticker.c; rowData.change = `${ticker.P}%`; // 止盈止损可根据你的业务逻辑计算,以下为示例代码可自行调整 rowData.sltp = `SL: ${(rowData.signalPrice * 0.95).toFixed(2)} / TP: ${(rowData.signalPrice * 1.05).toFixed(2)}`; // 更新行数据 tenantsTable.row(rowIndex).data(rowData); } }); // 所有更新完成后统一重绘,false参数表示保持当前分页状态 tenantsTable.draw(false); }; }
额外优化说明
- 映射表的方式可以把每次推送的匹配耗时从O(n)降到O(1),即使表格有上千行也不会卡顿
- 批量更新后统一重绘,避免每次修改单元格就重绘一次,大幅减少页面重排重绘开销
- 统一交易对大小写,避免Binance返回的大写和你表格里的小写匹配失败的问题
- WebSocket初始化放在表格初始化完成后,彻底解决时序问题
内容的提问来源于stack exchange,提问作者Murat Şanlısavaş
相关产品推荐
相关产品推荐

