DOM节点树结构的主要入口——Node对象

223 阅读5分钟

这是我参与11月更文挑战的第 18 天,活动详情查看:2021最后一次更文挑战

Node对象

1.Node对象是什么?

DOM的标准规范中提供了Node对象,该对象主要提供了用于解析DOM节点树结构的属性和方法。

DOM树结构主要是依靠节点进行解析,称为DOM节点树结构。Node对象是解析DOM节点树结构的主要入口。

Node对象提供的属性和方法,可以实现遍历节点、插人节点和替换节点等操作。

如下代码做了document与Node的比较:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Node对象是什么</title>
</head>
<body>
<button id="btn">按钮</button>
<script>
    console.log(document);
    //1、Node对象并不像Document对象一样 —— 提供直接使用对象
    // console.log(node);报错

    // 2、Node类型是不允许通过new关键字创建对象的
    /*var node = new Node();
    console.log(node);//输出报错*/

    var btnElement = document.getElementById('btn');
    console.log(btnElement instanceof HTMLButtonElement);//true
    //定位页面元素其实就是Node对象 -> 称为元素节点
    console.log(btnElement instanceof Node);//true

    console.log(Node.prototype);
//Node {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, …}
    //延伸
    var btn = document.createElement('button');
    console.log(btn instanceof Node);//true
</script>
</body>
</html>

2.继承链关系

Node对象是继承于EventTarget对象的,EventTarget是一个用于接收事件的对象。

DOM的标准规范中的Document对象和Element对象都是继承于Node对象的。

通过如下示例代码测试他们之间的继承关系:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>继承链关系</title>
</head>
<body>
<script>
    // Node对象是继承于EventTarget对象的,EventTarget是一个用于接收事件的对象。测试代码如下:
    console.log(Node.prototype instanceof EventTarget);//true

    // DOM的标准规范中的Document对象和Element对象都是继承于Node对象的。测试代码如下:
    console.log(Document.prototype instanceof Node);//true
    console.log(Element.prototype instanceof Node);//true
</script>
</body>
</html>

3.判断节点类型

Node对象中提供了nodeName、nodeType和nodeValue分别可以用于获取指定节点的节点名称、 节点类型和节点的值。

DOM节点树结构中,我们实际开发最常见的节点有:

节点名称含义
元素节点表示HTML页面的标签(即HTML页面的结构)
属性节点表示HTML页面中的开始标签包含的属性
文本节点表示HTML页面中的标签所包含的文本内容

如下代码展示了如何判断节点类型:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>判断节点类型</title>
</head>
<body>
<button id="btn" class="cls">按钮</button>
<script>
    //1、元素节点
    var btnElement = document.getElementById('btn');
    // 元素节点的nodeName属性值为标签名称(大写)
    console.log(btnElement.nodeName);//BUTTON
    console.log(btnElement.nodeType);//1
    console.log(btnElement.nodeValue);//null

    //2、文本节点
    var textNode = btnElement.firstChild;//获取元素的第一个子节点
    //文本节点的nodeName属性值是固定值(#text)
    console.log(textNode.nodeName);//#text
    console.log(textNode.nodeType);//3
    // 文本节点的nodeValue属性值是文本内容
    console.log(textNode.nodeValue);//按钮

    textNode.nodeValue="新按钮";//更新textNode的nodeValue属性值。

    // 3、属性节点
    var attrNode = btnElement.getAttributeNode('class');
    // 属性节点的nodeName属性值为当前元素的属性名称
    console.log(attrNode.nodeName);//class
    console.log(attrNode.nodeType);//2
    // 属性节点的nodeValue属性值为当前元素的属性名称对应的值
    console.log(attrNode.nodeValue);//cls
</script>
</body>
</html>

获取父节点

通过HTML页面中指定元素查找其父级节点,我们可以使用Node对象的parentNode属性实现:

var.pNode = node.parentNode;

注意:在一个元素节点的父节点,可能是一个元素节点,也可能是一个文档节点

