由翻转DOM引发的学习DOMApi

1,295 阅读4分钟

问题:给定一个DOM元素结构如下:

<ul>
  <li/>
  <li/>
</ul>

如何翻转上述DOM结构?

考点: DOM API,DocumentFragment,ul,li

背景知识

DOM API

节点之间导航

对DOM的所有操作都是从document对象开始的,从这个对象我们可以到达任何节点,如下图所示:

Screen Shot 2019-11-19 at 9.06.45 PM.png

<html> = document.documentElement
<body> = document.body
<head> = document.head
需要注意的是,document.body可能为null,如果body为null,内嵌在<head>标签中<script>脚本是无法访问document.body元素的,因为浏览器还没有读到其中的内容:

<html>
<head>
  <script>
    alert( "From HEAD: " + document.body ); // null, there's no <body> yet
  </script>
</head>

<body>
  <script>
    alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists
  </script>
</body>
</html>

子元素:childNodes\firstChild\lastChild

  • 子元素,指的是元素下的所有同级元素,举例,都是的子元素
  • 子系元素,指的是所有嵌套在一个指定元素中的元素,包括这些元素的子元素,以及所有后代元素

假如有个元素为elem,那么调用elem.childNodes将返回所有elem元素下的子节点和全部文本节点,示例代码:

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>Information</li>
  </ul>

  <div>End</div>

  <script>
    for (let i = 0; i < document.body.childNodes.length; i++) {
      alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
    }
  </script>
  ...more stuff...
</body>
</html>

上面script标签中访问了body节点下childNodes,得到的结果为Text, DIV, Text, UL, ..., SCRIPT,值得注意的是,由于访问调用是在<scritpt>节点下的,因而<script>标签之后的内容其实是访问不到的,但是确实存在。

  • firstChild和lastChild属性是访问第一个和最后一个子元素的快捷方式

如果想判断一个节点是否有子节点,可以调用elem.hasChildNodes()
虽然childNodes看起来像个数组,但其实它并不是数组,而是一个集合,也就是类似数组的可迭代对象,所以常见的用于数组操作的方法,不能够用在childNodes身上,比如childNodes[i] = ...这种操作是不可以的。

  • parentNode,previousSibling,nextSibling

通过parentNode访问父节点,通过previousSibling访问上一个节点,通过nextSibling访问下一个节点,这里访问的是节点,也就是说可能会访问到如文本节点、注释节点等等。

元素之间导航

如果只想访问所有HTML标签元素的话,则childNodes相关的属性是不能满足需求的。

Screen Shot 2019-11-19 at 10.04.37 PM.png

上图中和前面的区别只是中间加了Element:

  • children,只获取类型为元素节点的子节点
  • firstElementChild \ lastElementChild,第一个和最后一个元素
  • previouseElementSibling \ nextElementSibling,兄弟元素
  • parentElement,父元素
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null

parentNode和parentElement的区别是parentNode返回任意类型的父节点,parentElement只返回元素类型父节点。通常来说二者是一样的,但是上面代码已给出唯一的区别。

DocumentFragment

DocumentFragment是一个特殊的DOM节点,用于传递节点列表的包装器,说得简单直白些就是将DocumentFragment挂载到其他节点下时,DocumentFragment会和该节点自动融合,所以看起来好像没有DocumentFragment节点一样

ul, ol, dl

ul,无序列表

<ul id="list">
  <li></li>
  <li></li>
  <li></li>
</ul>

Screen Shot 2019-11-20 at 7.49.02 PM.png

ol,有序列表

<ol id="list2">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ol>

Screen Shot 2019-11-20 at 7.49.09 PM.png

dl,dt,dd三者通常一起用,用来代表定义性列表,通常是描述一些技术术语,或用于展示key-value这样的键值对

<dl>
  <dt>属性名</dt>
  <dd>属性值属性值属性值属性值属性值属性值属性值属性值</dd>
</dl>

Screen Shot 2019-11-20 at 7.54.53 PM.png

解决方案

<body>
    <ul id="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>

    <input type="button" id="reverseBtn" value="翻转"/>
    <script>
        let list = document.getElementById('list')

        function reverseChildNodes(node) {
          	//创建包装器
            let frag = node.ownerDocument.createDocumentFragment();
          	//每次取出node节点的最后一个子节点
            while(node.lastChild) {
                //放入包装器中,domApi中取出节点的过程中也会将节点从原父节点中移除
              	//然后形成倒序结构
                frag.appendChild(node.lastChild)
            }
          	//将包装器挂在node节点之下,并于node融合
            node.appendChild(frag)
        }
        btn.onclick = reverseChildNodes.bind(this, list);
    </script>
</body>

参考文献

【1】现代Javascript教程 zh.javascript.info