认识DOM

104 阅读6分钟

DOM

一、认识DOM

JavaScript是一门编程语言,它的运行环境有两个,一个是浏览器,一个是node,前面我们学的JS必于ECMAScript中的语法,浏览器的JS有三部分组成:

  • ECMAScript
  • DOM
  • BOM

ECMAScript并不能和和网页进行交互,操作浏览器(网页),就需要给我们提供一个API,我们去调用API,实现操作,学习DOM和BOM就是学习浏览器给我们提供的API,所以说,DOM和BOM操作,也叫WebAPI。

深入理解DOM

  • 浏览器将我们编写在HTML中的每一个元素(Element)都抽象成了一个个对象
  • 所有这些对象都可以通过JavaScript来对其进行访问,那么我们就可以通过JavaScript来操作页面
  • 将这个抽象过程称之为 文档对象模型(Document Object Model)

DOM:Docuemnt Object Model 文档对象模型

  • 文档:html文档 之前的html文件就可以称一个文档
  • Object: 对象 一切都是对象 所有的元素都是对象
  • Model:模型 树模型 所有的元素,要形成一个树模型

整个文档被抽象到 document 对象中

  • 如document.documentElement对应的是html元素
  • 如document.body对应的是body元素
  • 如document.head对应的是head元素
 <script>
 //下面的一行代码可以让整个页面变成红色
 document.body.style.background = 'good'
 </script>

DOM树

  • 在html结构中,最终会形成一个树结构

前面我们学习了一个window的全局对象,window上事实上就包含了这些内容

  • JavaScript语法部分的Object、Array、Date等
  • DOM
  • BOM

document对象

Document节点表示的整个载入的网页,它的实例是全局的document对象

  • 对DOM的所有操作都是从 document 对象开始的
  • 它是DOM的 入口点,可以从document开始去访问任何节点元素

对于最顶层的html、head、body元素,我们可以直接在document对象中获取到

  • document.documentElement 得到 html元素
  • document.body 得到 body元素
  • document.head 得到 head元素
  • document.doctype 得到 文档声明元素
 <body>
     <script>
         // document是内置对象  也是GO中的
         // 一个节点就是一个对象
         // 对象是属性和方法的无序集合  api
         // 学习DOM操作就学习一个对象中的属性或方法  api
         // console.dir(document);
 ​
         // 获取head元素节点
         // console.log(document.head);
 ​
         // 获取title元素节点中的文本节点
         // console.log(document.title);
 ​
         // 获取body元素节点
         // console.log(document.body);
 ​
         document.write("<h1>Hello DOM</h1>")
     </script>
 </body

节点与节点关系

节点分类:

  • 元素节点
  • 文本节点
  • 属性节点
  • 注释节点
  • ...

注意点:

  • 在DOM树上,只需要关注元素节点和文本节点,在DOM上,没有属性节点。
  • 我们只需要关注三种节点:1)元素节点 2)文本节点 3)属性节点
 <body>
     <!-- 我是一个注释 -->
     <div id="box" title="haha">我是一个DIV</div>
     <a href="http://www.baidu.com">百度一下</a>
 ​
     <script>
         let oDiv = document.getElementById("box");
         // nodeType判断一个节点的类型
         // 如果是元素节点  它的nodeType是1
         console.log(oDiv.nodeType);  // 1 
 ​
         // 要获取属性节点,必须先得到元素节点
         // 通过打点的形式,就可以获取属性节点
         // console.log(oDiv.title); // haha
 ​
         // getAttribute 根据属性名获取属性值的
         console.log(oDiv.getAttribute("title")); // haha
         // getAttributeNode 获取属性节点
         let attr = oDiv.getAttributeNode("title"); // 2
         console.log(attr.nodeType);
 ​
         let text = oDiv.firstChild;
         console.log(text);  // 我是一个DIV
         console.log(text.nodeType); // 3
     </script>
 </body>

节点之间的关系一:

  • 获取到一个节点(Node)后,可以根据这个节点去获取其他的节点,我们称之为节点之间的关系
  • 父节点:parentNode
  • 前兄弟节点:previousSibling
  • 后兄弟节点:nextSibling
  • 子节点:childNodes
  • 第一个子节点:firstChild
  • 最后一个子节点:lastChild

 <body>
     <!-- 我是一个注释 -->
     我是一个文本
     <div class="box">
         我是一个孤独的DIV
     </div>
     <ul>
         <li>One</li>
         <li>Two</li>
         <li>Three</li>
     </ul>
     <!-- <script>
         // 获取body的元素节点
         let bodyEle = document.body;
 ​
         console.log(bodyEle.firstChild);  // #text 换行节点
 ​
         console.log(bodyEle.firstChild.nextSibling);
 ​
         console.log(bodyEle.parentNode);
     </script> -->
 ​
     <script>
         // 通过节点关系去获取某些节点,非常麻烦,因为需要考虑换行节点和注释节点
         let bodyEle = document.body;
         console.log(bodyEle.firstChild.nextSibling.nextSibling.nextSibling);
     </script>
 </body>

