# 节点操作

网页中所有内容都是节点(标签、属性、文本、注释等),在DOM中,节点使用node来表示。

HTML DOM树中所有节点均可通过JavaScript进行访问,所有HTML元素(节点)均可被修改,也可以创建或删除。

注意:实现开发中,节点操作主要操作的是元素节点。

# - 节点的组成

节点拥有节点类型(nodeType)、节点名称(nodeName)、节点值(nodeValue)这三个基本属性。

# 节点类型

注:红色加粗为常用的节点类型

节点类型 描述
1 Element 代表元素
2 Attr 代表属性
3 Text 代表元素或属性中的文本内容
4 CDATASection 代表文档中的 CDATA 部(不会由解析器解析的文本)
5 EntityReference 代表实体引用
6 Entity 代表实体
7 ProcessingInstruction 代表处理指令
8 Comment 代表注释
9 Document 代表整个文档(DOM 树的根节点)
10 DocumentType 向为文档定义的实体提供接口
11 DocumentFragment 代表轻量级的 Document 对象(文档的某个部分)
12 Notation 代表 DTD 中声明的符号

# - nodeName

元素节点的 nodeName 是标签名称(大写)

属性节点的 nodeName 是属性名称

文本节点的 nodeName 永远是 #text

文档节点的 nodeName 永远是 #document

# - nodeValue

对于文本节点,nodeValue 属性包含文本。

对于属性节点,nodeValue 属性包含属性值。

文档节点和元素节点,nodeValue 属性的值始终为 null

# - 节点层级关系获得元素

img

# - parentNode与parentElement

node.parentNode:返回某节点最近的一个父节点,如果没有父节点则返回null。

node.parentElement:返回某节点最近的一个父元素,如果没有父元素则返回null。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <div id="root">
      <div id="app"></div>
  </div>
</body>
<script>
    // parentNode与parentElement唯一的区别:
    // parentNode查找的是父节点,parentElement查找的是父元素。当前的节点元素如果是html,那么
    // html.parentNode ====> #document
    // html.parentElement ====> null


  // node.parentNode:返回某节点最近的一个父节点,如果没有父节点则返回null。
  var app = document.querySelector("#app");// dom 元素。----->element
  // console.log(app.parentNode);// div#root
  // 节点拥有节点类型(nodeType)、节点名称(nodeName)、节点值(nodeValue)这三个基本属性。
  // console.log(app.parentNode.nodeType);//节点类型  1
  // console.log(app.parentNode.nodeName);//节点名称  DIV
  // console.log(app.parentNode.nodeValue);//节点值   null

  // 通过元素的层级关系获得的。
  // console.log(app.parentNode);// div#root
  // 通过DOM元素操作获得
  // console.log(document.querySelector("#root"));
  // 通过元素的层级关系获得的===通过DOM元素操作获得
  // console.log(app.parentNode === document.querySelector("#root"));// true


  // console.log(app.parentNode);// div#root
  // console.log(app.parentNode.parentNode,
  //               app.parentNode.parentNode===document.body);//body true
  // console.log(app.parentNode.parentNode.parentNode,
  //           app.parentNode.parentNode.parentNode===document.documentElement);//html true
  // console.log(app.parentNode.parentNode.parentNode.parentNode);//document
  // console.log(app.parentNode.parentNode.parentNode.parentNode.parentNode);//null


  // node.parentElement:返回某节点最近的一个父元素,如果没有父元素则返回null。
  console.log(app.parentElement);// div#root
  console.log(app.parentElement.parentElement);// body
  console.log(app.parentElement.parentElement.parentElement);// html
  console.log(app.parentElement.parentElement.parentElement.parentElement);//  null
</script>
</html>

# - childNodes与children

node.childNodes:返回包含指定节点的所有子节点的集合,包括元素节点,文本节点等。

node.children:返回指定元素的所有子元素集合

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="root">
        <p></p>
        <p>稻香</p>
        <p>哪里都是你</p>
        <!-- yyds 周杰伦 -->
    </div>
