【问题标题】:Javascript performance optimizationJavascript 性能优化
【发布时间】:2018-07-19 20:37:03
【问题描述】:

我创建了以下js函数

function csvDecode(csvRecordsList)
{
    var cel;
    var chk;
    var chkACB;
    var chkAF;
    var chkAMR;
    var chkAN;
    var csvField;
    var csvFieldLen;
    var csvFieldsList;
    var csvRow;
    var csvRowLen = csvRecordsList.length;
    var frag = document.createDocumentFragment();
    var injectFragInTbody = function () {tblbody.replaceChild(frag, tblbody.firstElementChild);};
    var isFirstRec;
    var len;
    var newEmbtyRow;
    var objCells;
    var parReEx = new RegExp(myCsvParag, 'ig');
    var tblbody;
    var tblCount = 0;
    var tgtTblBodyID;

    for (csvRow = 0; csvRow < csvRowLen; csvRow++)
    {
        if (csvRecordsList[csvRow].startsWith(myTBodySep))
        {
            if (frag.childElementCount > 0)
            {
                injectFragInTbody();
            }
            tgtTblBodyID = csvRecordsList[csvRow].split(myTBodySep)[1];
            newEmbtyRow = getNewEmptyRow(tgtTblBodyID);
            objCells = newEmbtyRow.cells;
            len = newEmbtyRow.querySelectorAll('input')[0].parentNode.cellIndex; // Finds the cell index where is placed the first input (Check-box or button)

            tblbody = getElById(tgtTblBodyID);
            chkAF = toBool(tblbody.dataset.acceptfiles);
            chkACB = toBool(tblbody.dataset.acceptcheckboxes) ;
            chkAN = toBool(tblbody.dataset.acceptmultiplerows) ;
            tblCount++;
            continue;
        }

        csvRecordsList[csvRow] = csvRecordsList[csvRow].replace(parReEx, myInnerHTMLParag); // Replaces all the paragraph symbols ¶ used into the db.csv file with the tag <br> needed into the HTML content of table cells, this way will be possible to use line breaks into table cells
        csvFieldsList = csvRecordsList[csvRow].split(myEndOfFld);

        csvFieldLen = csvFieldsList.length;
        for (csvField = 0; csvField < csvFieldLen; csvField++)
        {
            cel = chkAN ? csvField + 1 : csvField;
            if (chkAF && cel === 1) {objCells[cel].innerHTML =  makeFileLink(csvFieldsList[csvField]);} 
            else if (chkACB && cel === len) {objCells[cel].firstChild.checked = toBool(csvFieldsList[csvField]);}
            else {objCells[cel].innerHTML = csvFieldsList[csvField];}
        }
        frag.appendChild(newEmbtyRow.cloneNode(true));
    }
    injectFragInTbody();

    var recNum = getElById(tgtTblBodyID).childElementCount;
    customizeHtmlTitle();
    return csvRow - tblCount + ' (di cui '+ recNum + ' record di documenti)';
}

超过 90% 的记录可能包含必须由以下 makeFileLink 函数处理的文件名:

function makeFileLink(fname)
{
    return ['<a href="', dirDocSan, fname, '" target="', previewWinName, '" title="Apri il file allegato: ', fname, '" >', fname, '</a>'].join('');
}

它旨在从特殊类型的 *.db.csv 文件中解码记录列表(= 逗号分隔的值,其中逗号被我硬编码到 var myEndOfFld 中的另一个符号替换)。 (这种特殊类型的 *.db.csv 是由我编写的另一个函数创建的,它只是一个“文本”文件)。

要解码并附加到 HTML 表的记录列表通过其唯一参数传递给函数:(csvRecordsList)

到 csv 文件中的是来自更多 HTML 表的托管数据。

表格的行数和列数以及某些其他包含的数据类型(可能是文件名、数字、字符串、日期、复选框值)是不同的。

有些表可能只有 1 行,有些则接受更多行。