节点之间的关系二:

  • 获取到一个节点(Node)后,可以根据这个节点去获取其他的节点,我们称之为节点之间的关系
  • 父节点:parentElement
  • 前兄弟节点:previousElementSibling
  • 后兄弟节点:nextElementSibling
  • 子节点:children
  • 第一个子节点:firstElementChild
  • 最后一个子节点:lastElementChild
 <body>
     <!-- 我是一个注释 -->
     我是一个文本
     <div class="box">
         我是一个孤独的DIV
     </div>
     <ul>
         <li>One</li>
         <li>Two</li>
         <li>Three</li>
     </ul>
     <script>
         let bodyEle = document.body;
         console.log(bodyEle.firstElementChild);
         console.log(bodyEle.firstElementChild.nextElementSibling);
         // 得到一个伪数组
         console.log(bodyEle.firstElementChild.nextElementSibling.children);
     </script>
 </body>

节点关系总结(加粗的是需要记的):

  • ☆parentNode 获取父元素节点 没有兼容性问题
  • ☆arentElement 获取父元素节点 没有兼容性问题
  • firstChild 获取第一个子节点 会考虑换行节点和注释节点 不要用
  • ☆firstElementChild 获取第一个子元素节点 可以使用
  • lastChild 获取最后一个子节点 会考虑换行节点和注释节点 不要用
  • ☆lastElementChild 获取最后一个子元素节点 可以使用
  • nextSibling 获取下一个兄弟节点 会考虑换行节点和注释节点 不要用
  • ☆nextElementSibling 获取下一个兄弟元素节点 可以使用
  • previousSibling 获取上一个兄弟节点 会考虑换行节点和注释节点 不要用
  • ☆previousElementSibling 获取上一个兄弟元素节点 可以使用

获取元素节点的方法

通过节点关系可以得到某个元素,但是,在实际开发中,我们希望可以任意的获取到某一个元素应该如何操作呢?

DOM为我们提供了获取元素的方法:

最常用的几个方法如下:

  • document.getElementById
  • document.getElementsByTagName
  • document.querySelectorAll
  • document.querySelector
 <body>
     <div id="box" name="a" class="item">我是一个DIV1</div>
     <div id="box" name="a" class="item">我是一个DIV2</div>
 ​
     <p class="father">
         <span class="son">son</span>
     </p>
 ​
     <ul class="wrap">
         <li>1</li>
         <li>2</li>
         <li>3</li>
     </ul>
     <script>
         // 1) ------  document.getElementById  获取1个
         // let oDiv = document.getElementById("box")
         // console.dir(oDiv);
 ​
         // 2) ------  document.getElementsByTagName 得到的是伪数组
         //     伪数组  本质是对象   Array.from()
         //     是文档中所有的div  要获取某个div,需要通过索引
         // let oDivs = document.getElementsByTagName("div")
         // console.log(oDivs)
         // console.log(oDivs[0])
 ​
         // 3) ------  document.getElementsByName 得到的是伪数组
         //     是文档中所有的div  要获取某个div,需要通过索引
         // let oDivs = document.getElementsByName("a")
         // console.log(oDivs)
         // console.log(oDivs[0])
 ​
         // 4) ------  document.getElementsByClassName 得到的是伪数组
         // let oDivs = document.getElementsByClassName("item")
         // console.log(oDivs)
         // console.log(oDivs[0])
 ​
         // 5) ------  document.querySelectorAll 得到的是伪数组
         // querySelector是选择器的意思    通过选择器来获取元素
         // querySelectorAll 获取多个
         // let lis = document.querySelectorAll("li")
         // console.log(Array.isArray(lis)); // 得到的也是一个伪数组
         // console.log(lis[0]);
 ​
         // 6) ------  document.querySelector 得到的是伪数组
         // querySelector 获取第1个
         // let li = document.querySelector("li")
         // console.log(li);
     </script>
 </body>

节点的属性

节点的属性之nodeType

不同的节点类型有可能有不同的属性,但它们有共有属性:

