WebAPIs-DOM:操作元素

操作元素

JavaScript的 DOM 操作可以改变网页内容、结构和样式,我们可以利用 DOM 操作元素来改变元素里面的内容、属性等。

1. 改变元素内容

html 复制代码
element.innerText
从起始位置到终止位置的内容,但它去除html标签,同时空格和换行也会去掉
html 复制代码
element.innerHTML
起始位置到终止位置的全部内容,包括html标签,同时保留空格和换行

设置内容的时候,当设置不含标签的内容的时候应该使用 innerText,效率更高。

  • innerText 的兼容性处理

innerText 和 textContent 都可以获取文本内容,但是有浏览器兼容问题,下面学习一下兼容性处理的思想。

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="box">
    hello
  </div>
  <script>
    var box = document.getElementById('box');
    console.log(getInnerText(box));
    // 处理innerText的兼容性问题
    function getInnerText(element) {
      // 判断当前浏览器 是否支持元素的innerText属性
      if (typeof element.innerText === 'string') { // 当属性存在的时候返回的是该属性的类型
        return element.innerText;
      } else { // 当属性不存在的时候返回的是undefined
        // 如果不支持innerText属性,使用element.textContent获取内容
        return element.textContent;
      }
    }
  </script>
</body>
</html>

2. 常用元素的属性操作

  • innerText、innerHTML 改变元素内容
  • src、href
  • id、alt、title

示例代码:

js 复制代码
<body>
    <button id="ldh">刘德华</button>
    <button id="zxy">张学友</button> <br>
    <img src="images/ldh.jpg" alt="" title="刘德华">
    <a href="跳转目标" target="目标窗口的弹出方式">文本或图像</a>
    <script>
        // 修改元素属性  src
        // 1. 获取元素
        var ldh = document.getElementById('ldh');
        var zxy = document.getElementById('zxy');
        var img = document.querySelector('img');
        // 2. 注册事件  处理程序
        zxy.onclick = function() {
            img.src = 'images/zxy.jpg';
            img.title = '张学友思密达';
        }
        ldh.onclick = function() {
            img.src = 'images/ldh.jpg';
            img.title = '刘德华';
        }
    </script>
</body>

3. 表单元素的属性操作

利用DOM可以操作如下表单元素的属性:

js 复制代码
type 可以获取input标签的类型(输入框或复选框等)
value 用于大部分表单元素的内容获取(option除外)
checked 复选框选中属性  (布尔类型)
selected 下拉菜单选中属性  (布尔类型)
disabled 禁用属性 (布尔类型)  

示例代码:

js 复制代码
<body>
    <button>按钮</button>
    <input type="text" value="输入内容">
    <script>
        // 1. 获取元素
        var btn = document.querySelector('button');
        var input = document.querySelector('input');
        // 2. 注册事件 处理程序
        btn.onclick = function() {
            // 表单里面的值 文字内容是通过 value 来修改的
            input.value = '被点击了';
            // 如果想要某个表单被禁用
            // btn.disabled = true;
            this.disabled = true;
            // this 指向的是事件函数的调用者 btn
        }
    </script>
</body>

① 案例:给文本框赋值,获取文本框的值

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input type="text"><br>
  <input id="btn" type="button" value="获取文本框的值">
  <script>
    // 1 当页面加载完毕,给所有的文本框赋值
    var inputs = document.getElementsByTagName('input');
    for (var i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      // 根据type属性 判断是否是文本框
      if (input.type === 'text') {
        input.value = i; // 给文本框赋值
      }
    }

    // 2 当点击按钮的时候,获取所有文本框的值,并使用 | 分割输出
    // 0|1|2
    var btn = document.getElementById('btn');
    btn.onclick = function () {
      var array = [];
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        // 判断是否是文本框
        if (input.type === 'text') {
          //大量字符串的拼接会有性能问题,所以我们先放到一个数组里面,再用数组拼接
           array.push(input.value);
        }
      }
      console.log(array.join('|')); // 打印:0|1|2|3|4|5|6|7|8
    }
  </script>
</body>
</html>

② 案例:点击下拉框左边的设置,随机选中一项

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <input type="button" value="设置" id='btnSet'>
  <select id="selCities">
    <option value="1">北京</option>
    <option value="2">上海</option>
    <option value="3">杭州</option>
    <option value="4">郑州</option>
    <option value="5">武汉</option>
  </select>
  <script>
    // 1 给按钮注册事件
    var btnSet = document.getElementById('btnSet');
    btnSet.onclick = function () {
      // 2 获取下拉框中的所有option
      var selCities = document.getElementById('selCities');
      var options = selCities.getElementsByTagName('option');
      // 3 随机生成索引
      // Math.random() -> [0, 1)
      // Math.random() * 5 -> [0, 5)
      var randomIndex = parseInt(Math.random() * options.length);
      // 4 根据索引获取option,并让option选中
      var option = options[randomIndex];
      option.selected = true;
    }
  </script>
</body>
</html>

4. 通过操作style属性改变样式

