• 前端基础入门之js什么是DOM Document Object Model


    DOM

    1、DOM简介

    DOM,全称Document Object Model 文档对象模型

    JS中通过DOM来对HTML文档进行操作。只要理解了DOM就可以随心所欲的操作WEB页面。

    文档

    文档表示的就是整个的HTML网页文档

    对象

    对象表示将网页中的每一个部分都转换为了一个对象

    模型

    使用模型来表示对象之间的关系,这样方便我们获取对象

    image-20210801152925307

    DOM树体现了节点与节点之间的关系

    image-20210801153018626

    2、节点

    节点Node,是构成我们网页的最基本的组成部分,网页中的每一个部分都可以称为是一个节点

    比如:html标签、属性、文本、注释、整个文档等都是一个节点

    虽然都是节点,但是实际上他们的具体类型是不同的。比如:

    • 标签称为元素节点
    • 属性称为属性节点
    • 文本称为文本节点
    • 文档称为文档节点

    节点的类型不同,属性和方法也都不尽相同

    节点类型

    节点:Node——构成HTML文档最基本的单元

    常用节点分为四类

    • 文档节点:整个HTML文档
    • 元素节点:HTML文档中的HTL标签
    • 属性节点:元素的属性
    • 文本节点:HTML标签中的文本内容

    image-20210801153554420

    节点属性

    image-20210801153633118

    文档节点(Document)

    文档节点document,代表的是整个HTML文档,网页中的所有节点都是它的子节点

    document对象作为window对象的属性存在的,我们不用获取可以直接使用

    通过该对象我们可以在整个文档访问内查找节点对象,并可以通过该对象创建各种节点对象

    元素节点(Element)

    HTML中的各种标签都是元素节点,这也是我们最常用的一个节点

    浏览器会将页面中所有的标签都转换为一个元素节点,我们可以通过document的方法来获取元素节点

    比如:document.getElementById() 根据id属性值获取一个元素节点对象。

    文本节点(Text)

    文本节点表示的是HTML标签以外的文本内容,任意非HTML的文本都是文本节点

    它包括可以字面解释的纯文本内容

    文本节点一般是作为元素节点的子节点存在的

    获取文本节点时,一般先要获取元素节点,再通过元素节点获取文本节点。例如:元素节点.firstChild;

    获取元素节点的第一个子节点,一般为文本节点

    属性节点(Attr)

    属性节点表示的是标签中的一个一个的属性,这里要注意的是属性节点并非是元素节点的子节点,而是元素节点的一部分

    可以通过元素节点来获取指定的属性节点。例如:元素节点.getAttributeNode("属性名");

    注意:我们一般不使用属性节点

    浏览器已经为我们提供文档节点对象,这个对象是window

    属性可以在页面中直接使用,文档节点代表的是整个网页

    // 获取button对象
    var btn = document.getElementById("btn");
    console.log(btn); // <button type="button" id="btn">我是一个按钮</button>
    // 修改btn的文本节点内容
    btn.innerHTML = "I'm a button.";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、事件

    事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间

    JavaScript与HTML之间的交互是通过事件实现的

    对于Web应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等

    我们可以在事件对应的属性中设置一些js代码,这样当事件被触发时,这些代码将会执行

    <button type="button" id="btn" onclick="alert('Fuck');">我是一个按钮</button>
    
    • 1

    这种写法我们称为结构和行为耦合,不方便维护,不推荐使用

    可以为按钮的对应事件绑定处理函数的形式来响应事件,这样当事件被触发时,其对应的函数将会被调用

    // 绑定一个单击事件
    btn.onclick = function(){
        alert("Don't touch me.");
    }
    
    • 1
    • 2
    • 3
    • 4

    像这种为单击事件绑定的函数,我们称为单击响应函数

    4、文档的加载

    当我们把script标签放到head中时,会报错UncaughtTypeError: Cannot set property 'innerHTML' of null,这是为什么呢?

    浏览器在加载一个页面时,是按照自上向下的顺序加载的,读取到一行就运行一行,如果将script标签写到页面的上边,在代码执行时,页面还没有加载,DOM对象也没有加载,会导致无法获取到DOM对象

    image-20210801160850690

    如果非要这么干,也不是没有办法

    onload事件会在整个页面加载完成之后才触发,可以为window对象绑定一个onload事件

    window.onload = function(){
        // 获取button对象
        var btn = document.getElementById("btn");
        // 绑定一个单击事件
        btn.onclick = function(){
            alert("Don't touch me.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    该事件对应的响应函数将会在页面加载完成之后执行,这样可以确保我们的代码执行时所有的DOM对象已经加载完毕了

    5、DOM查询

    获取元素节点

    image-20210803222352914

    通过document对象调用

    为了方便,定义一个通用的函数,专门用来为指定元素绑定单击响应函数

    // 参数:
    // 	idstr	要绑定单击响应函数的对象的id属性值
    // 	fun		事件的回调函数,当单击元素时,该函数将会被触发
    function myClick(idStr, fun){
        var btn = document.getElementById(idStr);
        btn.onclick = fun;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • getElementById() 通过id属性获取一个元素节点对象

      myClick("btn01", function () {
          // innerHTML 通过这个属性可以获取到元素内部的html代码
          alert(document.getElementById("bj").innerHTML); // 北京
      });
      
      • 1
      • 2
      • 3
      • 4
    • getElementsByTagName() 通过标签名获取一组元素节点对象

      myClick("btn02", function () {
          // getElementsByTagName()可以根据标签名来获取一组元素节点对象
          // 这个方法会给我们返回一个类数组对象,所有查询到的元素都会封装到对象中
          // 即使查询到的元素只有一个,也会封装到数组中返回
          var li_list = document.getElementsByTagName("li");
          alert(li_list.length); // 14
          
          var arr = [];
          for(var i=0;i<li_list.length;i++){
              arr.push(li_list[i].innerHTML);
          }
          alert(arr); // 北京,上海,东京,首尔,红警,实况,极品飞车,魔兽,IOS,Android,Windows Phone,IOS,Android,Windows Phone
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • getElementsByName() 通过name属性获取一组元素节点对象

      myClick("btn03", function () {
          var inputs = document.getElementsByName("gender");
          alert(inputs.length); // 2
          
          var arr = [];
          for(var i=0;i<inputs.length;i++){
              // innerHTML用于获取元素内战的HTML代码的
              // 如果需要读取元素节点属性,直接使用`元素.属性名`
              // 例子:`元素.id`  `元素.name`  `元素.value`
              arr.push(inputs[i].value); 
              // 注意:class属性不能采用这种方式,读取class属性时需要使用`元素.className`
              arr.push(inputs[i].className);
          }
          alert(arr); // male,hello,female,hello
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    练习:图片切换

    HTML代码

    <div class="outer">
        <p id="info">共5张图片,当前第1张</p>
        <img src="img/1.jpg" alt="冰棍"/>
        <button type="button" id="prev">上一张</button>
        <button type="button" id="next">下一张</button>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    CSS代码

    *{
        margin:0;
        padding:0;
    }
    
    .outer{
        width: 500px;
        margin: 50px auto;
        padding: 10px;
        background-color: greenyellow;
        /* 文本居中:内联样式当成是文本 */
        text-align: center;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    JS代码

    // 上一张
    var prev = document.getElementById("prev");
    // 下一张
    var next = document.getElementById("next");
    // 图片
    var img = document.getElementsByTagName("img")[0];
    // 信息
    var info = document.getElementById("info");
    // 图片集合
    var imgArr = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"];
    // 记录第几张
    var index = 0;
    // 上一张绑定单击相应事件
    prev.onclick = function(){
        // 循环切换
        index = (index < 0) ? imgArr.length - 1 : index;
        // 修改img的src属性,以切换图片
        img.src = imgArr[index];
        // 修改文字提示
        info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
        // 切换上一张
        index--;
    };
    // 下一张绑定单击相应事件
    next.onclick = function(){
        // 循环切换			
        index = (index > imgArr.length - 1) ? 0 :index;
        // 修改img的src属性,以切换图片
        img.src = imgArr[index];
        // 修改文字提示
        info.innerHTML = "共" + imgArr.length + "张图片,当前第" + (index + 1) + "张";
        // 切换下一张
        index++;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    效果

    图片切换

    获取元素节点的子节点

    获取元素节点的子节点

    通过具体的元素节点调用

    • getElementsByTagName()方法,返回当前节点的指定标签名后代节点

      myClick("btn04", function () {
          var city = document.getElementById("city");
          // 获取city下1i节点
          var list = city.getElementsByTagName("li");
          alert(list.length); // 4
          
          var arr = [];
          for(var i=0;i<list.length;i++){
              arr.push(list[i].innerHTML);
          }
          alert(arr); // 北京,上海,东京,首尔
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • childNodes属性,表示当前节点的所有子节点

      myClick("btn05", function () {
          var city = document.getElementById("city");
          // childNodes属性会获取包括文本节点在内的所有节点
          // 根据DOM标签标签间空白也会当成文本节点
          // 注意:在IE8及以下的浏览器中,不会将空白文本当成子节点
          // 所以该属性在IE8中会返回4个子元素,而其他浏览器是9个
          var list = city.childNodes;
          alert(list.length); // 9
          
          var arr = [];
          for(var i=0;i<list.length;i++){
              arr.push(list[i]);
          }
          alert(arr); // [object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text],[object HTMLLIElement],[object Text]
      });
      myClick("btn05", function () {
          var city = document.getElementById("city");
          // children属性可以获取当前元素的所有子元素
          var list = city.children;
          alert(list.length); // 4
          
          var arr = [];
          for(var i=0;i<list.length;i++){
              arr.push(list[i].innerHTML);
          }
          alert(arr); // 北京,上海,东京,首尔
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
    • firstChild属性,表示当前节点的第一个子节点

      myClick("btn06", function () {
          var phone = document.getElementById("phone");
          // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点)
          var firstChild = phone.firstChild;				
          alert(firstChild); // [object HTMLLIElement]
          alert(firstChild.innerHTML); // IOS
      });
      myClick("btn06", function () {
          var phone2 = document.getElementById("phone2");
          // firstChild可以获取到当前元素的第一个子节点(包括空白文本节点)
          var firstChild = phone2.firstChild;				
          alert(firstChild); // [object Text]
          alert(firstChild.innerHTML); // undefined
      });
      myClick("btn06", function () {
          var phone2 = document.getElementById("phone2");
          // firstElementchild不支持IE8及以下的浏览器,如果需要兼容他们尽量不要使用
          var firstElementChild = phone2.firstElementChild;				
          alert(firstElementChild); // [object HTMLLIElement]
          alert(firstElementChild.innerHTML); // IOS
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • lastChild属性,表示当前节点的最后一个子节点

      document.getElementById("btn062").onclick = function () {
          var phone = document.getElementById("phone");
          // children属性可以获取当前元素的所有子元素
          var lastChild = phone.lastChild;				
          alert(lastChild); // [object HTMLLIElement]
          alert(lastChild.innerHTML); // Windows Phone
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    获取父节点和兄弟节点

    image-20210803222448362

    通过具体的节点调用

    • parentNode属性,表示当前节点的父节点

      myClick("btn07", function () {
          var bj = document.getElementById("bj");
          var parentNode = bj.parentNode;				
          alert(parentNode); // [object HTMLUListElement]
          alert(parentNode.innerHTML);
          // <li id="bj">北京</li>
          // <li>上海</li>
          // <li>东京</li>
          // <li>首尔</li>
          
          // innerText
          // -该属性可以获取到元素内部的文本内容
          // -它和innerHTML类似,不同的是它会自动将htm1去除
          alert(parentNode.innerText);
          // 北京
          // 上海
          // 东京
          // 首尔
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • previousSibling属性,表示当前节点的前一个兄弟节点

      myClick("btn08", function () {
          var android = document.getElementById("android");
          // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
          var previousSibling = android.previousSibling;				
          alert(previousSibling); // [object HTMLLIElement]
          alert(previousSibling.innerHTML); // IOS
      });
      myClick("btn08", function () {
          var android2 = document.getElementById("android2");
          // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
          var previousSibling = android2.previousSibling;				
          alert(previousSibling); // [object Text]
          alert(previousSibling.innerHTML); // undefined
      });
      myClick("btn08", function () {
          var android2 = document.getElementById("android2");
          // previousElementSibling获取前一个兄弟元素,IE8及以下不支持
          var previousElementSibling = android2.previousElementSibling;				
          alert(previousElementSibling); // [object HTMLLIElement]
          alert(previousElementSibling.innerHTML); // IOS
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
    • nextSibling属性,表示当前节点的后一个兄弟节点

      myClick("btn082", function () {
          var android = document.getElementById("android");
          // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
          var nextSibling = android.nextSibling;				
          alert(nextSibling); // [object HTMLLIElement]
          alert(nextSibling.innerHTML); // Windows Phone
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    6、全选练习

    HTML代码

    <form method="post" action="">
        你爱好的运动是?<input type="checkbox" id="checkedAllBox" />全选/全不选 
    
        <br />
        <input type="checkbox" name="items" value="足球" />足球
        <input type="checkbox" name="items" value="篮球" />篮球
        <input type="checkbox" name="items" value="羽毛球" />羽毛球
        <input type="checkbox" name="items" value="乒乓球" />乒乓球
        <br />
        <input type="button" id="checkedAllBtn" value="全 选" />
        <input type="button" id="checkedNoBtn" value="全不选" />
        <input type="button" id="checkedRevBtn" value="反 选" />
        <input type="button" id="sendBtn" value="提 交" />
    </form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    全选

    document.getElementById("checkedAllBtn").onclick = function(){
        var items = document.getElementsByName("items");
        for(var i=0;i<items.length;i++){
            // 通过多选框的checked属性可以来获取或设置多选框的选中状态
            items[i].checked = true;
        }
        // 全选按钮也要同步选中
        document.getElementById("checkedAllBox").checked = true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    全不选

    document.getElementById("checkedNoBtn").onclick = function(){
        var items = document.getElementsByName("items");
        for(var i=0;i<items.length;i++){
            items[i].checked = false;
        }
        // 全选按钮也要同步不选中
        document.getElementById("checkedAllBox").checked = false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    反选

    document.getElementById("checkedRevBtn").onclick = function(){
        var items = document.getElementsByName("items");
        var flag = true;
        for(var i=0;i<items.length;i++){
            items[i].checked = !items[i].checked;
            if(!items[i].checked){
                flag = false;
            }
        }
        // 全选按钮也要同步选中或不选中
        document.getElementById("checkedAllBox").checked = flag;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    提交

    document.getElementById("sendBtn").onclick = function(){
        var items = document.getElementsByName("items");
        var arr = [];
        for(var i=0;i<items.length;i++){
            if(items[i].checked){
                arr.push(items[i].value);
            }
        }
        alert(arr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    全选/全不选

    document.getElementById("checkedAllBox").onclick = function(){
        var items = document.getElementsByName("items");
        for(var i=0;i<items.length;i++){
            // 在事件的响应函数中,响应函数是给谁绑定的this就是谁
            items[i].checked = this.checked; 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    items

    var flag;
    var items = document.getElementsByName("items");
    for(var i=0;i<items.length;i++){
        items[i].onclick = function(){
            flag = true;
            for(var j=0;j<items.length;j++){
                if(!items[j].checked){
                    flag = false;
                    break;
                }
            }
            document.getElementById("checkedAllBox").checked = flag;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    效果

    image-20210804215409752

    image-20210804215455471

    image-20210804215513918

    7、DOM查询的剩余方法

    document.body

    document中有一个属性body,它保存的是body的引用

    // 注意:如果script标签是定义在head中的,则这里需要window.onload = function(){}包裹,否则会出现null的情况
    var body = document.getElementsByTagName("body");
    console.log(body); // HTMLCollection [body]
    body = document.body;
    console.log(body); // <body></body>
    console.log(typeof body); // object
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    document.documentElement

    document.documentElement保存的是html根标签

    var html = document.documentElement;
    console.log(html);
    
    • 1
    • 2

    document.all

    document.all代表页面中所有的元素

    var all = document.all;
    console.log(all); // HTMLAllCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
    console.log(all.length); // 11
    console.log(typeof all); // undefined
    for(var i=0;i<all.length;i++){
        console.log(all[i]);
    }
    
    var el = document.getElementsByTagName("*");
    console.log(el); // HTMLCollection(11) [html, head, meta, title, script, script, script, body, script, script, script]
    console.log(all.length); // 11
    console.log(typeof all); // undefined
    for(var i=0;i<el.length;i++){
        console.log(el[i]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    document.getElementsByClassName()

    根据元素的class属性值查询一组元素节点对象

    getElementsByClassName()可以根据class属性值获取一组元素节点对象,但是该方法不支持IE8及以下的浏览器

    var boxs = document.getElementsByClassName("box");
    console.log(boxs); // HTMLCollection(3) [div.box, div.box, div.box]
    console.log(boxs.length); // 3
    console.log(typeof boxs); // object
    
    • 1
    • 2
    • 3
    • 4

    document.querySelector()

    需要一个选择器的字符串作为参数,可以根据一个CSS选择器来查询一个元素节点对象

    虽然IE8中没有getElementsByClassName()但是可以使用querySelector()代替

    使用该方法总会返回唯一的一个元素,如果满足条件的元素有多个,那么它只会返回第一个

    var div = document.querySelector(".box div");
    console.log(div.innerHTML); // I'm first div.
    boxs = document.querySelector(".box");
    console.log(boxs); 
    // <div class="box">
    // 		<div>I'm first div.</div>
    // </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    document.querySelectorAll()

    该方法和querySelector()用法类似,不的是它会将符合条件的元素封装到一个数组中返回

    即使符合条件的元素只有一个,它也会返回数组

    boxs = document.querySelectorAll(".box");
    console.log(boxs); // NodeList(3) [div.box, div.box, div.box]
    console.log(boxs.length); //3
    
    • 1
    • 2
    • 3

    8、DOM增删改

    document.createElement()

    可以用于创建一个元素节点对象,它需要一个标签名作为参数,将会根据该标签名创建元素节点对象,并将创建好的对象作为返回值返回

    document.createTextNode()

    可以用来创建一个文本节点对象,它需要一个文本内容作为参数,将会根据该内容创建文本节点,并将新的节点返回

    appendChild()

    向一个父节点中添加一个新的子节点,用法:父节点.appendChild(子节点);

    insertBefore()

    可以在指定的子节点前插入新的子节点,语法:父节点.insertBefore(新节点, 旧节点);

    replaceChild()

    可以使用指定的子节点替换已有的子节点,语法:父节点.replaceChild(新节点, 旧节点);

    removeChild()

    可以删除一个子节点,语法:父节点.removeChild(子节点);子节点.parentNode.removeChild(子节点);

    // 创建一个"广州"节点,添加到#city下
    var city = document.getElementById("city");
    myClick("btn01",function(){
        // 创建元素节点
        var li = document.createElement("li");
        // 创建文本节点
        var gz = document.createTextNode("广州");
        // 将文本节点添加到元素节点中
        li.appendChild(gz);
        // 将元素节点添加至#city下
        city.appendChild(li);
    });
    // 将"广州"节点插入到#bj前面	
    var bj = document.getElementById("bj");
    myClick("btn02",function(){
        var li = document.createElement("li");
        var gz = document.createTextNode("广州");
        li.appendChild(gz);
        // 将元素节点插入到#bj前面
        city.insertBefore(li,bj);
    });
    // 使用"广州"节点替换#bj节点
    myClick("btn03",function(){
        var li = document.createElement("li");
        var gz = document.createTextNode("广州");
        li.appendChild(gz);
        // 将元素节点替换#bj节点
        city.replaceChild(li,bj);
    });
    // 删除#bj节点
    myClick("btn04",function(){
        // 将元素节点替换#bj节点
        // city.removeChild(bj);
        // 更常用,不需要知道父节点是什么	
        bj.parentNode.removeChild(bj);
    });
    // 使用innerHTML将"广州"节点添加到#city下
    myClick("btn07",function(){
        // 使用innerHTML也可以完成DOM的增删改的相关操作
        // city.innerHTML += "<li>广州</li>";
        // 不过这种方式会先删除再替换,耗费性能,所以一般我们会两种方式结合使用
        var li = document.createElement("li");
        li.innerHTML = "广州";
        city.appendChild(li);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    9、增删练习

    准备

    image-20210805221937924

    HTML代码

    <table id="employeeTable">
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Salary</th>
            <th>&nbsp;</th>
        </tr>
        <tr>
            <td>Tom</td>
            <td>tom@tom.com</td>
            <td>5000</td>
            <td><a href="deleteEmp?id=001">Delete</a></td>
        </tr>
        <tr>
            <td>Jerry</td>
            <td>jerry@sohu.com</td>
            <td>8000</td>
            <td><a href="deleteEmp?id=002">Delete</a></td>
        </tr>
        <tr>
            <td>Bob</td>
            <td>bob@tom.com</td>
            <td>10000</td>
            <td><a href="deleteEmp?id=003">Delete</a></td>
        </tr>
    </table>
    
    <div id="formDiv">
        <h4>添加新员工</h4>
        <table>
            <tr>
                <td class="word">name: </td>
                <td class="inp">
                    <input type="text" name="empName" id="empName" />
                </td>
            </tr>
            <tr>
                <td class="word">email: </td>
                <td class="inp">
                    <input type="text" name="email" id="email" />
                </td>
            </tr>
            <tr>
                <td class="word">salary: </td>
                <td class="inp">
                    <input type="text" name="salary" id="salary" />
                </td>
            </tr>
            <tr>
                <td colspan="2" align="center">
                    <button id="addEmpButton" value="abc">
                        Submit
                    </button>
                </td>
            </tr>
        </table>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    JS代码

    // a的单击相应函数
    function delRow() {
        // 添加提示信息
        grandPrentNode = this.parentNode.parentNode;
        var name = grandPrentNode.children[0].innerHTML;
        if (confirm("确认删除" + name + "吗?")) {
            // 删除祖先节点
            grandPrentNode.parentNode.removeChild(grandPrentNode);
        }
        // 点击超链接以后,超链接会跳转页面,这个是超链接的默认行为,
        // 但是此时我们不希望出现默认行为,可以通过在响应函数的最后return false来取消默认行为
        return false;
    }
    
    window.onload = function() {
        // 1、删除
        // 为delete绑定单击相应函数
        var a;
        var grandPrentNode;
        var aList = document.getElementsByTagName("a");
        for (var i = 0; i < aList.length; i++) {
            aList[i].onclick = delRow;
        }
    
        // 2、添加
        document.getElementById("addEmpButton").onclick = function() {
            // 获取name/email/salary
            var empName = document.getElementById("empName").value;
            var email = document.getElementById("email").value;
            var salary = document.getElementById("salary").value;
    
            // 校验数据是否为空
            if (!empName || !email || !salary) {
                alert("有数据为空,无法添加!");
                return;
            }
    
            // 创建文本节点
            var empName_text = document.createTextNode(empName);
            var email_text = document.createTextNode(email);
            var salary_text = document.createTextNode(salary);
            var delete_text = document.createTextNode("Delete");
    
            // 创建元素节点
            var tr = document.createElement("tr");
            var empName_td = document.createElement("td");
            var email_td = document.createElement("td");
            var salary_td = document.createElement("td");
            var a_td = document.createElement("td");
            var a = document.createElement("a");
    
            // 添加内容
            a.href = "javascript:;";
            a.onclick = delRow;
    
            // 添加子节点
            empName_td.appendChild(empName_text);
            email_td.appendChild(email_text);
            salary_td.appendChild(salary_text);
            a.appendChild(delete_text);
            a_td.appendChild(a);
            tr.appendChild(empName_td);
            tr.appendChild(email_td);
            tr.appendChild(salary_td);
            tr.appendChild(a_td);
    
            // 将tr添加至table中
            // document.getElementById("employeeTable").appendChild(tr);
            // 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
            var employeeTable = document.getElementById("employeeTable");
            var tbody = employeeTable.getElementsByTagName("tbody")[0];
            tbody.appendChild(tr);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    添加优化

    结合createElementinnerHTML,优化修改上述添加代码逻辑

    document.getElementById("addEmpButton").onclick = function() {
        // 获取name/email/salary
        var empName = document.getElementById("empName").value;
        var email = document.getElementById("email").value;
        var salary = document.getElementById("salary").value;
    
        // 校验数据是否为空
        if (!empName || !email || !salary) {
            alert("有数据为空,无法添加!");
            return;
        }
    
        // 创建元素节点
        var tr = document.createElement("tr");
    
        // 添加子节点
        var empNameTd = "<td>" + empName + "</td>";
        var emailTd = "<td>" + email + "</td>";
        var salaryTd = "<td>" + salary + "</td>";
        var aTd = "<td><a href=\"javascript:;\">Delete</a></td>";
        tr.innerHTML = empNameTd + emailTd + salaryTd + aTd;
    
        // 为a绑定单击相应函数
        tr.getElementsByTagName("a")[0].onclick = delRow;
    
        // 将tr添加至table中
        // document.getElementById("employeeTable").appendChild(tr);
        // 注意:浏览器生成的table结构会在内部套一层tbody,为了以防万一,也为了结构一致性和样式一致性,应该将其添加至tbody中
        var employeeTable = document.getElementById("employeeTable");
        var tbody = employeeTable.getElementsByTagName("tbody")[0];
        tbody.appendChild(tr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    a的索引问题

    上述中,我们为每个a都添加了单击响应函数,使用了this获取遍历中的a元素,通过this.parentNode.parentNode获取了 tr 元素,如果这里改成aList[i].parentNode.parentNode,能够拿到 tr 元素吗?

    看起来好像毫无悬念,但实际上是拿不到的,这是为什么呢?

    我们可以改造下 for 循环中 a 元素的单击相应函数,打印下每次拿到的 i

    for (var i = 0; i < aList.length; i++) {
        aList[i].onclick = function(){
            alert(i);
            return false;
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image-20210806192603271

    会发现,每次打印的结果都是3,而 aList 的长度为3最大索引是2

    原因其实很简单,因为单击相应函数的执行是晚于 for 循环的执行的。也就是说,我们在点击 Delete 前,for 循环就已经执行完毕了。即当 i=2 的循环执行之后,会执行 i++,此时 i=3,这是循环条件判断 i ≮ 2,即不满足循环条件,for 循环退出。所以每次拿到的都是 for 循环执行完毕之后的 i,因此通过 aList[i] 的方式是无法取得对应的 a 元素的

    总结: for 循环会在页面加载完成之后立即执行,而响应函数会在超链接被点击时才执行当响应函数执行时,for 循环早已执行完毕

    10、操作内联样式

    修改元素内联样式

    通过JS修改元素的内联样式,语法:元素.style.样式名 = 样式值

    box1.style.height = "200px";
    box1.style.width = "200px";
    
    • 1
    • 2

    注意:如果CSS的样式名中含有一,这种名称在JS中是不合法的,比如background-color

    需要将这种样式名修改为驼峰命名法,去掉-,然后将-后的字母大写

    // box1.style.background-color = "red"; // Uncaught SyntaxError: Invalid left-hand side in assignment
    box1.style.backgroundColor = "red";
    
    • 1
    • 2

    在 w3school 手册中,可以查看到每个样式所对应的 JS 代码

    image-20210806194738937

    我们通过 style 属性设置的样式都是内联样式,而内联样式有较高的优先级,所以通过JS修改的样式往往会立即显示

    但是如果在样式中写了!important,则此时样式会有最高的优先级,即使通过JS也不能覆盖该样式,此时将会导致JS修改样式失效,所以尽量不要为样式添加!important

    我们给 background-color设置!important之后,通过 box1.style.backgroundColor = "red";设置的样式就“废”了

    background-color: yellow !important;
    
    • 1

    操作内联样式

    读取元素内联样式

    通过 JS 读取元素的内联样式,语法:元素.style.样式名

    通过style属性设置和读取的都是内联样式,无法读取样式表中的样式

    alert(box1.style.height); // 
    box1.style.height = "200px";
    alert(box1.style.height); // 200px
    
    • 1
    • 2
    • 3

    img

    别急,耐心往下看

    读取元素样式

    获取元素的当前显示的样式,语法:元素.currentStyle.样式名

    它可以用来读取当前元素正在显示的样式,如果当前元素没有设置该样式,则获取它的默认值

    alert(box1.currentStyle.height); // 100px
    box1.style.height = "200px";
    alert(box1.currentStyle.height); // 200px
    
    • 1
    • 2
    • 3

    不过currentstyle只有 IE 浏览器支持,其他的浏览器都不支持。我们在 IE 中测试是可行的,在 Chrome 或 Edge 中报错的:UncaughtTypeError: Cannot read property 'height' of undefined

    img

    不过,在其他浏览器中可以使用getComputedStyle(),这个方法来获取元素当前的样式

    这个方法是window的方法,可以直接使用,需要两个参数

    • 第一个:要获取样式的元素
    • 第二个:可以传递一个伪元素,一般都传null

    该方法会返回一个对象,对象中封装了当前元素对应的样式

    可以通过对象.样式名来读取样式,如果获取的样式没有设置,则会获取到真实的值,而不是默认值

    比如:没有设置 width,它不会获取到 auto,而是一个长度

    但是该方法不支持IE8及以下的浏览器

    var obj = getComputedStyle(box1, null);
    alert(obj); // [object CSSStyleDeclaration]
    alert(obj.width); // 200px
    alert(obj.height); // 200px
    alert(obj.backgroundColor); // rgb(2  55, 0, 0)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    那么问题来了,如果想要兼容IE8及以下的浏览器,就会陷入一个两难的境地, 该怎么办呢?

    img

    通过currentStylegetComputedStyle()读取到的样式都是只读的,不能修改,如果要修改必须通过style属性

    那么我就只能自己写个函数,来兼容所有浏览器

    // 自定义兼容所有浏览器获取元素样式的方法
    function getStyle(obj, name) {
        // 判断是否有getComputedStyle方法
        if (getComputedStyle) {
            // 正常浏览器的方式
            return getComputedStyle(obj, null)[name];
        } else {
            // IE的方式
            return obj.currentStyle[name];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试结果

    Hbuilder内置浏览器

    Chrome

    Edge

    image-20210806204741700

    IE11

    image-20210806204937646

    IE8

    image-20210806204535471

    怎么 IE8 还是不行,提示“getComputedStyle”未定义

    这是因为执行到 if 语句时,会先在 function 中找,找不到会在全局作用域中找,全局作用域中也找不到getComputedStyle,就会报错了

    那么怎么解决这个问题呢?

    我们先改造一下 function 代码,将getComputedStyle改成window.getComputedStyle

    function getStyle(obj, name) {
        // 判断是否有getComputedStyle方法
        if (window.getComputedStyle) {
            // 正常浏览器的方式
            return getComputedStyle(obj, null)[name];
        } else {
            // IE的方式
            return obj.currentStyle[name];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    效果

    image-20210806205500790

    为什么呢?

    因为变量找不到会报错,而属性找不到返回的是undefined而不会报错,这样就可以利用undefined != true的特点,执行 else 中的代码

    同理,下面代码同样可以判断,只不过,会优先走currentStyle的方式,而我们希望的优先走getComputedStyle方法,所以不建议用

    function getStyle(obj, name) {
        // 判断是否有currentStyle属性
        if (obj.currentStyle) {
            // IE的方式
            return obj.currentStyle[name];
        } else {
            // 正常浏览器的方式
            return getComputedStyle(obj, null)[name];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    那么上述代码有没有优化或者说简化的空间呢?当然,我们可以使用三元运算符对其进行精简

    function getStyle(obj, name) {
        return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
    }
    
    • 1
    • 2
    • 3

    三元运算符更加简洁,if-else 的方式更加清晰,建议使用 if-else 的方式,不过本质上是一样的,看个人习惯

    11、其他样式相关的属性

    image-20210806214629582

    clientWidth、clientHeight

    这两个属性可以获取元素的可见宽度和高度

    这些属性都是不带px的,返回都是一个数字,可以直接进行计算

    会获取元素宽度和高度,包括内容区和内边距

    这些属性都是只读的,不能修改(改只有一种方式,就是通过元素.style.样式 = 样式值

    // #box1 {
    // 	width: 100px;
    // 	height: 100px;
    // 	background-color: red;
    // 	padding: 10px;
    // 	border: 10px solid yellow;
    // }
    alert(box1.clientHeight); // 120
    alert(box1.clientWidth); // 120
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    offsetWidth、offsetHeight

    获取元素的整个的宽度和高度,包括内容区、内边距和边框

    // #box1 {
    // 	width: 100px;
    // 	height: 100px;
    // 	background-color: red;
    // 	padding: 10px;
    // 	border: 10px solid yellow;
    // }
    alert(box1.offsetHeight); // 140
    alert(box1.offsetWidth); // 140
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    offsetParent

    可以用来获取当前元素的定位父元素

    会获取到离当前元素最近的开启了定位(只要position不是sticky)的祖先元素

    如果所有的祖先元素都没有开启定位,则返回body

    // <div id="box1"></div>
    alert(box1.offsetParent); // [object HTMLBodyElement]
    
    // <div id="box2">
    //     <div id="box1"></div>
    // </div>
    alert(box1.offsetParent); // [object HTMLBodyElement]
    
    //<div id="box3">
    //	<div id="box2">
    //		<div id="box1"></div>
    //	</div>
    //</div>
    alert(box1.offsetParent); // [object HTMLBodyElement]
    
    //<div id="box3" style="position: relative;">
    //	<div id="box2" style="position: relative;">
    //		<div id="box1"></div>
    //	</div>
    //</div>
    alert(box1.offsetParent); // [object HTMLDivElement]
    alert(box1.offsetParent.id); // box2
    
    //<div id="box3" style="position: relative;">
    //	<div id="box2">
    //		<div id="box1"></div>
    //	</div>
    //</div>
    alert(box1.offsetParent); // [object HTMLDivElement]
    alert(box1.offsetParent.id); // box3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    offsetLeft、offsetTop

    当前元素相对于其定位父元素的水平或垂直偏移量

    //<div id="box3">
    //	<div id="box2">
    //		<div id="box1"></div>
    //	</div>
    //</div>
    alert(box1.offsetLeft); // 8  浏览器的默认样式
    alert(box1.offsetTop); // 54
    
    //<div id="box3">
    //	<div id="box2" style="position: relative;">
    //		<div id="box1"></div>
    //	</div>
    //</div>
    alert(box1.offsetLeft); // 0
    alert(box1.offsetTop); // 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image-20210806215118948

    scrollHeight、scrollWidth

    可以获取元素整个滚动区域的宽度和高度

    // #box4 {
    // 	width: 200px;
    // 	height: 300px;
    // 	background-color: yellow;
    // 	overflow: auto;
    // }
    // #box5 {
    // 	width: 400px;
    // 	height: 600px;
    // 	background-color: #bfa;
    // }
    alert(box4.scrollHeight); // 600
    alert(box4.scrollWidth); // 400
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    scrollLeft、scrollTop

    可以获取水平或垂直滚动条滚动的距离

    // #box4 {
    // 	width: 200px;
    // 	height: 300px;
    // 	background-color: yellow;
    // 	overflow: auto;
    // }
    // #box5 {
    // 	width: 400px;
    // 	height: 600px;
    // 	background-color: #bfa;
    // }
    alert(box4.scrollLeft); // 0/71.19999694824219/92/... 随着水平滚动条滚动而发生变化
    alert(box4.scrollTop); // 0/163.1999969482422/116/... 随着垂直滚动条滚动而发生变化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    看这么一个问题,打印如下值,将水平和垂直滚动条滚动到底

    alert(box4.clientHeight + ", " + (box4.scrollHeight - box4.scrollTop)); // 283, 283.20001220703125
    alert(box4.clientWidth + ", " + (box4.scrollWidth - box4.scrollLeft)); // 183, 183.1999969482422
    
    • 1
    • 2

    PS:我这里打印的结果存在小数点,不知为何

    • 当满足scrollHeight - scrollTop == clientHeight,说明垂直滚动条滚动到底了
    • 当满足scrollWidth - scrollLeft == clientwidth,说明水平滚动条滚动到底

    那么这个原理有什么用呢?

    爱到底到底,管我什么事 有些网站注册时会有一个 霸王条款 用户协议,要确保用户阅读协议了,才允许注册。那问题来了,怎么确保用户阅读了协议呢?就是利用了上述原理,当滚动条拖至最底部时,就可以注册了。

    那么接下来,我们就做一个 霸王条款 用户协议

    练习

    HTML 代码

    <div id="outer">
        <h3>亲爱的用户,欢迎注册本网站</h3>
        <p id="info">
            亲爱的用户,请仔细阅读以下协议,如果你不仔细阅读你就别注册
            此处省略一万字。。。
        </p>
        <div id="checkDiv">
            <input type="checkbox" name="checkInput" value="1" id="checkInput" disabled="disabled" />我已仔细阅读协议,一定遵守
        </div>
        <div id="submitDiv">
            <input type="submit" id="submitInput" disabled="disabled" value="注册"/>
        </div>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    CSS 代码

    #outer {
        width: 500px;
    }
    
    #outer,
    h3,
    #checkDiv,
    #submitDiv,
    #submitInput {
        margin: 10px auto;
    }
    
    #checkDiv {
        width: 250px;
    }
    
    #submitInput {
        display: block;
    }
    
    #info {
        height: 600px;
        overflow: auto;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    JS 代码

    // 为滚动条绑定事件,就是为有滚动条的元素绑定事件
    var info = document.getElementById("info");
    var checkInput = document.getElementById("checkInput");
    var submitInput = document.getElementById("submitInput");
    info.onscroll = function() {
        // 当滚动条滚动到底时,启用并自动勾选协议,并启用注册按钮
        if (parseInt(info.scrollHeight - info.scrollTop) == parseInt(info.clientHeight)) {
            // 自动勾选协议
            checkInput.disabled = false;
            checkInput.checked = true;
            // 启用注册按钮
            submitInput.disabled = false;
        }
    }
    // 为checkInput绑定勾选响应事件
    checkInput.onclick = function(ret) {
        // 如果勾选了协议,则启用注册按钮,否则禁用注册按钮
        if (!checkInput.checked) {
            submitInput.disabled = true;
        }
        else{
            submitInput.disabled = false;
        }
    }
    // 为submit绑定单击响应函数
    submitInput.onclick = function(){
        if(confirm("确认注册吗?")){
            alert("注册成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    效果

    在这里插入图片描述

  • 相关阅读:
    windows本地认证
    将Apache访问日志保存到MySQL数据库
    JdbcTemplate_java培训
    ACwing算法基础课——基础算法
    【项目实战】springboot+vue舞蹈课程在线学习系统-java舞蹈课程学习打卡系统的设计与实现
    linux下日志查看命令
    前端AST详解,手写babel插件
    产品经理认证(UCPM)备考心得
    C语言笔试题:将字符串中的单词倒序
    [BJDCTF2020]ZJCTF,不过如此 preg_replace /e模式漏洞
  • 原文地址:https://blog.csdn.net/agonie201218/article/details/125418174