</body>
<script>
    // node.childNodes:返回包含指定节点的所有子节点的集合,包括元素节点,文本节点等。
    // childNodes返回是一个伪数组。
    var root = document.querySelector("#root");
    // console.log(root.childNodes);
    // root.childNodes.forEach(function (item,index){
    //     console.group("**********"+index+"************")
    //     console.log("nodeType:",item.nodeType);
    //     console.log("nodeName:",item.nodeName);
    //     console.log("nodeValue:",item.nodeValue);
    //     console.groupEnd();
    // })

    // 伪数组===>真数组方法1
    // Array.from(root.childNodes).map(function (){
    //
    // });

    // 伪数组====》真数组方法2
    // 下义一个数组
    // var nodeArr = [];
    // root.childNodes.forEach(function (item){
    //     nodeArr.push(item);
    // })
    // // 通过map映射出来一个新的数组,数组中的元素为节点对应的类型。
    // var arrType = nodeArr.map(function (item){
    //     return item.nodeType;
    // })
    // console.log(arrType);


    // 如果只想得到元素节点,如何去做
    // 方法1:通过forEeach
    // var arrElement = []
    // root.childNodes.forEach(function (item){
    //     // 当nodeType为1说明是元素节点
    //     if(item.nodeType===1){
    //         arrElement.push(item);
    //     }
    // })
    // console.log(arrElement);// 元素节点数组


    // 方法2:通过filter
    // var arrElement = Array.from(root.childNodes).filter(function (item){
    //     return item.nodeType === 1;// 得到满足条件的元素
    // })
    // console.log(arrElement);



    // node.children:返回指定元素的所有子元素集合。
    // console.log(root.children);// 得到的是一个伪数组(HTMLCollection),数组内的元素都是元素节点。

    // HTMLCollection集合是没有forEach方法的
    // root.children.forEach();

    // 遍历1:for
    // console.log(root.children.length);// 3
    // for(var i=0;i<root.children.length;i++){
    //     console.log(root.children[i])
    // }

    // 遍历2:forEach
    Array.from(root.children).forEach(function(item){
        console.log(item);
    })

</script>
</html>

# - firstChild与 firstElementChild

firstChild: IE6、7、8 第一个元素节点; 非IE6、7、8:返回第一个节点

firstElementChild: IE6、7、8不支持;非IE6、7、8,获取第一个元素节点

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <p>1</p>
    </div>
</body>
<script>
    // firstChild:在非IE6、7、8中得到的是第一个子节点。
    //            在IE6、7、8中得到的是第一个子元素。
    // - firstChild: IE6、7、8 第一个元素节点; 非IE6、7、8:返回第一个元素节点或文本节点
    var app = document.getElementById("app");
    // 非IE6、7、8:返回第一个元素节点或文本节点
    // console.log(1111,app.firstChild);// #text 文本

    // IE6、7、8 第一个子元素节点;
    // console.log(1111,app.firstChild);// 元素节点


    // - firstElementChild:
    // IE6、7、8不支持;非IE6、7、8,获取第一个元素节点

    // E6、7、8不支持
    // console.log(app.firstElementChild);// undefined
    // 非IE6、7、8,获取第一个元素节点
    console.log(app.firstElementChild);
</script>
</html>

# - lastChild与 lastElementChild

lastChild:非IE678 当中得到的是最后一个子节点。在IE678中得到的是最后一个子元素。

lastElementChild :非IE678中得到的是最后一个子元素,不支持IE678

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="root">
        <p>1</p>
    </div>
</body>
<script>
    // lastChild:非IE678 当中得到的是最后一个子节点。在IE678中得到的是最后一个子元素。
    var root = document.getElementById("root");
    // console.log(root.lastChild);
    // lastElementChild :非IE678中得到的是最后一个子元素,不支持IE678
    console.log(root.lastElementChild);
</script>
</html>

# - nextSibling与nextElementSibling

nextSibling:在非IE678中得到的是兄弟级别的下一个节点,在IE678中得到的是下一个元素。

nextElementSibling:在非IE678中得到的是下一个元素节点。在IE678中不支持

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <div>
    <p id="one">1</p>
    <p id="two">2</p>
    <p id="three">3</p>
  </div>
</body>
<script>
     var two = document.getElementById("two");
     // * nextSibling:在非IE678中得到的是兄弟级别的下一个节点,在IE678中得到的是下一个元素。
     // console.log(two.nextSibling);
     // * nextElementSibling:在非IE678中得到的是下一个元素节点。在IE678中不支持
     console.log(two.nextElementSibling);

</script>
</html>

# - previousSibling与previousElementSibling

previousSibling:在非IE678中得到的是兄弟级别的上一个节点,在IE678中得到的是下一个元素。

previousElementSibling:在非IE678中得到的是上一个元素节点。在IE678中不支持

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
<div>
  <p id="one">1</p>
  <p id="two">2</p>
  <p id="three">3</p>
</div>
</body>
<script>
  var two = document.getElementById("two");
  // * previousSibling:在非IE678中得到的是兄弟级别的上一个节点,在IE678中得到的是下一个元素。
  // console.log(two.previousSibling);
  // * previousElementSibling:在非IE678中得到的是上一个元素节点。在IE678中不支持
  console.log(two.previousElementSibling);

</script>
</html>

# - 操作节点

# - createElement

document.createElement('tagName')方法创建由tagName指定的HTML元素。因为这些元素原先不存在,是根据我们的需求动态生成的,所以我们也称为动态创建元素元点

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <div id="app">
    <ul></ul>
  </div>