示例代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取父节点</title>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    // 作为子节点
     var mi = document.getElementById('mi');
     //通过子节点获取其父节点
    var parent1 = mi.parentNode;
    console.log(parent1);
    // parentElement属性-> 表示获取其父元素节点
    var parent2 = mi.parentElement;
    console.log(parent2);

    var html = document.documentElement;
    console.log(html.parentNode);//文档节点#document
    console.log(html.parentElement);//null

</script>
</body>
</html>

获取子节点

通过HTML页面中指定元素查找其子节点,我们可以通过以下Node对象的属性实现:

属性名描述
childNodes获取指定节点 的所有子节点
firstChild获取指定节点的第一个子节点
lastChild获取指定指定节点的最后一个子节点
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取子节点</title>
    <script src="myTools%20-%203.0.js"></script>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    var parent = document.getElementById('parent');

    var children = parent.childNodes;

    var children = myTools.childNodes(parent);
    console.log(children);

    /*var firstChild = parent.firstChild;
    firstChild = firstChild.nextSibling;
    console.log(firstChild);*/

    var firstChild = myTools.firstChild(parent);
    console.log(firstChild);

    /*var lastChild = parent.lastChild;
    lastChild = lastChild.previousSibling;
    console.log(lastChild);*/

    var lastChild = myTools.lastChild(parent);
    console.log(lastChild);

</script>
</body>
</html>

myTools-3.0文件如下:

// 创建对象 —— 专门用于封装解决空白节点问题
var myTools = {
    // 解决获取其所有子节点childNodes属性的问题
    childNodes : function (parentNode) {
    var children = parentNode.childNodes;
        var arr = [];
        for (var i=0;i<children.length;i++){
            var child = children[i];
            if (child.nodeType===1){
                arr.push(child);
            }
        }
        return arr;
    },
    firstChild:function (parentNode) {
    var firstChild = parentNode.firstChild;
    firstChild = firstChild.nextSibling;
    return firstChild;
    },
    lastChild:function (parentNode) {
        var lastChild = parent.lastChild;
        lastChild = lastChild.previousSibling;
        return lastChild;
    }
}

获取子节点会出现获取的子节点包含空白节点的问题,

如下代码展示了所遇到的空白节点问题和解决空白节点问题的两种方法:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>空白节点问题</title>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    var parent = document.getElementById('parent');
    // childNode属性 - 获取指定节点的所有子节点
    var children = parent.childNodes;
    console.log(children);//NodeList(7) 包含空白节点

    /* 1、利用循环加判断筛选
    var arr = [];
    for (var i=0;i<children.length;i++){
        var child = children[i];
        if (child.nodeType===1){
            arr.push(child);
        }
    }
    console.log(arr);
     */
    // 2、利用getElementsByTagName()方法
    var lis = parent.getElementsByTagName('li');
    console.log(lis);
</script>
</body>
</html>

上述代码所遇到的空白节点问题如下图所示:

aOpbKP.png

空白节点

主流浏览器解析HTML页面内容为DOM节点树结构时,会产生空文本的空白节点。这是由HTML页面源代码中的换行引起的。

空白节点的解决方案

在开发中,空白节点的问题将DOM节点树结构的解析及操作增加了不少的难度和麻烦。这里提供一 种比较简单有效的解决方式:

1.弃用DOM中Node对象用于获取指定节点的子节点和兄弟节点的属性。

2通过使用geElementsByTagName()方法实现相应功能。

比如要查找HTML页面指定元素的所有子节点的话,可以按照如下代码示例实现:

var parentNode = document.getElementById('parent');
var children = parentNode.getElementsByTagName('button');
console.log(children);

获取相邻兄弟节点

通过HTML页面中指定元素查找其相邻兄弟节点,我们可以通过以下Node对象的属性实现:

属性名描述
perviousSibing获取指定节点的前面相邻兄弟节点
nextSibing获取指定节点的后面相邻兄弟节点