一行数据的基本结构如下:

data field content 1|data field content 2|data field content 3|etc...

一旦被我的算法解码,它就会被正确地呈现到 HTML td 元素中,即使在一个字段中还有更多的段落。实际上标签
将被添加到代码需要的地方:

csvRecordsList[csvRow].replace(par, myInnerHTMLParag)

替换我选择的所有字符来表示我硬编码到变量myCsvParag中的段落符号。

在编程时不可能知道要在每个表中加载的记录数、从 CSV 文件加载的记录数、每条记录的字段数或将包含数据的表字段或将为空:在同一记录中,某些字段可能包含数据,其他字段可能为空。一切都必须在运行时发现。

在特殊的 csv 文件中,每个表都由一行与下一个表分开,其中只包含一个具有以下模式的字符串:myTBodySep = tablebodyid where myTBodySep = "targettbodydatatable" 这只是我选择的硬编码字符串。 tablebodyid 只是一个占位符,其中包含一个字符串,表示要在其中插入新记录的目标表 tbody 元素的 id,例如:tBodyDataCars、tBodyDataAnimals...等。

因此,当第一个 for 循环在 csvRecordsList 中找到一个字符串,该字符串与该字符串一起进入变量 myTBodySep 时,它会从同一行获取 tablebodyid:这将是必须定位的新 tbodyid用于在其中注入下一条记录

每个表都归档到 CSV 文件中

第一个 for 循环扫描文件中的 csv 记录列表,第二个 for 循环准备使用数据编译目标表所需的内容。

上面的代码运行良好,但有点慢:事实上,在具有 2 GB 内存和 Pentium core 2 的计算机上,从 CSV 文件加载大约 300 条记录到 HTML 表中需要多一点 2.5 秒4300 双核,频率为 1800 MHz,但如果我评论更新 DOM 的行,该功能需要不到 0.1 秒。所以恕我直言,瓶颈是代码的片段和 DOM 操作部分。

我的目标和希望是在不丢失功能的情况下优化上述代码的速度。

请注意,我只针对现代浏览器,而不关心其他浏览器和不符合标准的浏览器...我为他们感到难过...

有什么建议吗? 提前致谢。

编辑 16-02.2018

我不知道它是否有用,但最后我注意到,如果从浏览器会话存储中加载数据,则加载和渲染时间或多或少减半。但奇怪的是,从文件和会话存储中加载数据的函数完全相同。 我不明白为什么会出现这种不同的行为,因为数据完全相同,并且在这两种情况下都在开始检查性能计时之前传递给函数本身处理的变量。

编辑 18.02.2018

  1. 行数因目标表而异:从 1 到 1000(在特定情况下可能更多)
  2. 取决于目标表的列数:从 10 到 18-20

【问题讨论】:

  • 您能否提供一个示例,说明您认为该函数运行后输出的 HTML 应该是什么?这可能有助于我给你一个更好的答案,而不是仅仅试图对你在这里所做的事情进行逆向工程。
  • injectFragInTbody 是做什么的?你确定你需要它inside for 循环吗?附加建议:尽量不要使用字符串和innerHTML(改用document.createElementnode.appendChild)看看它是否有所作为(应该)。
  • @user3297291 injectFragInTbody 函数将表的空行替换为包含从文件(或浏览器会话存储)获取的数据的片段中包含的行块。它会在目标表的每次更改以及最后一个表的循环结束时执行此操作。
  • @th3n3wguy 无需示例:代码只是获取变量 csvRecordsList 的数据(可以是文件内容或浏览器会话存储内容的键值,无所谓)和将其插入到一些 html 表格中
  • 就 JS 而言,使用开发工具分析器并缩小耗时最长的事情。而且,DOM 操作是双向的——从 DOM 读取可能会产生重大影响,具体取决于您的页面加载了多少节点、类等。您需要注意以下事项——何时替换元素,他们移动或变大/变小了吗?该表/td 上的样式和类也将产生重大影响。表格不作为布局边界,但内部的渲染成本仍然很高,尤其是当您有超过 200 行和很多列时。