</body>
<script>
    // 需求:在ul中插入<li>我的团长我的团</li>
    // 1- innderHTML
    // var ul = document.querySelector("#app ul");
    // ul.innerHTML = "<li>我的团长我的团</li>";

    // 2- createElement
    // createELement是document下的方法。该方法接收的字符串即是标签名。
    // 2-1:创建元素
    var li = document.createElement("li");// 创建了一个名字为li的DOM元素。
    li.innerText = "我的团长我的团";
    li.style.color = "red";
    var ul = document.querySelector("#app ul");
    // 2- 将创建的li放入到ul中。
    ul.appendChild(li);// 追加到ul的尾部
</script>
</html>

# - appendChild

node.appendChild(child):将一个节点添加到指定父节点的子节点列表末尾。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <p>刘德华</p>
    </div>
</body>
<script>
    var app = document.querySelector("#app");
    // 创建一个元素
    var p = document.createElement("p");
    // 为元素添加内容
    p.innerHTML = "郭富城";
    // 将p元素放入到app元素尾部
    app.appendChild(p);

</script>
</html>

# - insertBefore

node.insertBefore(child,指定元素):将一个节点添加到父节点的指定子节点前面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li>霸道总裁爱上你</li>
        <li>还珠格格</li>
        <li>斗罗大陆</li>
    </ul>
</body>
<script>
    // 1- 创建元素li
    var ul = document.querySelector("ul");
    var li = document.createElement("li");
    li.innerText = "斗破苍穹";
    li.style.fontWeight="bold";
    // ul.appendChild(li);//  放置在尾部
    ul.insertBefore(li,ul.lastElementChild);//  放置在斗罗大陆的前面。

    // 将li放在最上面
    // 第一个参数是指定插入的元素,第二个参数指定放在哪个元素的前面。
    // 要将第一个参数放置在第二个参数的前面
    // var lis = ul.querySelectorAll("li");
    // ul.insertBefore(li,lis[0])
    // ul.insertBefore(li,ul.firstElementChild);

</script>
</html>

# - removeChild

node.removeChild():从DOM中删除一个子节点,返回删除的节点

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<button>移除最后一个li</button>
<button>移除最上面的li</button>
<button>将最上面的li移动到最下边</button>
<ul>
    <li>霸道总裁爱上你</li>
    <li>还珠格格</li>
    <li>斗罗大陆</li>
</ul>
</body>
<script>
    // var ul = document.querySelector("ul");
    // // 1- 获得要移除的元素
    // var lis = document.querySelectorAll("ul li");
    // // 移除下标为0
    // ul.removeChild(lis[0]);
    // // 移除下标为1
    // ul.removeChild(lis[1]);
    // // 移除下标为2
    // ul.removeChild(lis[2]);

    var btns = document.querySelectorAll("button");
    var ul = document.querySelector("ul");
    btns[0].onclick = function(){
        ul.removeChild(ul.lastElementChild);
        console.log(ul.children.length);
        // 如果ul没有子元素节点,那么让按钮失败
        if(ul.children.length===0) this.disabled = true;
    }
    btns[1].onclick = function (){
        // removeChild会将删除的节点进行返回。
        var removeNode  = ul.removeChild(ul.firstElementChild);
        console.log(removeNode);
        // 如果ul没有子元素节点,那么让按钮失败
        if(ul.children.length===0) this.disabled = true;
    }
    btns[2].onclick = function(){
        // removeNode是删除的元素节点。
        var removeNode = ul.removeChild(ul.firstElementChild);
        ul.appendChild(removeNode);
    }
</script>
</html>

# - cloneNode

node.cloneNode():返回调用该方法的节点的一个副本。也称为克隆节点/拷贝节点。

如果参数为空或者为false,是浅拷贝,即只克隆复制节点本向,不克隆里面的子节点。

如果括号参数为true,则是深度拷贝,会复制节点本身以及里面所有的子节点。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li>霸道总裁爱上你</li>
        <li>还珠格格</li>
        <li>斗罗大陆</li>
    </ul>
</body>
<script>
    var ul = document.querySelector("ul");
    // var lis = document.querySelectorAll("li");
    // console.log(lis[0].cloneNode());// 复制节点
    // console.log(lis[0].cloneNode(true));// 复制节点以及节点所包裹的内容。

    // ul.appendChild(lis[0].cloneNode(true))

    // lis[0].parentElement.appendChild(lis[1].cloneNode(true));

    ul.appendChild(ul.children[2].cloneNode(true));

</script>
</html>