我们可以通过 JS 修改元素的大小、颜色、位置等样式。

js 复制代码
元素对象.style.样式属性 = 值;

注意:

  1. JS 里面的样式采用驼峰命名法,比如:fontSize、backgroundColor
  2. JS 修改style样式操作,产生的行内样式,CSS权重比较高

示例代码:

js 复制代码
<body>
    <div></div>
    <script>
        // 1. 获取元素
        var div = document.querySelector('div');
        // 2. 注册事件 处理程序
        div.onclick = function() {
            // div.style里面的属性 采取驼峰命名法 
            this.style.backgroundColor = 'purple';
            // 通过样式属性设置宽高、位置的属性类型是字符串,需要加上px
            this.style.width = '250px';
        }
    </script>
</body>

① 案例:隐藏显示二维码

js 复制代码
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
      ......
        .hide {
            display: none;
        }
        .show {
            display: block;
        }
    </style>
</head>
<body>
    <div class="nodeSmall" id="node_small">
        <div class="erweima hide" id="er">
            <img src="images/456.png" alt=""/>
        </div>
    </div>
    <script src="common.js"></script>
    <script>
        // 根据id获取元素 封装成函数
        function my$(id) {
            return document.getElementById(id);
        }
        // 当鼠标移入  onmouseover
        // 当鼠标移出  onmouseout
        var nodeSmall = my$('node_small');
        nodeSmall.onmouseover = function () {
            // my$('er').className = 'erweima show';
            my$('er').className = my$('er').className.replace('hide', 'show'); 
        }
        nodeSmall.onmouseout = function () {
            // my$('er').className = 'erweima hide';
            my$('er').className = my$('er').className.replace('show', 'hide');
        }
    </script>
</body>
</html>

② 案例:显示隐藏文本框内容

当鼠标点击文本框时,里面的默认文字隐藏,当鼠标离开文本框时,里面的文字显示

案例分析:

  1. 首先表单需要两个新事件,获得焦点onfocus,失去焦点onblur
  2. 如果获得焦点,判断表单里面内容是否为默认文字,如果是,则清空表单内容
  3. 如果失去焦点,判断表单内容是否为空,如果为空,则表单内容改为默认文字
js 复制代码
// 1.获取元素
var text = document.querySelector('input');
// 2.注册事件 获得焦点事件 onfocus 
text.onfocus = function() {
        // console.log('得到了焦点');
        if (this.value === '手机') {
            this.value = '';
        }
        // 获得焦点需要把文本框里面的文字颜色变黑
        this.style.color = '#333';
    }
// 3. 注册事件 失去焦点事件 onblur
text.onblur = function() {
    // console.log('失去了焦点');
    if (this.value === '') {
        this.value = '手机';
    }
    // 失去焦点需要把文本框里面的文字颜色变浅色
    this.style.color = '#999';
}

5. 通过操作className属性改变样式

js 复制代码
元素对象.className = 值;
Tip:因为class是关键字,所以使用className。

注意:

  1. 如果样式修改比较多,可以采用操作类名方式更改元素样式
  2. class因为是个保留字,因此使用className来操作元素类名属性
  3. className会直接更改元素的类名,会覆盖原先的类名

示例代码:

html 复制代码
<body>
    <div class="first">文本</div>
    <script>
        // 1. 使用 element.style 获得修改元素样式  如果样式比较少 或者 功能简单的情况下使用
        var test = document.querySelector('div');
        test.onclick = function() {
            // this.style.backgroundColor = 'purple';
            // this.style.color = '#fff';
            // this.style.fontSize = '25px';
            // this.style.marginTop = '100px';

            // 2. 我们可以通过 修改元素的className更改元素的样式 适合于样式较多或者功能复杂的情况
            // 3. 如果想要保留原先的类名,我们可以这么做 多类名选择器
            // this.className = 'change';
            this.className = 'first change';
        }
    </script>
</body>

案例:密码框格式提示错误信息

用户如果离开密码框,里面输入的文字个数不是6~16,显示红色小图标,输入正确显示绿色小图标。

案例分析:

  1. 首先判断的事件是表单失去焦点onblur
  2. 如果输入正确,则提示正确信息:绿色小图标
  3. 如果输入不是6~16位,则提示错误信息:红色小图标
  4. 因为里面变化样式较多,所以我们采用className修改样式
html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        div {
            width: 600px;
            margin: 100px auto;
        }
        
        .message {
            display: inline-block;
            font-size: 12px;
            color: #999;
            background: url(images/mess.png) no-repeat left center;
            padding-left: 20px;
        }
        
        .wrong {
            color: red;
            background-image: url(images/wrong.png);
        }
        
        .right {
            color: green;
            background-image: url(images/right.png);
        }
    </style>
</head>