标签: javascript performance function csv optimization


【解决方案1】:

事实上,使用 DOM 操作构建表格比简单的 innerHTML 更新表格元素要慢得多。

如果您尝试重写代码以准备一个 html 字符串并将其放入表格的 innerHTML 中,您会看到显着的性能提升。

浏览器经过优化,可以解析它们从服务器接收到的text/html,因为这是它们的主要用途。通过 JS 对 DOM 的操作是次要的,所以它们没有那么优化。

我为你做了一个简单的基准测试。

让我们制作一个 300x300 的表格并用“A”填充 90000 个单元格。 有两个功能。

第一个是代码的简化变体,它使用DOM methods

var table = document.querySelector('table tbody');
var cells_in_row = 300, rows_total = 300;

var start = performance.now();
fill_table_1();
console.log('using DOM methods: ' + (performance.now() - start).toFixed(2) + 'ms');

table.innerHTML = '<tbody></tbody>';


function fill_table_1() {
  var frag = document.createDocumentFragment();

  var injectFragInTbody = function() {
    table.replaceChild(frag, table.firstElementChild)
  }

  var getNewEmptyRow = function() {
    var row = table.firstElementChild;
    if (!row) {
      row = table.insertRow(0);
      for (var c = 0; c < cells_in_row; c++) row.insertCell(c);
    }
    return row.cloneNode(true);
  }

  for (var r = 0; r < rows_total; r++) {
    var new_row = getNewEmptyRow();
    var cells = new_row.cells;
    for (var c = 0; c < cells_in_row; c++) cells[c].innerHTML = 'A';
    frag.appendChild(new_row.cloneNode(true));
  }
  injectFragInTbody();
  return false;
}
&lt;table&gt;&lt;tbody&gt;&lt;/tbody&gt;&lt;/table&gt;

第二个准备html字符串,放入表格的innerHTML

var table = document.querySelector('table tbody');
var cells_in_row = 300, rows_total = 300;

var start = performance.now();
fill_table_2();
console.log('setting innerHTML: ' + (performance.now() - start).toFixed(2) + 'ms');

table.innerHTML = '<tbody></tbody>';

function fill_table_2() {// setting innerHTML
  var html = '';
  for (var r = 0; r < rows_total; r++) {
    html += '<tr>';
    for (var c = 0; c < cells_in_row; c++) html += '<td>A</td>';
    html += '</tr>';
  }
  table.innerHTML = html;
  return false;
}
&lt;table&gt;&lt;tbody&gt;&lt;/tbody&gt;&lt;/table&gt;

相信你会得出一些结论。

【讨论】:

  • 这个基准非常具有误导性。用textContent 替换重复的innerHTML 集合(为每个单元格解析!),重新排序两次尝试,并使用更正常的行设置——您最终会更快地获得DOM 报告。 (这也不是一个准确的基准——我只是在解释为什么这个没有显示任何东西。)在 Firefox 上试试:jsfiddle.net/7z2dLv2r
  • 谢谢科什。哇!我在 Chrome 中的结果:DOM 2345 ms 和 innerHTML 322 ms 你的测试表明 innerHTML 比 DOM 方法快或多或少 7 倍。我不明白:那里的每个人都说使用 DOM 方法而不是 innerHTML 更好,因为 DOM 方法更快。但是这里的结果却完全相反,这怎么可能呢?我真的很困惑。而且我不得不说,在我自己使用 innerHTML 之前,时间是 1.2 - 1.3 秒,但现在使用 DOM 方法更慢。所以我“可以确认”你的方法。但为什么其他人的经历如此不同?
  • @Ryan setting innerHTML: 348.00ms using DOM methods: 1141.00ms true 在我看来,Kosh 是有道理的,他的方法根本没有误导,可能是这样可以优化,但这是一个有趣的点。
  • @willywonka:你在 Firefox 上试过了吗?两者都比 Chrome 的 innerHTML 更快。但是,是的,它在 Chrome 上仍然具有误导性,因为您可以在一个玩具示例中看到差异从 7× 变为 3×。
  • @Ryan 感谢您的建议:ff 设置 innerHTML:234.54ms 使用 DOM 方法:277.45ms true 设置 innerHTML:223.81ms 使用 DOM 方法:257.80ms真 使用DOM方法设置innerHTML:224.49ms:252.23ms真 使用DOM方法设置innerHTML:232.85ms:270.35ms真 设置innerHTML:224.15 ms 使用 DOM 方法:260.73ms true 设置 innerHTML:218.59ms 使用 DOM 方法:254.74ms true 设置 innerHTML:223.58ms 使用 DOM 方法:269.07ms true 使用 DOM 方法设置 innerHTML: 217.38ms: 253.65ms true
