DOM 可以将任何 HTML 或 XML 文档描绘成一个由多层次节点构成的结构。

节点分为多种不同的类型,每种类型分别表示文档中不同的信息及标记。每个节点都拥有各自的特点、数据和方法,也与其他节点存在某种关系。

文档节点是每一个文档的根节点。在 HTML 中,文档节点只有一个子节点,即 html 元素,称为文档元素。文档元素是文档最外层的元素,每个文档只能有一个文档元素,在 HTML 页面中,文档元素始终是 html 元素。

1. Node 类型

DOM1 级定义了一个 Node 接口,该接口将由 DOM 中所有节点类型实现。

在 JavaScript 中,这个 Node 接口是作为 Node 类型实现的,除了 IE 外,其他所有浏览器中都可以访问到这个类型。

在一个 HTMl 文档中出现的所有东西都是节点

  • 元素节点(HTML 标签)

  • 文本节点(文字内容)

  • 注释节点(注释内容)

  • 文档节点(document)

JavaScript 中所有节点类型都继承自 Node 类型,因此所有节点都共享着 Node 类型的基本属性和方法。

节点类型由在 Node 类型中定义的 12 个数值常量来表示:

  • Node.ELEMENT_NODE:1

  • Node.ATTRIBUTE_NODE:2

  • Node.TEXT_NODE:3

  • Node.CDATA_NODE:4

  • Node.ENTITY_REFERENCE_NODE:5

  • Node.ENTITY_NODE:6

  • Node.PROCESSING_INSTRUCTION_NODE:7

  • Node.COMMENT_NODE:8

  • Node.DOCUMENT_NODE:9

  • Node.DOCUMENT_TYPE_NODE:10

  • Node.DOCUMENT_FRAGMENT_NODE:11

  • Node.NOTATION_NODE:12

注意:由于 IE 不支持访问 Node,所以为了确保浏览器的兼容,在判断节点类型时,最好还是将 nodeType 属性与数字值进行比较

2. 常用节点类型与属性

每一种类型的节点都会有一些属性区分自己的特性和特征。

  • nodeType:节点类型

  • nodeName:节点名称

  • nodeValue:节点值

常用:主要记住 nodeType 的值
元素节点

  • nodeType:1

  • nodeName:大写标签名

  • nodeValue:null

文本节点

  • nodeType: 3

  • nodeName:’#text’

  • nodeValue:文本内容

在标准浏览器中会把空格、换行都当作文本节点处理

注释节点

  • nodeType:8

  • nodeName:’#common’

  • nodeValue:注释内容

文档节点

  • nodeType: 9

  • nodeName:’#document’

  • nodeValue:null

3. 节点关系

文档中所有节点之间都存在一定的关系,可以用传统的家族关系来描述,将文档树比喻成家谱。

DOM 节点 - 图1

每个节点都有一些属性,存储着关系指针。这些关系指针指向特定关系的节点,是只读的。

父节点

parentNode

获取当前节点唯一的父亲节点

每一个节点都有一个 parentNode 属性,指向文档树中的父节点。包含在 childNodes 列表中的所有节点都有相同的父节点。

子节点

childNodes

获取当前元素的所有子节点

  • 子节点:只获取儿子级别的

  • 所有:包含元素节点、文本节点等

每个节点都有一个 childNodes 属性,其中保存的是一个 NodeList 对象。

NodeList 是一个类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点。NodeList 对象的独特之处在于,它实际上是基于 DOM 结构动态执行查询的结果,因此 DOM 结构的变化能够自动反映在 NodeList 对象中。

可以通过方括号,也可以使用 item() 方法,来访问 NodeList 中的节点;通过 length 属性可以访问其长度。

  1. var firstChild = someNode.childNodes[0];
  2. var secondChild = someNode.childNodes.item(1);
  3. var count = someNode.childNodes.length;

children

获取当前元素所有的元素子节点

在 IE6~8 中会把注释节点也当作元素节点获取到,所以兼容性不好。需要自己编写兼容方法,不过,以后也不需要考虑 IE 低版本的兼容了。

兄弟节点

previousSibling

获取当前节点的上一个兄弟节点(哥哥)

可能是元素也可能是文本等

previousElementSibling

获取上一个兄弟(哥哥)元素的节点(不兼容 IE6~8)


nextSibling

获取当前节点的下一个兄弟节点(弟弟)

可能是元素也可能是文本等

nextElementSibling
获取下一个兄弟(弟弟)元素的节点(不兼容 IE6~8)

首尾节点

firstChild

获取当前元素的第一个子节点(可能是元素也可能是文本等)

firstElementchild

获取当前元素的第一个元素子节点(不兼容 IE6~8)


lastChild

获取当前元素的最后一个子节点(可能是元素也可能是文本等)

lastElementChild

获取当前元素的最后一个元素子节点(不兼容 IE6~8)

ownerDocument

所有节点都有这个属性,指向表示整个文档的文档节点

4. 兼容方法编写

需求一:获取当前元素的所有元素子节点

