DOM操作

Posted 奋飛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DOM操作相关的知识,希望对你有一定的参考价值。

​ 页面上有个空的无序列表节点,用<ul></ul> 表示,通过javascript动态往列表中插入 3 个<li>,每个列表项的文本内容是列表项的插入顺序,取值 1, 2, 3;同时绑定click事件,单击依次输出1,2,3。

<ul class="js-container">
    <!-- 动态添加内容 -->
</ul>

动态添加li

var containerDom = document.querySelector('.js-container'),
    itemDom = null;

for(let i = 1; i <= 3; i++) {
    itemDom = document.createElement("li");
    itemDom.innerText = i;
    containerDom.appendChild(itemDom);
}

绑定事件

var containerDom = document.querySelector('.js-container'),
    itemDom = null;

for(let i = 1; i <= 3; i++) {
    itemDom = document.createElement("li");
    itemDom.innerText = i;
    itemDom[i].addEventListener('click', function() {
        alert(i); // alert(this.innerText);
    });
    containerDom.appendChild(itemDom);
}

需要注意: 上述使用let局部作用域(使用闭包同样可以实现)!绑定事件使用addEventListener,而没有使用内联事件onclick。是因为内联事件是作为元素属性保存起来的,这些属性可以被覆盖,所以如果为同一个事件绑定了多个处理程序,那么最后一个处理程序会覆盖之前的。

增大数据量

​ 如果将li的数量改为500,5000甚至更大呢?页面必然会出现卡顿或者直接卡死。

事件代理,减少事件数量

var containerDom = document.querySelector('.js-container'),
    itemDom = null;

for(let i = 1; i <= 500; i++) {
    itemDom = document.createElement("li");
    itemDom.innerText = i;
    containerDom.appendChild(itemDom);
}

// 事件代理
containerDom.addEventListener('click', function(e){
    const target = e.target;
    if (target.tagName === 'LI') {
        alert(target.innerhtml);
    }
});

DocumentFragement可以减少DOM操作

​ 接口表示的是没有父节点的最小的文档对象。它被当做一个轻量版本的 Document 使用,用于存储已排好版的或尚未打理好格式的XML片段。可以使用document.createDocumentFragment方法或者构造函数来创建一个空的 DocumentFragment.

​ DocumentFragement通常用来创建一个文档片段,然后将创建的DOM元素插入到文档片段中,最后把文档片段插入到DOM树中。在DOM树中,文档片段会被替换为它所有的子元素。因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面重绘(reflow)(对元素位置和几何上的计算)。因此,使用文档片段DocumentFragement通常会起到优化性能的作用。

var containerDom = document.querySelector('.js-container'),
    contentFragment = document.createDocumentFragment(),
    itemDom = null;

// 先将li添加到contentFragment,已减少dom操作
for(let i = 1; i <= 500; i++) {
    itemDom = document.createElement("li");
    itemDom.innerText = i;
    contentFragment.appendChild(itemDom);
}
containerDom.appendChild(contentFragment);

// 事件代理
containerDom.addEventListener('click', function(e){
    const target = e.target;
    if (target.tagName === 'LI') {
        alert(target.innerHTML);
    }
});

分批处理,requestAnimationFrame平滑过渡

创建动画时,大家经常会想到使用setTimeoutsetInterval。使用上述方式有这样几个问题:

  • 动画区域或者页面已被隐藏,setTimeoutsetInterval仍被执行;
  • 大多数计算机显示器以60Hz的速率刷新,这基本上意味着每秒重新绘制60次。为了得到最平滑的动画,需要设置最佳间隔是1000ms / 60或约17ms,但这不能覆盖全部浏览器;
  • 延迟毫秒数并不意味着该毫秒后被执行,仅表示其进行排队。如果UI线程很忙,可能会处理用户操作,那么该代码将不会立即执行;

window.requestAnimationFrame(callback) 方法告诉浏览器您希望执行动画,并请求浏览器调用指定的函数在下一次重绘之前更新动画。该方法将在重绘之前调用的回调作为参数。window.cancelAnimationFrame() 来取消这个回调函数。

const containerDom = document.querySelector('.js-container');

const total = 5000,   // 5000个li
    batchSize = 50,   // 每一个批次执行50个
    batchCount = Math.ceil(total / batchSize); // 批次数
let batchDone = 0;  // 已经完成的批处理个数

/**
 * 批次插入li
 */
function appendItems() {
    let contentFragment = document.createDocumentFragment(),
        itemDom = null;
    for(let i = 1; i <= batchSize; i++) {
        itemDom = document.createElement("li");
        itemDom.innerText = (batchDone * batchSize) + i;
        contentFragment.appendChild(itemDom);
    }
    containerDom.appendChild(contentFragment);
    batchDone++;
    // 调用下一批次
    doBatchAppend();
}

/**
 * 平滑插入各个批次
 */
function doBatchAppend() {
    if (batchDone < batchCount) {
        // 无需设置时间
        window.requestAnimationFrame(appendItems);
    }
}

doBatchAppend();

// 绑定事件
containerDom.addEventListener('click', function(e){
    const target = e.target;
    if (target.tagName === 'LI') {
        alert(target.innerHTML);
    }
});

注意: requestAnimationFrame()存在一定的兼容性问题

兼容性

(function() {
    if (window.requestAnimationFrame) {
        return;
    } else {
        var lastTime = 0;
        var vendors = ['ms', 'moz', 'webkit', 'o'];
        for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
            window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
            window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
                     || window[vendors[x]+'CancelRequestAnimationFrame'];
        }
        // 使用setTimeout模拟实现
        if (!window.requestAnimationFrame){
            window.requestAnimationFrame = function(callback, element) {
                var currTime = new Date().getTime();
                var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                var id = window.setTimeout(function() { 
                  callback(currTime + timeToCall); 
                }, timeToCall);
                lastTime = currTime + timeToCall;
                return id;
            };
            window.cancelAnimationFrame = function(id) {
                clearTimeout(id);
            };
        }
    }
}());

参考地址:
http://creativejs.com/resources/requestanimationframe/
https://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestanimationframe/

以上是关于DOM操作的主要内容,如果未能解决你的问题,请参考以下文章

jquery 对象的 heightinnerHeightouterHeight 的区别以及DOM 元素的 clientHeightoffsetHeightscrollHeightoffset(代码片段

DOM操作

JavaScript单行代码,也就是代码片段

dom操作

实用代码片段将json数据绑定到html元素 (转)

更改页面javascript代码(TamperMonkey)以将键盘笔触发送到父DOM