优雅的DOM操作(中)

118 阅读3分钟

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

节点克隆

使用 DOM 方法更新页面内容的另一个途径是克隆已有元素,而不是创建新元素就是使用element.cloneNode()代替document.createElement()。在大多数浏览器中,节点克隆更有效率。

HTML 集合

HTML 集合包含了 DOM 节点引用的类数组对象。以下方法的返回值是一个集合,类似数组的列表。

  • document.getElementByName()
  • document.getElementByClassName()
  • document.getElementByTagName()
  • document.images
  • document.links
  • document.forms
  • document.forms[0].elements

为了演示集合的实时性,考虑以下代码:

    let alldivs = document.getElementsByTagName('div');
    for(let i = 0; i < alldivs.length; i++){
        document.body.appendChild(document.createElement('div'))
    }

实际上是一个死循环,每次迭代时length都会增加,它反映出的是底层文档的当前状态。像这样便利HTMNL集合可能会导致逻辑错误而且也很慢。在循环的条件控制语句中读取数组的length属性是不推荐的做法。读取一个集合的length要比读取普通数组的length要慢很多。
很多情况下如果只需要便利一个相对较小的集合,那么缓存length就够了,但由于遍历数组比遍历集合快,因此如果先将集合元素拷贝到数组中,那么访问它的属性会更快。

一般来说,对于任何类型的 DOM 访问,需要多次访问同一个 DOM 属性或方法需要多次访问时,最好使用一个局部变量缓存此成员。当遍历一个集合时,第一优化原则是把集合村在局部变量中,把length缓存在循环外部,然后用局部变量代替这些需要多次读取的元素。

// 较慢
function collectionGlobal(){
    let coll = document.getElementsByTagName('div'),
    len = coll.length,
    name = '';
    for(let i = 0; i < len; i++){
        name = document.getElementsByTagName('div')[i].nodeName;
        name = document.getElementsByTagName('div')[i].nodeType;
        name = document.getElementsByTagName('div')[i].tagName;
    }
    return name;
}

// 较快
function collectionlocal(){
    let coll = document.getElementsByTagName('div'),
    len = coll.length,
    name = '';
    for(let i = 0; i < len; i++){
        name = coll[i].nodeName;
        name = coll[i].nodeType;
        name = coll[i].tagName;
    }
    return name;
}

// 最快
function collectionlocal(){
    let coll = document.getElementsByTagName('div'),
    len = coll.length,
    name = '',
    el = null;
    for(let i = 0; i < len; i++){
        el = coll[i]
        name = el.nodeName;
        name = el.nodeType;
        name = el.tagName;
    }
    return name;
}

遍历 DOM

DOM API 提供了多种方法来读取文档结构中的特定部分。当你需要从多种方案中选择时,最好为特定操作选择最高效的 API。

通常你需要从某一个 DOM 元素开始,操作周围的元素,或者递归查找所有子节点。你可以用 childNodes 得到元素集合,或者用 nextSibling 来获取每个相邻元素。

function testNextSibling(){

    let el = document.getElementById('myDiv'),
        ch = el.firstChild,
        name = '';
        
    do{
        name = ch.nodeName;
      } while(ch = ch.nextSibling);
      
      return name;
}

function testChildNodes(){

    let el = document.getElementById('myDiv'),
        ch = el.childNodes,
        len - ch.length,
        name = '';
        
    for(let i = 0; i < len; i++){
        name = ch[count].nodeName;
    }
      
      return name;
}

childNodes 是个元素集合, 因此在循环中注意缓存 length 属性。

在不同浏览器两个方法的运行时间几乎相等。

元素节点

DOM 元素属性诸如 childNodes, firstChild 和 nextSibling 并不区分元素节点和其他类型节点,比如注释和文本节点。在某些情况下,需要访问元素节点,因此在循环中很可能需要检擦返回的节点类型并过滤掉非元素节点。 使用children代替childNodes 会更快,因为集合项更少。HTML源码中的空白实际上是文本节点,而且它并不包含在children集合中。

对 DOM 中的特定元素操作时,开发者通常需要得到比 getElementById()getElementsByTagName()更好的控制。最新的浏览器也提供了一个名为querySelectorAll()的原生 DOM 方法。