如下代码展示了获取相邻兄弟节点的操作:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>获取相邻兄弟节点</title>
    <script src="myTools%20-%203.0.js"></script>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    var mi = document.getElementById('mi');

    var prev = mi.previousSibling;
    prev = prev.previousSibling;
    console.log(prev);

    console.log(prev.previousSibling);//空白节点
    console.log(prev.previousSibling.previousSibling);//null

    var next = mi.nextSibling;
    next = next.nextSibling;
    console.log(next);
</script>
</body>
</html>

上述代码运行结果如下:

aOnise.png


appendChild()方法

Node对象提供的appendChild()方法可以向指定节点的子节点列表的最后添加一个新的子节点。

var child = node.appendChild(child)
  • appendChild()方法的参数child表示添加的子节点,同时盖子节点也是appendChild()方法的返回值。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>appendChild()方法</title>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    var parent = document.getElementById('parent');

    var newLi = document.createElement('li');
    var textNode = document.createTextNode('华为');
    newLi.appendChild(textNode);
    /*
            parentNode.appendChild(childNode)
        *说明
            * parentNode - 表示指定节点(作为父节点)
            *childNode - 表示被添加的节点(作为子节点)
        *特点 — 被添加的指定节点所有子节点列表的最后
    */
    parent.appendChild(newLi);
</script>
</body>
</html>

insertBefore()方法

Node对象除了提供了appendChild()方法可以实现插人节点之外,还提供了insertBefore()方法同样可以实现插入节点的功能。

var insertedElement = parentElement.insertBefore(newElement,referenceElement)
  • 参数referenceElement表示指定节点的某个子节点。
  • 参数newElement表示插人的节点。
  • 调用insertBefore()方法的parentElement表示指定的节点。
  • 作为返回值的insertedElement表示被插人的节点,即newElement。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>insertBefore()方法</title>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<script>
    // 获取指定父节点
    var parent = document.getElementById('parent');
    // 创建新的子节点
    var newLi = document.createElement('li');
    var textNode = document.createTextNode('华为');
    newLi.appendChild(textNode);
    //获取目标节点
    var mi  = document.getElementById('mi');
    //使用insertBefore()方法将新创建的子节点插入到目标节点的前面
    parent.insertBefore(newLi,mi);

    //DOM中的Node对象并没有提供 insertAfter()方法
    
</script>
</body>
</html>

得出问题如果插入到节点的后面怎么做?是使用insertAfter()方法吗?不,DOM中的Node对象并没有提供 insertAfter()方法。

解决方法如下:

// 第一种
function insertAfter(parentNode,newChild,currentChild) {
    var nextNode = currentChild.nextSibling.nextSibling;
    if (nextNode === null){
        parentNode.appendChild(nextNode);
    }
}

// 第二种
Object.defineProperty(Node.prototype,'insertAfter',{
    value : function (newChild,currentChild) {
        var nextNode = currentChild.nextSibling.nextSibling;
        if (nextNode === null){
            this.appendChild(newChild);
        }else{
            this.insertBefore(newChild,nextNode);
        }
    }
})

解析图如下:

aOaYkV.png


删除节点

Node对象提供了removeChild()方法实现从HTML页面中删除指定节点。

var oldChild = node.remoxeChild(child);
OR
element.removeChild(child);
  • 调用removeChild(方法的node表示child参数的父节点。
  • child参数则表示要删除的那个节点。

oldChild则用于存储要删除的节点的引用,即oldChild === child。当然,如果我们需要完成的仅仅只是删除节点操作的话,并不需要定义变量来存储被删除的节点。

注意:在上述语法结构中,如果child参数不是node的子节点的话,调用该方法时会报错。

示例代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>删除节点</title>
</head>
<body>
<ul id="parent">
    <li>苹果</li>
    <li id="mi">小米</li>
    <li>锤子</li>
</ul>
<li id="huawei">华为</li>
<script>
    var parent = document.getElementById('parent');
    var mi = document.getElementById('mi');
    parent.removeChild(mi);

    // 删除不是对应父节点中的子节点会报错
    var huawei = document.getElementById('huawei');
    parent.removeChild(huawei);
</script>
</body>
</html>