# 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应用来说,有下面这些代表性的事件:点击某个元素、将鼠标移动至某个元素上方、按下键盘上某个键,等等

image-20210801155425259

我们可以在事件对应的属性中设置一些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

效果

图片切换

# 获取元素节点的子节点

image-20210803222419830

通过具体的元素节点调用

  • 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增删改

image-20210804215312180

# 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

image-20210806194643089

我们通过 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内置浏览器

image-20210806204815473

Chrome

image-20210806204840431

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

效果

协议注册