<body>
    <div class="register">
        <input type="password" class="ipt">
        <p class="message">请输入6~16位密码</p>
    </div>
    <script>
        // 首先判断的事件是表单失去焦点 onblur
        // 如果输入正确则提示正确的信息颜色为绿色小图标变化
        // 如果输入不是6到16位,则提示错误信息颜色为红色 小图标变化
        // 因为里面变化样式较多,我们采取className修改样式
        // 1.获取元素
        var ipt = document.querySelector('.ipt');
        var message = document.querySelector('.message');
        //2. 注册事件 失去焦点
        ipt.onblur = function() {
            // 根据表单里面值的长度 ipt.value.length
            if (this.value.length < 6 || this.value.length > 16) {
                // console.log('错误');
                message.className = 'message wrong';
                message.innerHTML = '您输入的位数不对要求6~16位';
            } else {
                message.className = 'message right';
                message.innerHTML = '您输入的正确';
            }
        }
    </script>
</body>
</html>

6. 排他思想(首先干掉其他人,再设置自己)

点击如下某个按钮,这个按钮变粉色,其他按钮恢复原色。

如果有同一组元素,我们想要某一个元素实现某种样式, 需要用到循环的排他思想算法:

  1. 所有元素全部清除样式(干掉其他人)
  2. 给当前元素设置样式 (留下我自己)
  3. 注意顺序不能颠倒,首先干掉其他人,再设置自己
js 复制代码
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <button>按钮5</button>
    <script>
        // 1. 获取所有按钮元素
        var btns = document.getElementsByTagName('button');
        // btns得到的是伪数组  里面的每一个元素 btns[i]
        for (var i = 0; i < btns.length; i++) {
            btns[i].onclick = function() {
                // (1) 我们先把所有的按钮背景颜色去掉  干掉所有人
                for (var i = 0; i < btns.length; i++) {
                    btns[i].style.backgroundColor = '';
                }
                // (2) 然后才让当前的元素背景颜色为pink 留下我自己
                this.style.backgroundColor = 'pink';
            }
        }
    </script>

案例:全选反选

js 复制代码
<script>
   var j_tb = document.getElementById('j_tb');
   var inputs = j_tb.getElementsByTagName('input');
   // 1 全选
   // 1.1 获取父checkbox,注册点击事件
   var j_cbAll = document.getElementById('j_cbAll');
   j_cbAll.onclick = function () {
     // 1.2 找到所有子的checkbox,让这些checkbox的状态跟父checkbox保持一致
     for (var i = 0; i < inputs.length; i++) {
       var input = inputs[i];
       if (input.type === 'checkbox') {
        // 让子的checkbox的选中状态,和父checkbox的选中状态一致
         input.checked = this.checked;
       }
     }
   }

   // 2 当点击子的checkbox,如果所有的子的checkbox都被选中了,让父的checkbox也选中
   // 如果有一个子的checkbox没有被选中,父的checkbox也不被选中
   // 此处的循环,是遍历所有子的checkbox,注册点击事件
   for (var i = 0; i < inputs.length; i++) {
     var input = inputs[i];
     // 判断是否是checkbox
     if (input.type !== 'checkbox') {
      // 结束当前循环,继续下一次循环
       continue;
     }
     // 给子的checkbox注册点击事件
     input.onclick = function () {
       checkAllCheckBox();
     }
   }

   // 判断父的checkbox的状态 封装成函数
   function checkAllCheckBox() {
     // 假设所有的子的checkbox都被选中了
       var isAllChecked = true;
       // 判断是否所有的子的checkbox都被选中了
       for (var i = 0; i < inputs.length; i++) {
         var input = inputs[i];
         if (input.type !== 'checkbox') {
           continue;
         }
         // 判断当前的所有checkbox是否都被选中
         if (!input.checked) {
           isAllChecked = false;
         }
       }
       // 设置父的checkbox的状态
       j_cbAll.checked = isAllChecked;
   }

   // 3 反选
   // 3.1 给反选按钮注册点击事件
   var btn = document.getElementById('btn');
   btn.onclick = function () {
      // 3.2 找到所有的子的checkbox,让其反选
      for (var i = 0; i < inputs.length; i++) {
        var input = inputs[i];
        // 判断是否是checkbox
        if (input.type !== 'checkbox') {
          continue;
        }
        // 让子的checkbox反选
        input.checked = !input.checked;
        // 设置父的checkbox的状态
        checkAllCheckBox();
      }
   }
</script>

7. 自定义属性操作

① 获取属性值 getAttribute()

js 复制代码
element.属性;  获取内置属性值(元素本身自带的属性)
element.getAttribute('属性');  主要获取自定义的属性(标准),我们程序员自定义的属性
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <img src="haha.png" id="hahaha" class="image" index="1">
  <script>
    var img = document.querySelector('img');
    // 1. 获取元素的属性值
    // (1) element.属性
    console.log(img.src); // file:///Users/zjnx1111/Desktop/haha.png
    console.log(img.id); // hahaha
    console.log(img.class); // undefined
    console.log(img.index); // undefined
    // (2) element.getAttribute('属性')  get得到获取 attribute 属性的意思 我们程序员自己添加的属性我们称为自定义属性 index
    console.log(img.getAttribute('src')); // haha.png
    console.log(img.id); // hahaha
    console.log(img.getAttribute('class')); // image
    console.log(img.getAttribute('index')); // 1
