笔记来源 尚硅谷最新版JavaScript基础全套教程完整版(140集实战教学,JS从入门到精通)_哔哩哔哩_bilibili

DOM

1、DOM简介

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

JS中通过DOM来对HTML文档进行操作(操作网页)。

只要理解了DOM就可以随心所欲的操作WEB页面。

文档

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

对象

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

模型

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

14_DOM - 图1

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

14_DOM - 图2

2、节点

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

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

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

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

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

节点类型

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

常用节点分为四类

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

14_DOM - 图3

节点属性

14_DOM - 图4

文档节点(Document)

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

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

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

元素节点(Element)

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

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

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

文本节点(Text)

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

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

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

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

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

属性节点(Attr)

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

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

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

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

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

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

3、事件

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

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

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

14_DOM - 图5

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

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

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

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

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

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

4、文档的加载

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

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

14_DOM - 图6

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

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

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

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

5、DOM查询

获取元素节点

14_DOM - 图7

通过document对象调用

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

  1. // 参数:
  2. // idstr 要绑定单击响应函数的对象的id属性值
  3. // fun 事件的回调函数,当单击元素时,该函数将会被触发
  4. function myClick(idStr, fun){
  5. var btn = document.getElementById(idStr);
  6. btn.onclick = fun;
  7. }
  • getElementById() 通过id属性获取一个元素节点对象
    1. myClick("btn01", function () {
    2. // innerHTML 通过这个属性可以获取到元素内部的html代码
    3. alert(document.getElementById("bj").innerHTML); // 北京
    4. });
  • getElementsByTagName() 通过标签名获取一组元素节点对象

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

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

练习:图片切换

HTML代码

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

CSS代码

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

JS代码

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

效果

14_DOM - 图8

获取元素节点的子节点

14_DOM - 图9

通过具体的元素节点调用

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

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

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

获取父节点和兄弟节点

14_DOM - 图10

通过具体的节点调用

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

    1. myClick("btn07", function () {
    2. var bj = document.getElementById("bj");
    3. var parentNode = bj.parentNode;
    4. alert(parentNode); // [object HTMLUListElement]
    5. alert(parentNode.innerHTML);
    6. // <li id="bj">北京</li>
    7. // <li>上海</li>
    8. // <li>东京</li>
    9. // <li>首尔</li>
    10. // innerText
    11. // -该属性可以获取到元素内部的文本内容
    12. // -它和innerHTML类似,不同的是它会自动将htm1去除
    13. alert(parentNode.innerText);
    14. // 北京
    15. // 上海
    16. // 东京
    17. // 首尔
    18. });
  • previousSibling属性,表示当前节点的前一个兄弟节点
    1. myClick("btn08", function () {
    2. var android = document.getElementById("android");
    3. // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    4. var previousSibling = android.previousSibling;
    5. alert(previousSibling); // [object HTMLLIElement]
    6. alert(previousSibling.innerHTML); // IOS
    7. });
    8. myClick("btn08", function () {
    9. var android2 = document.getElementById("android2");
    10. // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    11. var previousSibling = android2.previousSibling;
    12. alert(previousSibling); // [object Text]
    13. alert(previousSibling.innerHTML); // undefined
    14. });
    15. myClick("btn08", function () {
    16. var android2 = document.getElementById("android2");
    17. // previousElementSibling获取前一个兄弟元素,IE8及以下不支持
    18. var previousElementSibling = android2.previousElementSibling;
    19. alert(previousElementSibling); // [object HTMLLIElement]
    20. alert(previousElementSibling.innerHTML); // IOS
    21. });
  • nextSibling属性,表示当前节点的后一个兄弟节点
    1. myClick("btn082", function () {
    2. var android = document.getElementById("android");
    3. // 返回#android的前一个兄弟节点(也可能获取到空白的文本)
    4. var nextSibling = android.nextSibling;
    5. alert(nextSibling); // [object HTMLLIElement]
    6. alert(nextSibling.innerHTML); // Windows Phone
    7. });

6、全选练习

HTML代码

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

全选

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

全不选

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

反选

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

提交

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

全选/全不选

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

items

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

效果

14_DOM - 图11

14_DOM - 图12

14_DOM - 图13

7、DOM查询的剩余方法

document.body

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

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

document.documentElement

document.documentElement保存的是html根标签

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

document.all

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

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

document.getElementsByClassName()

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

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

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

document.querySelector()

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

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

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

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

document.querySelectorAll()

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

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

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

8、DOM增删改

14_DOM - 图14

document.createElement()

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

document.createTextNode()

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

appendChild()

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

insertBefore()

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

replaceChild()

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

removeChild()

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

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

9、增删练习

准备

14_DOM - 图15

HTML代码

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

JS代码

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

添加优化

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

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

a的索引问题

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

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

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

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

14_DOM - 图16

会发现,每次打印的结果都是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.样式名 = 样式值

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

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

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

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

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

14_DOM - 图17

14_DOM - 图18

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

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

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

  1. background-color: yellow !important;

14_DOM - 图19

读取元素内联样式

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

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

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

14_DOM - 图20

别急,耐心往下看

读取元素样式

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

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

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

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

14_DOM - 图21

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

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

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

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

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

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

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

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

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

14_DOM - 图22

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

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

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

测试结果

Hbuilder内置浏览器

14_DOM - 图23

Chrome

14_DOM - 图24

Edge

14_DOM - 图25

IE11

14_DOM - 图26

IE8

14_DOM - 图27

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

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

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

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

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

效果

14_DOM - 图28

为什么呢?

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

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

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

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

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

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

11、其他样式相关的属性

14_DOM - 图29

clientWidth、clientHeight

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

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

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

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

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

offsetWidth、offsetHeight

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

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

offsetParent

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

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

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

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

offsetLeft、offsetTop

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

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

14_DOM - 图30

scrollHeight、scrollWidth

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

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

scrollLeft、scrollTop

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

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

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

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

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

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

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

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

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

练习

HTML 代码

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

CSS 代码

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

JS 代码

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

效果

14_DOM - 图31