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>
相关推荐
懒大王爱吃狼30 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風4 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦6 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子6 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山6 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf9 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨9 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL9 小时前
npm入门教程1:npm简介
前端·npm·node.js