</script>
</body>
</html>

② 设置属性值 setAttribute(,)

js 复制代码
element.属性 = '值';  设置内置属性值
element.setAttribute('属性', '值');  主要设置自定义的属性(标准)

③ 移除属性 removeAttribute()

js 复制代码
element.removeAttribute('属性');

案例:tab栏

当鼠标点击上面相应的选项卡(tab),下面内容跟随变化

案例分析:

  1. Tab栏切换有两个大的模块
  2. 上面的模块选项卡,点击某一个,当前这个的底色是红色,其他不变(排他思想),修改类名的方式
  3. 下面的模块内容,会跟随上面的选项卡变化,所以下面模块变化写到点击事件里面
  4. 规律:下面的模块显示内容和上面的选项卡一一对应,相匹配
  5. 核心思路:给上面的tab_list里面的所有小li添加自定义属性,属性值从0开始
  6. 当我们点击tab_list里面的某个小li,让tab_con里面对应序列号的内容显示,其余隐藏(排他思想)
js 复制代码
<script>
    // 获取元素
    var tab_list = document.querySelector('.tab_list');
    var lis = tab_list.querySelectorAll('li');
    var items = document.querySelectorAll('.item');
    // for循环,给选项卡绑定点击事件
    for (var i = 0; i < lis.length; i++) {
        // 开始给5个小li 设置索引号 
        lis[i].setAttribute('index', i);
        lis[i].onclick = function() {
            // 1. 上的模块选项卡,当前这一个底色会是红色,其余不变(排他思想)
            // 干掉所有人 其余的li清除 class 这个类
            for (var i = 0; i < lis.length; i++) {
                lis[i].className = '';
            }
            // 留下我自己 
            this.className = 'current';
            // 2. 下面的显示内容模块
            var index = this.getAttribute('index');
            console.log(index);
            // 干掉所有人 让其余的item 这些div 隐藏
            for (var i = 0; i < items.length; i++) {
                items[i].style.display = 'none';
            }
            // 留下我自己 让对应的item 显示出来
            items[index].style.display = 'block';
        }
    }
</script>

④ H5自定义属性 data-和dataset

有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性,所以H5给我们新增了自定义属性,H5规定,自定义属性data-开头作为属性值,并且赋值。dataset是h5新增的方法,得到的是一个集合,里面存放了所有以data开头的自定义属性。

  1. 设置H5自定义属性

H5规定自定义属性的属性名以data-开头,比如:

js 复制代码
<div data-index='1'> </div>
或者使用JS设置:
element.setAttribute('data-index', '2')
  1. 获取H5自定义属性
js 复制代码
兼容性获取 element.getAttribute('data-index');
H5新增element.dataset.index 或者 element.dataset['index']  ie11才支持
js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div getTime="20" data-index="2" data-list-name="andy"></div>
  <script>
      var div = document.querySelector('div');
      console.log(div.getTime); // undefined
      console.log(div.getAttribute('getTime')); // 20
      div.setAttribute('data-time', 30); // 新增自定义属性
      console.log(div.getAttribute('data-time')); // 30
      console.log(div.getAttribute('data-index')); // 2
      console.log(div.getAttribute('data-list-name')); // andy
  
      // dataset是h5新增的方法,得到的是一个集合,里面存放了所有以data开头的自定义属性
      console.log(div.dataset); // DOMStringMap {index: "2", listName: "andy", time: "20"}
      console.log(div.dataset.index); // 2
      console.log(div.dataset['index']); // 2
      // 如果自定义属性里面有多个-链接的单词,我们获取的时候采取驼峰命名法
      console.log(div.dataset.listName); // andy
      console.log(div.dataset['listName']); // andy
  </script>
</body>
</html>

8. 节点操作

① 节点概述

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

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

一般地,节点至少拥有nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个基本属性。

  • 元素节点 nodeType为1
  • 属性节点 nodeType为2
  • 文本节点 nodeType为3(文本节点包含文字、空格、换行等)

我们实际开发中,主要操作的是元素节点。

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="box" value="我是div">hello</div>
  <script>
      var div = document.querySelector('div');
      console.log(div.nodeType); // 1
      console.log(div.nodeName); // DIV
      // 元素内的文本是文本节点,所以我们通过childNodes[0]获取这个文本节点,然后通过nodeValue获取文本节点的值
      console.log(div.childNodes[0].nodeValue); // hello
  </script>
</body>
</html>

② 节点层级

利用 DOM 树可以把节点划分为不同的层级关系,常见的是父子兄弟关系

③ 父节点 node.parentNode

js 复制代码
node.parentNode
  • parentNode属性可返回某节点的父节点,注意是最近的一个父节点
  • 如果指定的节点没有父节点则返回null
js 复制代码
<div class="demo">
    <div class="box">
        <span class="erweima">×</span>
    </div>
</div>
<script>
    // 1. 父节点 parentNode
    var erweima = document.querySelector('.erweima');
    // var box = document.querySelector('.box');
    // 得到的是离元素最近的父级节点(亲爸爸) 如果找不到父节点就返回为 null
    console.log(erweima.parentNode);