【解决方案2】:

我有两个想法给你。

1:如果您想知道代码的哪些部分(相对)较慢,您可以使用此处描述的技术进行非常简单的性能测试。我没有阅读您提供的所有代码示例,但您可以自己添加这些性能测试并检查哪些操作需要更多时间。

2:我对 JavaScript 和浏览器的了解是,更改 DOM 是一项昂贵的操作,您不想更改 DOM 太多次。相反,您可以做的是构建一组更改,然后通过一次 DOM 更改应用所有这些更改。这可能会使您的代码不太好,但这通常是您想要获得高性能时的权衡。

让我知道这对你有什么影响。

【讨论】:

  • 感谢 Niels Born 的回复。我使用 DocumentFragment 来防止过多的 DOM 访问和更改以及页面重新渲染:使用它,每个表的代码只访问 DOM 一次。
【解决方案3】:

您应该首先在多个函数中重构您的代码,使其更具可读性。确保将 DOM 操作函数与数据处理函数分开。理想情况下,创建一个类并将这些变量从您的函数中取出,这样您就可以使用this 访问它们。

然后,您应该在web worker 中执行处理数据的每个函数,这样您就可以确定您的 UI 不会被进程阻塞。您将无法在 Web Worker 中访问 this,因此您必须将其限制为纯“输入/输出”操作。

您也可以使用promises 代替自制回调。它使代码更具可读性,并且更容易调试。你可以做一些很酷的事情,比如:

this.processThis('hello').then((resultThis) => {
    this.processThat(resultThis).then((resultThat) => {
        this.displayUI(resultThat);
    }, (error) => {
        this.errorController.show(error); //processThat error
    });
}, (error) => {
    this.errorController.show(error); //processThis error
});

祝你好运!

【讨论】:

  • 感谢纪尧姆的回答和祝你好运。 Web Worker 非常适合防止 UI 冻结。但是在这种特定情况下,我不确定实现它们是否是一件好事:恕我直言,用户最好在程序 UI 完成加载数据之前不能做任何事情,因为如果他这样做,IMO 可能会发生混乱。尽管如此,有一个向用户显示操作当前状态的进度条可能会很有趣:如果操作的时间在未来超过 5 秒,我已经在考虑实现进度条。
  • 关于 DOM 操作函数与数据处理函数的分离:这不是意味着更多的 for 循环会花费更多的执行时间吗?我错了吗?
  • 另外,你不能直接从工作人员内部操作 DOM,所以我看不到在这种特定情况下如何使用它们
  • 嘿@willywonka!不,您不能从 web worker 操作 DOM,这就是为什么我指定它将用于数据处理功能;-) 在重构代码时您不需要进行更多循环。您将只在循环中执行两个函数,而不是一个指令块。
  • 是的,正确的。优化 DOM 交互的最好方法是使用虚拟 DOM,你不想花时间自己开发 ;)
猜你喜欢
  • 1970-01-01
  • 2016-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 2016-05-14
  • 2011-10-17
  • 2014-01-08
相关资源
最近更新 更多