常见的节点类型有如下:

 <body>
     <!-- 我是一个注释 -->
     我是文本
     <div class="father">
         <h2>我是一个H2</h2>
         <p>我是内容</p>
     </div>
     <script>
         let bodyChildNodes = document.body.childNodes;
         let commentNode = bodyChildNodes[1];
         let textNode = bodyChildNodes[2];
         let divNode = bodyChildNodes[3];
 ​
         console.log(commentNode.nodeType);
         console.log(textNode.nodeType);
         console.log(divNode.nodeType);
 ​
         for(let node of bodyChildNodes){
             if(node.nodeType === 8){
                 console.log(node + "是注释节点");
             }else if(node.nodeType === 3){
                 console.log(node + "是文本节点");
             }else if(node.nodeType === 1){
                 console.log(node + "是元素节点");
             }
         }
     </script>
 </body>

节点的属性之nodeName

  • nodeName:获取node节点的名字;
  • tagName:获取元素的标签名词;

tagName 和 nodeName 之间有什么不同呢?

  • tagName 属性仅适用于 Element 节点;
  • nodeName 是为任意 Node 定义的
  • nodeName 对于元素,它的意义与 tagName 相同,所以使用哪一个都是可以的;
  • nodeName 对于其他节点类型(text,comment 等),它拥有一个对应节点类型的字符串;
 <body>
     <!-- 我是一个注释 -->
     我是文本
     <div class="father">
         <h2>我是一个H2</h2>
         <p>我是内容</p>
     </div>
     <script>
         let bodyChildNodes = document.body.childNodes;
         let commentNode = bodyChildNodes[1];
         let textNode = bodyChildNodes[2];
         let divNode = bodyChildNodes[3];
 ​
         // 得到节点名
         console.log(commentNode.nodeName);  // #comment
         console.log(textNode.nodeName);  // #text
         console.log(divNode.nodeName);  // #DIV
 ​
         console.log("-------------");
 ​
         console.log(commentNode.tagName);  // undefined
         console.log(textNode.tagName);  // undefined
         console.log(divNode.tagName);  // #DIV
     </script>
 </body>

节点的属性之innerHTML和textContent

innerHTML 属性

  • 将元素中的 HTML 获取为字符串形式
  • 设置元素中的内容;
<body>
    <!-- 我是一个注释 -->
    我是文本
    <div class="father">
        <h2>我是一个H2</h2>
        <p>我是内容</p>
    </div>
    <script>
        let bodyChildNodes = document.body.childNodes;
        let commentNode = bodyChildNodes[1];
        let textNode = bodyChildNodes[2];
        let divNode = bodyChildNodes[3];

        // 获取div标签中的内容
        console.log(divNode.innerHTML);
        // 设置div标签中的内容
        divNode.innerHTML = "<strong>我是一个Strong标签</strong>"
    </script>
</body>

outerHTML 属性

  • 包含了元素的完整 HTML
  • innerHTML 加上元素本身一样;
<body>
    <!-- 我是一个注释 -->
    我是文本
    <div class="father">
        <h2>我是一个H2</h2>
        <p>我是内容</p>
    </div>
    <script>
        let bodyChildNodes = document.body.childNodes;
        let divNode = bodyChildNodes[3];

        // 获取div标签中的内容  outerHTML相比innerHTML来说,带上自己本身
        console.log(divNode.outerHTML);

        // 对于设置来说,使用innerHTML多一点
        divNode.outerHTML = "<strong>我是一个Strong标签</strong>"
    </script>
</body>

textContent 属性

  • 仅仅获取元素中的文本内容;
<body>
    <!-- 我是一个注释 -->
    我是文本
    <div class="father">
        <h2>我是一个H2</h2>
        <p>我是内容</p>
    </div>
    <script>
        let bodyChildNodes = document.body.childNodes;
        let divNode = bodyChildNodes[3];

        // 只能获取文本节点
        console.log(divNode.textContent);

        //  设置也是针对文本节点,如果写了标签,也不会解析
        divNode.textContent = "<strong>我是一个Strong标签</strong>";
    </script>
</body>

innerHTML和textContent的区别:

  • 使用 innerHTML,我们将其“作为 HTML”插入,带有所有 HTML 标签。
  • 使用 textContent,我们将其“作为文本”插入,所有符号(symbol)均按字面意义处理。

节点的属性之nodeValue

  • 用于获取非元素节点的文本内容