</script>

④ 子节点 parentNode.children

所有子节点:

js 复制代码
parentNode.childNodes (标准)

parentNode.childNodes 返回包含指定节点的子节点的集合,该集合为及时更新的集合。

注意:返回值里面包含了所有的子节点,包括元素节点、文本节点等。如果只想要获得里面的元素节点,则需要专门处理,所以我们一般不提倡使用childNodes。

子元素节点:

js 复制代码
parentNode.children (非标准)

parentNode.children 是一个只读属性,返回所有的子元素节点。它返回子元素节点,其余节点不返回(这个是我们重点掌握的)。虽然children是一个非标准,但却得到了各个浏览器的支持,因此我们可以放心使用。

js 复制代码
<ul>
    <li>我是li</li>
    <li>我是li</li>
    <li>我是li</li>
    <li>我是li</li>
</ul>
<script>
    // DOM 提供的方法(API)获取
    var ul = document.querySelector('ul');
    var lis = ul.querySelectorAll('li');
    // 1. 子节点  childNodes 所有的子节点 包含 元素节点 文本节点等等
    console.log(ul.childNodes);
    console.log(ul.childNodes[0].nodeType);
    console.log(ul.childNodes[1].nodeType);
    // 2. children 获取所有的子元素节点 也是我们实际开发常用的
    console.log(ul.children);
</script>

第1个子节点:

js 复制代码
parentNode.firstChild

firstChild 返回第一个子节点,找不到则返回null。同样,也是包含所有的节点。

最后1个子节点:

js 复制代码
parentNode.lastChild

lastChild 返回最后一个子节点,找不到则返回null。同样,也是包含所有的节点。

第1个子元素节点:

js 复制代码
parentNode.firstElementChild

firstElementChild 返回第一个子元素节点,找不到则返回null。

最后1个子元素节点:

js 复制代码
parentNode.lastElementChild

lastElementChild 返回最后一个子元素节点,找不到则返回null。

注意:这两个方法有兼容性问题,IE9以上才支持。

兼容性处理

实际开发中,firstChild 和 lastChild 包含其他节点,操作不方便,而 firstElementChild 和 lastElementChild 又有兼容性问题,那么我们如何获取第一个子元素节点或最后一个子元素节点呢?

解决方案:都不用,我们使用children,如下:

js 复制代码
如果想要第一个子元素节点,可以使用:parentNode.children[0]
如果想要最后一个子元素节点,可以使用:parentNode.children[parentNode.children.length - 1]
js 复制代码
<ol>
  <li>我是li1</li>
  <li>我是li2</li>
  <li>我是li3</li>
  <li>我是li4</li>
  <li>我是li5</li>
</ol>
<script>
  var ol = document.querySelector('ol');
  // 1. firstChild 第一个子节点 不管是文本节点还是元素节点
  console.log(ol.firstChild);
  console.log(ol.lastChild);
  // 2. firstElementChild 返回第一个子元素节点 ie9才支持
  console.log(ol.firstElementChild);
  console.log(ol.lastElementChild);
  // 3. 实际开发的写法  既没有兼容性问题又返回第一个子元素
  console.log(ol.children[0]);
  console.log(ol.children[ol.children.length - 1]);
</script>

案例:新浪下拉菜单

案例分析:

  1. 导航栏里面的li都要有鼠标经过效果,所以需要循环注册鼠标事件
  2. 核心原理:当鼠标经过li里面的第二个孩子ul显示,当鼠标离开,则ul隐藏
js 复制代码
<body>
    <ul class="nav">
        <li>
            <a href="#">微博</a>
            <ul>
                <li>
                    <a href="">私信</a>
                </li>
                <li>
                    <a href="">评论</a>
                </li>
                <li>
                    <a href="">@我</a>
                </li>
            </ul>
        </li>
        ......
        //nav里面有四个li
    </ul>
    <script>
        // 1. 获取元素
        var nav = document.querySelector('.nav');
        var lis = nav.children; // 得到4个小li
        // 2.循环注册事件
        for (var i = 0; i < lis.length; i++) {
            lis[i].onmouseover = function() {
                // 3.显示ul
                this.children[1].style.display = 'block';
            }
            lis[i].onmouseout = function() {
                // 3.隐藏ul
                this.children[1].style.display = 'none';
            }
        }
    </script>
</body>

⑤ 兄弟节点

下一个兄弟节点:

js 复制代码
node.nextSibling

返回当前元素的下一个兄弟节点,找不到则返回null。

上一个兄弟节点:

js 复制代码
node.previousSibling

返回当前元素上一个兄弟节点,找不到则返回null。

下一个兄弟元素节点(有兼容性问题):

js 复制代码
node.nextElementSibling

返回当前元素下一个兄弟元素节点,找不到则返回null。

上一个兄弟元素节点(有兼容性问题):

js 复制代码
node.previousElementSibling

返回当前元素上一个兄弟元素节点,找不到则返回null。