# - 案例-找徐峥

  • 效果

  • 代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            * {
                padding: 0;
                margin: 0;
            }
    
            #app {
                width: 500px;
                height: 500px;
                margin: 0 auto;
                border: 2px solid black;
                font-size: 0;
            }
        </style>
    </head>
    <body>
    <div id="app">
    
    </div>
    </body>
    <script>
        // 获得app元素
        var app = document.querySelector("#app");
        // 指定列数
        init(2);
    
        function getRandom(min, max) {
            return Math.floor(Math.random() * (max - min + 1) + min);
        }
    
        function init(col) {
            var maxWidth = app.clientWidth / col;
            var activeIndex = getRandom(0, col * col - 1)
            for (var i = 0; i < col * col; i++) {
                var img = new Image();
                img.width = img.height = maxWidth;
                // 判断随机数是否与i相同,相同则为徐峥
                img.src = activeIndex === i ? "./img/1.png" : "./img/2.png";
                img.dataset.index = i;
                img.onclick = function () {
                    // console.log(this.dataset.index/1===activeIndex);
                    if (this.dataset.index / 1 === activeIndex) {
                        // 如果选中的是徐峥那么应该加大难度。2-》3
                        alert("恭喜您 ,选对啦");
                        app.innerHTML = "";
                        init(++col);
                    } else {
                        alert("选择错误")
                    }
                }
                app.appendChild(img);
            }
        }
    </script>
    </html>
    

# - 案例-钟表

  • 效果

  • 代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            *{
                padding:0;
                margin:0;
            }
            #app{
                width:400px;
                height: 400px;
                border:2px solid black;
                margin:0px auto;
                border-radius: 100%;
                position: relative;
            }
            .kedu{
                position: absolute;
                top:0;
                /*(400-4)/2*/
                left:198px;
                width: 4px;
                height:20px;
                background:black;
                transform-origin: 2px 200px;
            }
            .timeDiv{
                position: absolute;
                /*(400-宽40)/2*/
                left:180px;
                /*刻度的高度*/
                top:30px;
                width: 40px;
                height:170px;
                /*background: red;*/
                transform-origin: bottom;
                font-size:25px;
            }
            /*时针*/
            .hour{
                position: absolute;
                width: 6px;
                left:197px;
                top:130px;
                height: 70px;
                background: green;
                transform-origin: bottom;
            }
            /*分针*/
            .min{
                position: absolute;
                width: 4px;
                left:198px;
                top:100px;
                height: 100px;
                background: skyblue;
                transform-origin: bottom;
            }
            /*秒针*/
            .sec{
                position: absolute;
                width: 2px;
                left:199px;
                top:60px;
                height: 140px;
                background: red;
                transform-origin: bottom;
            }
            .origin{
                position:absolute;
                width: 20px;
                height: 20px;
                background: black;
                top:190px;
                left:190px;
                border-radius: 100%;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <div class="hour"></div>
            <div class="min"></div>
            <div class="sec"></div>
            <div class="origin"></div>
        </div>
    </body>
    <script>
        var app = document.querySelector("#app");
    
        // 让时分秒动起来:
        var hour = document.querySelector(".hour");
        var min = document.querySelector(".min");
        var sec = document.querySelector(".sec");
    
        init();
        function init(){
            // 获得当前时间
            var date = new Date();
            var h = date.getHours();// 小时
            var m = date.getMinutes();// 分
            var s = date.getSeconds();// 秒
    
            // 1- 每小时占30度
            // 2- 每小时共60分钟
            // 由以上两点可以得出:每分钟占用 30/60 = 0.5
    
            // 1-每分钟占6度
            // 2-每分钟有60秒
            // 由以上两点可以得出:每秒钟占用 6/60=0.1
            hour.style.transform = "rotateZ("+(h*30+m*0.5)+"deg)";// 360/12
            min.style.transform = "rotateZ("+(m*6+s*0.1)+"deg)";// 360/60
            sec.style.transform = "rotateZ("+s*6+"deg)";// 360/60
        }
    
        setInterval(init,1000);
    
        // 1- 增加刻度
        for(var i=0;i<60;i++){
            // 创建div元素
            var div = document.createElement("div");
            // 为div增加样式
            div.className = "kedu";
            // 旋转
            div.style.transform = "rotateZ("+i*6+"deg)";
            // 判断是否被5整除
            if(i%5===0){
                div.style.height = "30px"
            }
            // 将创建的元素div放到app元素的尾部
            app.appendChild(div);
        }
        // 2- 增加时间
        for(var i=1;i<=12;i++){
            var timeDiv = document.createElement("div");
            timeDiv.className = "timeDiv";
            timeDiv.style.transform = "rotateZ("+i*30+"deg)"
    
            var numDiv = document.createElement("div");
            numDiv.innerHTML = i;
            numDiv.style.transform = "rotateZ("+(-i*30)+"deg)";
            numDiv.style.textAlign="center";
            timeDiv.appendChild(numDiv)
            app.appendChild(timeDiv);
        }
    </script>
    </html>