<body>
    <!-- 我是一个注释 -->
    我是文本
    <div class="father">
        <h2>我是一个H2</h2>
        <p>我是内容</p>
    </div>
    <script>
        let bodyChildNodes = document.body.childNodes;
        let commentNode = bodyChildNodes[1];
        let textNode = bodyChildNodes[2];
        let divNode = bodyChildNodes[3];

        // 获取注释节点中的内容
        console.log(commentNode.nodeValue);
        // 获取文本节点中的内容
        console.log(textNode.nodeValue);
        // 元素节点的nodeValue是null
        console.log(divNode.nodeValue);  // null
    </script>
</body>

节点的属性之hidden

  • 用于设置元素隐藏
<body>
    <button id="btn">Toggle</button>
    <div class="box" style="color: red;">
        Hello DOM~
    </div>
    <script>
        let btn = document.getElementById("btn");
        let div = document.getElementsByTagName("div")[0];
        
        // console.log("start...");

        // 给btn绑定点击事件  btn叫事件源    click叫点击事件(事件类型)
        // function(){} 事件处理程序,也叫监听器
        // 事件绑定是异步任务,是宏任务
        btn.onclick = function(){
            // console.log("click...");
            // 隐藏div
            // div.style.display = "none";  // 隐藏方式一
            // div.hidden = true; // 隐藏方式二

            // if(div.hidden === false){
            //     div.hidden = true;
            // }else{
            //     div.hidden = false;
            // }

            // 经典
            div.hidden = !div.hidden
        }
        // console.log("end...");
    </script>
</body>

创建和挂载节点

我们想要插入一个元素,通常会按照如下步骤:

  • 步骤一:创建一个元素;
  • 步骤二:插入元素到DOM的某一个位置;

创建节点:

  • createElement 创建元素节点
  • createTextNode 创建文本节点
  • createComment 创建注释节点
  • createAttribute 创建属性节点

挂载节点:

  • node.append(...nodes or strings) —— 在 node 末尾 插入节点或字符串,
  • node.prepend(...nodes or strings) —— 在 node 开头 插入节点或字符串,
  • node.before(...nodes or strings) —— 在 node 前面 插入节点或字符串,
  • node.after(...nodes or strings) —— 在 node 后面 插入节点或字符串,
  • node.replaceWith(...nodes or strings) —— 将 node 替换为给定的节点或字符串。

<body>
    <div class="father">
        <div class="son">
            SON
        </div>
    </div>

    <script>
        let father = document.querySelector(".father");

        // 使用innerHTML也可以添加元素,不推荐
        // father.innerHTML = "<h2>我是一个H2标签</h2>";

        let h2Ele = document.createElement("h2");
        h2Ele.textContent = "我是H2标签";
        console.log(h2Ele);

        // 需要把h2挂载到DOM树上
        father.append(h2Ele);
        // father.prepend(h2Ele)
        // father.before(h2Ele)
        // father.after(h2Ele)
        // father.replaceWith(h2Ele)

        let spanEle = document.createElement("span");
        spanEle.textContent = "我是一个孤独的span";

        h2Ele.append(spanEle)
    </script>
</body>

删除,替换,克隆节点

删除节点:

  • removeChild 只有父元素才有资格删除一个子元素
  • remove 移除元素我们可以调用元素本身的remove方法:
<body>
    <div class="father">
        <div class="son">
            son
        </div>
    </div>
    <script>
        let father = document.querySelector(".father");
        let son = document.querySelector(".son");

        // son.remove, 自己移除自己
        // son.remove();

        // 父也有能力去移除它里面的元素
        father.removeChild(son)
    </script>
</body>

替换节点:

  • replaceChild
<body>
    <div class="father">
        <div class="son">
            son
        </div>
    </div>
    <script>
        let father = document.querySelector(".father");
        let son = document.querySelector(".son");

        let pEle = document.createElement("p");
        pEle.textContent = "GrandSon";

        father.replaceChild(pEle,son)

    </script>
</body>

如果我们想要复制一个现有的元素,可以通过cloneNode方法克隆节点:

  • 可以传入一个Boolean类型的值,来决定是否是深度克隆;
  • 深度克隆会克隆对应元素的子元素,否则不会;
<body>
    <div class="father">
        <div class="son">
            son
        </div>
    </div>
    <script>
        let father = document.querySelector(".father");
        let son = document.querySelector(".son");

        // 默认是浅copy  只copy一个节点,内部的其它节点不会copy
        // let newFather = father.cloneNode();
        // console.log(newFather);

        // 如果传递一个true表示深copy   如果不传或传一个false表示浅copy
        let newFather = father.cloneNode(true);
        console.log(newFather);
    </script>
</body>

\