js 复制代码
<div>我是div</div>
<span>我是span</span>
<script>
    var div = document.querySelector('div');
    // 1.nextSibling 下一个兄弟节点 包含元素节点或者文本节点等等
    console.log(div.nextSibling);
    console.log(div.previousSibling);
    // 2. nextElementSibling 得到下一个兄弟元素节点
    console.log(div.nextElementSibling);
    console.log(div.previousElementSibling);
</script>

注意:nextElementSibling和previousElementSibling有兼容性问题,IE9以上才支持。

问:如何解决兼容性问题? 答:自己封装一个兼容性的函数

获取下一个兄弟元素节点,兼容性处理

js 复制代码
// 获取下一个兄弟元素节点
function getNextElementSibling(element) {
  var el = element;
  while (el = el.nextSibling) {
    if (el.nodeType === 1) {
        return el;
    }
  }
  return null;
}  

⑥ 子节点和兄弟节点总结

js 复制代码
//节点包括:元素节点、属性节点、文本节点、注释节点
var box = document.getElementById('box');
console.log(box.parentNode);      //父节点  只有一个
console.log(box.hasChildNodes()); //是否有子节点
console.log(box.childNodes);      //子节点  有很多个
console.log(box.firstChild);      //第一个子节点
console.log(box.lastChild);       //最后一个子节点
console.log(box.nextSibling);     //下一个兄弟节点
console.log(box.previousSibling); //上一个兄弟节点
js 复制代码
//元素节点
console.log(box.children);          //子元素集合
console.log(box.firstElementChild); //第一个子元素,有兼容性问题,从IE9以后支持
console.log(box.lastElementChild);  //最后一个子元素,有兼容性问题,从IE9以后支持
console.log(box.nextElementSibling);     //下一个兄弟元素,有兼容性问题,从IE9以后支持
console.log(box.previousElementSibling); //上一个兄弟元素,有兼容性问题,从IE9以后支持

⑦ 创建节点 createElement()

js 复制代码
document.createElement('tagName')

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

js 复制代码
// 在内存中创建一个DOM对象
var div = document.createElement('div'); //创建div
// 设置对象的属性
div.innerText = 'hello';
div.style.color = 'red'; 
document.body.appendChild(div);         //追加div

⑧ 添加节点 node.appendChild(child)

js 复制代码
node.appendChild(child)

node.appendChild() 方法将一个节点添加到指定父节点的子节点列表末尾。类似于css里面的after伪元素。

js 复制代码
node.insertBefore(child, 指定元素)

node.insertBefore( , ) 方法将一个节点添加到父节点的指定子节点前面。类似于css里面的before伪元素。

js 复制代码
<ul>
    <li>123</li>
</ul>
<script>
    // 1. 创建节点元素节点
    var li = document.createElement('li');
    // 2. 添加节点 node.appendChild(child)  node 父级  child 是子级 后面追加元素
    var ul = document.querySelector('ul');
    ul.appendChild(li);
    // 3. 添加节点 node.insertBefore(child, 指定元素);
    var lili = document.createElement('li');
    ul.insertBefore(lili, ul.children[0]);
</script>

案例:简单版发布留言

案例分析:

  1. 核心思路:点击按钮之后,就动态创建一个li,添加到ul里面
  2. 创建li的同时,把文本域里面的值通过li.innerHTML赋值给li
  3. 如果想到新的留言后面显示,就用appendChild,如果想要前面显示就用insertBefore
js 复制代码
<body>
    <textarea name="" id=""></textarea>
    <button>发布</button>
    <ul>

    </ul>
    <script>
        // 1. 获取元素
        var btn = document.querySelector('button');
        var text = document.querySelector('textarea');
        var ul = document.querySelector('ul');
        // 2. 注册事件
        btn.onclick = function() {
            if (text.value == '') {
                alert('您没有输入内容');
                return false;
            } else {
                // console.log(text.value);
                // (1) 创建元素
                var li = document.createElement('li');
                // 先有li 才能赋值
                li.innerHTML = text.value;
                // (2) 添加元素
                // ul.appendChild(li);
                ul.insertBefore(li, ul.children[0]);
            }
        }
    </script>
</body>

⑨ 删除节点 node.removeChild(child)

js 复制代码
node.removeChild(child)

node.removeChild() 方法从 node节点中删除一个子节点,返回删除的节点。

js 复制代码
<button>删除</button>
<ul>
    <li>熊大</li>
    <li>熊二</li>
    <li>光头强</li>
</ul>
<script>
    // 1.获取元素
    var ul = document.querySelector('ul');
    var btn = document.querySelector('button');
    // 2. 删除元素  node.removeChild(child)
    // ul.removeChild(ul.children[0]);
    // 3. 点击按钮依次删除里面的孩子
    btn.onclick = function() {
        if (ul.children.length == 0) {
            this.disabled = true;
        } else {
            ul.removeChild(ul.children[0]);
        }
    }
</script>

案例:删除留言

