优雅的DOM操作(上)

217 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

本章讨论以下三类问题:

  • 访问和修改DOM元素
  • 修改DOM元素的样式会导致重绘和重排
  • 通过DOM事件处理与用户的交互

浏览器中的DOM

DOM—文档对象模型是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。在浏览器中,主要用来与HTML文档打交道,同样也用在Web程序中获取XML文档,并使用DOM API用来访问文档的数据。

尽管DOM是个与语言无关的API,它在浏览器中的接口确实用JS实现的。客户端脚本编程大多数时候是在和底层文档打交道,DOM就成为现在JS编码最重要的部分。

浏览器中通常会把 DOM 和 JS 独立实现。比如在 IE 中, Javascript 的实现名为 JScript,位于 jscript.dll 文件中;DOM 的实现则存在另一个库中,名为 mshtml.dll。 这个分离允许其他技术和语言,比如 VBScript 能够共享使用 DOM 以及 Trident 提供的渲染函数。

DOM访问和修改

简单理解,两个相互独立的功能只需要通过接口彼此连接,就会产生消耗。访问DOM元素是有代价的,修改元素的代价则更昂贵因为他会导致浏览器重新计算页面的几何变化。最糟糕的情况是在循环中访问或修改元素,尤其是对HTML元素集合循环操作。

举个例子:

function innerHTMLLoop(){
    for(let i = 0; i < 10000; i++){
        document.getElementById('test').innerHTML += 'a';
    }
}

这个函数循环修改页面元素内容,每次循环迭代该元素都被访问两次:一次读取innerHTML属性值,另一次重写它。

改进:

    function innerHTMLLoop(){
    let str = '';
    for(let i = 0; i < 10000; i++){
        content += 'a';
    }
    document.getElementById('test').innerHTML = str;
}

结果显而易见,访问 DOM 次数越多,代码运行速度越慢。因此通用的法则是减少访问 DOM 的次数,把运算尽量留着 ES 这一端处理

用两种方法创建一个 1000 行的表格:

  • 合并HTML字符然后更新DOM的innerHTML属性
  • 只用标准的DOM方法,比如document.createElement()document.createTextNode()

使用innerHTML生成:

fucntion tableInnerHTML(){
    let i, h = ['<table border="1" width="100%">'];
    h.push('<thead>');
    h.push('<tr><th>id</th><th>yes?</th><th>name</th><th>url</th></tr>')
    h.push("</thead>")
    for(i = 1; i <= 1000; i++){
        h.push("<tr><td>")
        h.push("i")
        h.push("</td><td>")
        h.push("And the answer is ..." + (i % 2 ? "yes": "no"))
        h.push("</td><td>")
        h.push("my name is #" + i)
        h.push("</td><td>")
        h.push("<a herf="#">#</a>")
        h.push("</td><td>")
        h.push("<\td>")
        h.push("</tr>")
    }
    h.push('</tbody>')
    h.push('</table>')
    document.getElementById('test').innerHTML = h.join('');
}

使用DOM方法生成:

function tableDOm(){
    let i,table,thead,tbody,tr,th,td,a;
    tbody = document.createElement('tbody');
    
    for(i = 1; i <= 1000; i++){
        tr = document.createElement("tr");
        td = document.createElement("td");
        td.appedChild(document.createTextNode((i % 2) ? 'yes' : 'no'));
        tr.appendChild(td);
        td = document.createElement("td");
        td.appedChild(document.createTextNode(i);
        tr.appendChild(td);
        td = document.createElement("td");
        td.appedChild(document.createTextNode('my name is #' + i);
        tr.appendChild(td);
        
        a = document.createElement('a');
        a.setAttribute('herf', '#');
        a.appendChild(document.createTextNode('#'))
        
        td = document.createElement('td');
        td.appendChild(a);
        tr.appendChild(td);
        
        tbody.appendChild(tr)
    }
    
    tr = document.createElement('tr');
    th = document.createElement('th');
    th.appendChild(document.createTextNode('yes?'));
    tr.appendChild('th');
    th = document.createElement('th');
    th.appendChild(document.createTextNode('id'));
    tr.appendChild('th');
    th = document.createElement('th');
    th.appendChild(document.createTextNode('name'));
    tr.appendChild('th');
    th = document.createElement('th');
    th.appendChild(document.createTextNode('url'));
    tr.appendChild('th');
    
    thead = document.createElement('thead');
    thead.appendChild(tr);
    table = document.createElement('table');
    table.setAttribute('border', 1);
    table.setAttribute('width', 100%);
    table.appendChild(thead);
    table.appendChild(tbody);
    
    document.getElementById('test').appendChild(table);
}

在旧版浏览器innerHTNL的优势更加明显,新版浏览器则不明显。因此选择哪种方式取决于你的用户使用的浏览器类型,以及编程习惯。
如果一个对性能苛求的操作中更新一大段HTML推荐使用innerHTML,因为它在绝大部分浏览器中运行得更快。