基于 children 不兼容 IE 低版本浏览器(会把注释也当作元素节点)

  1. 首先拿到所有子节点

  2. 遍历这些子节点,判断 nodeType 是否为 1,即元素节点,将其放入数组中

  1. /* 基本注释编写:
  2. * children: get all the element subnodes of the current element
  3. * @parameter
  4. * curEle: [object] current element
  5. * @return
  6. * [Array] all the element nodes
  7. * by team on 2018/07/11 21:32
  8. * update ...
  9. */
  10. function children(curEle) {
  11. // 1. 首先获取当前元素下所有子节点
  12. var nodeList = curEle.childNodes,
  13. result = [];
  14. // 2. 遍历这些节点,然后遍历这些节点,筛选出元素节点,把筛选出来的结果单独存储起来
  15. for (var i = 0; i < nodeList.length; i++) {
  16. var item = nodeList[i];
  17. if (item.nodeType === 1) {
  18. result.push(item);
  19. }
  20. }
  21. return result;
  22. }

需求二:获取去当前元素的上一个兄弟元素节点(哥哥)

基于 previousElementSibling不兼容 IE 低版本浏览器(会把注释也当作元素节点)

  1. /*
  2. * prev: get the last elder brother element node of the current element
  3. * @parameter
  4. * curEle: [object] current element
  5. * @return
  6. * [object] the element node
  7. * by destiny 2018/7/11 21:45
  8. */
  9. function prev(curELe) {
  10. //=> 先找到当前元素的哥哥节点
  11. //=> 判断是否为元素节点,不是的话,基于哥哥,找哥哥的上一个哥哥节点,直到找到元素节点或者已经没有哥哥了(说明当前元素就是老大)则结束查找
  12. var pre = curEle.previousSibling;
  13. while(pre && pre.nodeType !== 1) {
  14. /*
  15. * pre && pre.nodeType !== 1
  16. * pre 是验证还有没有,这样写代表有,没有 pre 是 null
  17. * pre.nodeType 是验证是否为元素节点
  18. */
  19. pre = pre.previousSibling;
  20. }
  21. return pre;
  22. }

其他需求拓展

  • next 获取下一个弟弟元素节点
  1. /*
  2. * next: get next brother element node of the current element
  3. * @parameter
  4. * curELe: [object]
  5. * @return
  6. * [object] the element node
  7. * by destiny 2018/07/11 23:18
  8. */
  9. function next(curEle) {
  10. var nt = curEle.nextSibling;
  11. while (nt && nt.nodeType !== 1) {
  12. nt = nt.nextSibling;
  13. }
  14. return nt;
  15. }
  • prevAll 获取所有哥哥元素节点
  1. /*
  2. * pre: get all the elder brother element nodes of the current element
  3. * @parameter
  4. * curEle: [object] current element
  5. * @return
  6. * [Array] all the element nodes
  7. * by destiny 2018/7/11 21:45
  8. */
  9. function prevAll(curELe) {
  10. //=> 先找到当前元素的哥哥节点,
  11. //=> 如果存在哥哥节点,则判断是否为元素节点,是的话,单独存储起来
  12. //=> 不是的话,基于哥哥,找哥哥的上一个哥哥节点,直到已经没有哥哥节点了,则结束查找
  13. var pre = curEle.previousSibling,
  14. result = [];
  15. while(pre) {
  16. if (pre.nodeType === 1) {
  17. result.push(pre);
  18. }
  19. pre = pre.previousSibling;
  20. }
  21. return result;
  22. }
  • nextAll 获取所有弟弟元素节点
  1. /*
  2. * next: get all the next brother element nodes of the current element
  3. * @parameter
  4. * curELe: [object]
  5. * @return
  6. * [Array] all the element nodes
  7. * by destiny 2018/07/11 23:18
  8. */
  9. function nextAll(curEle) {
  10. var nt = curEle.nextSibling,
  11. result = [];
  12. while (nt) {
  13. if (nt.nodeType === 1) {
  14. result.push(nt);
  15. }
  16. nt = nt.nextSibling;
  17. }
  18. return result;
  19. }
  • siblings 获取所有兄弟元素节点

    • 先获取当前节点的父节点

    • 获取父节点的所有子节点

    • 遍历这些节点,判断是否是元素节点,如果是并且不是当前节点,则单独存储起来

  1. function sibings(ele) {
  2. var parent = ele.parentNode;
  3. var sons = children(parent);
  4. var result = [];
  5. sons.forEach(function(item) {
  6. if (item !== ele) {
  7. result.push(item);
  8. }
  9. });
  10. return result;
  11. }
  • index 获取当前元素的索引

    • 获取其父元素的所有子元素

    • 把这些子元素与当前元素相同标签的存储到一个数组中

    • 返回当前元素在数组中的位置

  1. function index(curEle) {
  2. var parent = curEle.parentNode;
  3. var sons = children(parent);
  4. var nodeName = curEle.nodeName;
  5. var ary = [];
  6. sons.forEach(function (item) {
  7. if (item.nodeName === nodeName) {
  8. ary.push(item);
  9. }
  10. });
  11. return ary.indexOf(curEle);
  12. }