案例分析:

  1. 当我们把文本域里面的值赋值给li的时候,多添加一个删除的链接
  2. 需要把所有的链接获取过来,当我们点击当前的链接的时候,删除当前链接所在的li
  3. 阻止链接跳转需要添加javascript:void(0); 或者 javascript:;
js 复制代码
<textarea name="" id=""></textarea>
<button>发布</button>
<ul>

</ul>
<script>
    // 1. 获取元素
    var btn = document.querySelector('button');
    var text = document.querySelector('textarea');
    var ul = document.querySelector('ul');
    // 2. 注册事件
    btn.onclick = function() {
        if (text.value == '') {
            alert('您没有输入内容');
            return false;
        } else {
            // console.log(text.value);
            // (1) 创建元素
            var li = document.createElement('li');
            // 先有li 才能赋值
            li.innerHTML = text.value + "<a href='javascript:;'>删除</a>";
            // (2) 添加元素
            // ul.appendChild(li);
            // 添加到最前面,用insertBefore
            ul.insertBefore(li, ul.children[0]);
            // (3) 删除元素 删除的是当前链接的li 它的父亲
            var as = document.querySelectorAll('a');
            for (var i = 0; i < as.length; i++) {
                as[i].onclick = function() {
                    // 删除的是 li 当前a所在的li  this.parentNode;
                    ul.removeChild(this.parentNode);
                }
            }
        }
    }
</script>

🔟 复制节点 node.cloneNode(false/true);

js 复制代码
node.cloneNode()

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

注意:

  1. 如果括号参数为空或者为false,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点。
  2. 如果括号参数为true,则是深度拷贝,会复制节点本身以及里面所有的子节点。
js 复制代码
<ul>
    <li>1111</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
    var ul = document.querySelector('ul');
    // 1. node.cloneNode(); 括号为空或者里面是false 浅拷贝 只复制标签不复制里面的内容
    // 2. node.cloneNode(true); 括号为true 深拷贝 复制标签复制里面的内容
    var lili = ul.children[0].cloneNode(true);
    ul.appendChild(lili);
</script>

案例:动态创建表格

js 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    #box table {
      border-collapse: collapse;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <script src="common.js"></script>
  <script>
    // var s = {name: 'zs', subject: '语文', score: 90};
    // 模拟数据
    var datas = [
      {name: 'zs', subject: '语文', score: 90},
      {name: 'ls', subject: '数学', score: 80},
      {name: 'ww', subject: '英语', score: 99},
      {name: 'zl', subject: '英语', score: 100},
      {name: 'xs', subject: '英语', score: 60},
      {name: 'dc', subject: '英语', score: 70}
    ];

    // 表头数据
    var headDatas = ['姓名', '科目', '成绩', '操作'];

    // 1 创建table 元素
    var table = document.createElement('table');
    my$('box').appendChild(table);
    table.border = '1px';
    table.width = '400px';

    // 2 创建表头
    var thead = document.createElement('thead');
    table.appendChild(thead);

    var tr = document.createElement('tr');
    thead.appendChild(tr);
    tr.style.height = '40px';
    tr.style.backgroundColor = 'lightgray';

    // 遍历头部数据,创建th
    for (var i = 0; i < headDatas.length; i++) {
      var th = document.createElement('th');
      tr.appendChild(th);
      // th.innerText
      setInnerText(th, headDatas[i]);
    }
    
    // 3 创建数据行
    var tbody = document.createElement('tbody');
    table.appendChild(tbody);
    tbody.style.textAlign = 'center';
    for (var i = 0; i < datas.length; i++) {
      // 一个学生的成绩 {name: 'zl', subject: '英语', score: 100},
      var data = datas[i];
      tr = document.createElement('tr');
      tbody.appendChild(tr);

      // 遍历对象
      for (var key in data) {
        var td = document.createElement('td');
        tr.appendChild(td);
        setInnerText(td, data[key]);
      }
      // 生成删除对应的列
      td = document.createElement('td');
      tr.appendChild(td);
      // 删除的超链接
      var link = document.createElement('a');
      td.appendChild(link);
      link.href = 'javascript:void(0)';
      setInnerText(link, '删除');

      // 点击删除的事件
      link.onclick = linkDelete;
    }

    function linkDelete() {
      // 获取要删除的行
      var tr = this.parentNode.parentNode;
      tbody.removeChild(tr);
      return false;
    }
  </script>
</body>
</html>

案例:移动水果

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        select {
            width:200px;
            height: 200px;
            background-color: #33cccc;
            font-size: 20px;
        }
    </style>
</head>
<body>
  <select id="all" multiple="multiple">
    <option>苹果</option>
    <option>橘子</option>
    <option>梨</option>
    <option>西瓜</option>
    <option>水蜜桃</option>
  </select>

  <input type="button" value=">>" id="btn1">
  <input type="button" value="<<" id="btn2">
  <input type="button" value=">" id="btn3">
  <input type="button" value="<" id="btn4">

  <select id="select" multiple="multiple">
    
  </select>
  
  <script src="common.js"></script>
  <script>
    var all = my$('all');
    var select = my$('select');
    // 1 全部选择
    my$('btn1').onclick = function () {
      // 错误的 因为children中的项被移走之后,索引会重新排列
      // for (var i = 0; i < all.children.length; i++) {
      //   var option = all.children[i];
      //   select.appendChild(option);
      // }
      // 
      // 倒序遍历,是都可以全部移过去,但是水果的顺序发生颠倒了
      // for (var i = all.children.length - 1; i >= 0; i--) {
      //   var option = all.children[i];
      //   select.appendChild(option);
      // }
      // 
      // 先把children.length取出来,这样移动元素的时候就不会实时变化了
      // 现在len的值始终是当前获取到的all.children.length 当前个数5
      var len = all.children.length; 
      for (var i = 0; i < len; i++) {
        //一直获取索引为0的元素
        var option = all.children[0]; 
        select.appendChild(option);
      }

      // 补充:这种方式更简单,但是有很多弊端
      // select.innerHTML = all.innerHTML;
      // all.innerHTML = '';  
      // 这两行代码都有问题:
      // 第一行:使用这种方式移动子元素的话,如果子元素有事件,移动之后元素的事件丢失
      // 第二行:当我们使用innerHTML=''清空子元素的时候,如果子元素有事件,此时会发生内存泄漏
    }

    // 3 移动选中的水果
    my$('btn3').onclick = function () {
      // 先找到所有选中的option,再把option放到数组中,再把数组中的option移动到第二个select中
      // 这样就不用管元素索引变化的问题和元素顺序的问题
      var array = []; // 存储选中的option
      for (var i = 0; i < all.children.length; i++) {
        var option = all.children[i];
        if (option.selected) {
          array.push(option);
          // 去掉当前option的选中效果
          option.selected = false;
        }
      }

      // 把数组中的option移动到第二个select中
      for (var i = 0; i < array.length; i++) {
        var option = array[i];
        select.appendChild(option);
      }
    }
  </script>
</body>
</html>

(11) 创建元素的三种方式

js 复制代码
document.write()
element.innerHTML
document.createElement()

区别:

  1. document.write 是直接将内容写入页面的内容流,如果文档流已经执行完毕了,则它会导致页面全部重绘
  2. innerHTML 是将内容写入某个DOM节点,不会导致页面全部重绘
  3. innerHTML 创建多个元素效率更高(不要拼接字符串,采用数组形式拼接),结构稍微复杂
  4. createElement() 创建多个元素效率稍微低一点,但是结构更清晰

总结:不同浏览器下,innerHTML 效率要比 createElement 高

js 复制代码
<script>
    // 三种创建元素方式区别 
    // 方式1. document.write() 创建元素  如果页面文档流加载完毕,再调用这句话会导致页面重绘
     var btn = document.querySelector('button');
     btn.onclick = function() {
         document.write('<div>123</div>');
     }

    // 方式2. innerHTML 创建元素
    var inner = document.querySelector('.inner');
    // 这样一次一次设置,效率比较低
    // for (var i = 0; i <= 100; i++) {
    //     inner.innerHTML += '<a href="#">百度</a>'
    // }
    var arr = [];
    for (var i = 0; i <= 100; i++) {
        arr.push('<a href="#">百度</a>');
    }
    // 数组拼接的方式效率比较高
    inner.innerHTML = arr.join('');

    // 方式3. document.createElement() 创建元素
    var create = document.querySelector('.create');
    for (var i = 0; i <= 100; i++) {
        var a = document.createElement('a');
        create.appendChild(a);
    }
</script>

(12) innerTHML和createElement()效率对比

innerHTML字符串拼接方式(效率低):

js 复制代码
<script>
    function fn() {
        var d1 = +new Date();
        var str = '';
        for (var i = 0; i < 1000; i++) {
            // innerHTML字符串拼接方式(效率低)
            document.body.innerHTML += '<div style="width:100px; height:2px; border:1px solid blue;"></div>';
        }
        var d2 = +new Date();
        console.log(d2 - d1);
    }
    fn();
</script>

createElement方式(效率一般):

js 复制代码
<script>
    function fn() {
        var d1 = +new Date();

        for (var i = 0; i < 1000; i++) {
            // createElement方式(效率一般)
            var div = document.createElement('div');
            div.style.width = '100px';
            div.style.height = '2px';
            div.style.border = '1px solid red';
            document.body.appendChild(div);
        }
        var d2 = +new Date();
        console.log(d2 - d1);
    }
    fn();
</script>

innerHTML数组方式(效率高):

js 复制代码
<script>
    function fn() {
        var d1 = +new Date();
        var array = [];
        for (var i = 0; i < 1000; i++) {
            array.push('<div style="width:100px; height:2px; border:1px solid blue;"></div>');
        }
        // innerHTML数组方式(效率高)
        document.body.innerHTML = array.join('');
        var d2 = +new Date();
        console.log(d2 - d1);
    }
    fn();
</script>
相关推荐
To_OC3 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic4 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy4 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志4 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble5 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯6 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜7 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司7 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g8 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森8 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http