1. DOM

Document Object Model,文档对象模型,用于处理可扩展标记语言(HTML或XML)的标准API

2. 相关名词

【文档】一个页面就是一个文档,DOM中用 document 表示
【元素】页面中所有的标签都是元素,DOM中用 element 表示
【节点】网页中所有的内容都是节点(标签、属性、文本、注释等),DOM中使用 node 表示
★DOM把以上三种内容都看做是对象★

3. 获取元素

① 根据 id 获取

【方法】document.getElementById('元素的id'); // 返回对应 id 的整个标签(包括内容),数据类型为对象
★可以用 console.dir(对象名); 来输出对象,会在终端展示该对象的所有属性和方法★

② 根据标签名获取

【方法】document.getElementsByTagName('标签名'); // 返回带有指定标签名的对象的集合(伪数组)
【伪数组】有长度,有索引号,但没有 push 、pop 等方法。
【注意1】得到的是一个对象的集合,若想操作里面的元素就需要遍历这个伪数组(for循环)。
【注意2】得到的元素是动态的,会随着页面内的标签的变化而变化。
【注意3】如果想要获取的标签在页面中只有一个,通过该API返回的依旧是伪数组。
【注意4】如果想要获取的标签在页面中不存在,通过该API返回的是空的伪数组。

【方法】element.getElementsByTagName('标签名'); // 获取某父元素内部的所有指定标签名的子元素
【注意1】element 填的是父元素,且该父元素必须是已经获取的★单个对象★,而不能是对象的集合。
【注意2】该API返回的也是对象的集合(伪数组),且不包括父元素。

  1. var ol = document.getElementsByTagName('ol'); // 先获取父元素 ol 的对象集合(伪数组)
  2. // console.log( ol.getElementsByTagName('li') ); 错误写法,此时的 ol 是对象集合,并非单个对象
  3. console.log( ol[0].getElementsByTagName('li') ); 正确写法,ol[0] 是单个对象,里面存放 li 对象集合

③ 通过HTML5新增的方法获取(IE9+)

【方法1】document.getElementsByClassName('类名'); // 获取带有指定类名的标签的对象集合(伪数组)
【方法2】document.querySelector('选择器名'); // 获取第一个带有指定CSS选择器名的标签的对象
【方法3】document.querySelectorAll('选择器名'); // 获取所有带有指定CSS选择器名的标签的对象集合
【选择器名】标签:div / 类名:.header / ID:#myHeader

④ 特殊元素的获取

【方法1】document.body; // 返回body元素对象
【方法2】document.documentElement; // 返回html元素对象

4. 事件基础

① 事件源

  • 事件被触发的对象

【例】var btn = document.getElementById('btn'); // 获取页面上的一个按钮对象作为事件源

② 事件类型

  • 触发哪个事件源、如何触发事件(比如鼠标点击、鼠标经过、按下键盘的某个键等)

【例】btn.onclick // 触发事件源是按钮,触发方式是鼠标点击

③ 事件处理程序

  • 通过一个函数赋值的方式来完成

【例】btn.onclick = function(){ alert('You click me!'); }

④ 总结【执行事件的步骤】

  • 获取事件源
  • 注册事件(绑定事件),不注册则页面一刷新就会执行一次事件
  • 添加事件处理程序(采取函数赋值的形式)

    ⑤ 常见鼠标事件

  • onclick 鼠标点击左键触发

  • onmouseover 鼠标经过触发
  • onmouseout 鼠标离开触发
  • onfocus 获得鼠标焦点触发
  • onblur 失去鼠标焦点触发
  • onmousemove 鼠标移动触发
  • onmouseup 鼠标弹起触发
  • onmousedown 鼠标按下触发

    5. 操作元素

    ① 操作元素内容【操作元素对象的 innerText / innerHTML 属性】

    【方法1】element.innerText = '属性值';
    【特点】可读写,不识别HTML标签,写入的内容全当作文本,读取的内容会去除空格与换行,非W3C标准
    【方法2】element.innerHTML = '属性值';
    【特点】可读写,写入的内容能识别HTML标签,读取的内容会保留空格与换行,W3C标准推荐使用

    ② 操作元素属性【修改元素对象的属性】

    【例】点击按钮切换图片

    1. <botton id="ldh">刘德华</botton>
    2. <botton id="zxy">张学友</botton><br>
    3. <img src="images/LDH.jpg" title="刘德华">
    // 获取元素对象
    var ldh = document.getElementById('ldh');
    var zxy = document.getElementById('zxy');
    var img = document.querySelector('img');
    // 注册事件、处理程序
    zxy.onclick = function(){
    img.src = 'images/ZXY.jpg';
    img.title = '张学友';
    }
    ldh.onclick = function(){
    img.src = 'images/LDH.jpg';
    img.title = '刘德华';
    }
    

    ③ 操作表单属性【修改 type、value、checked、selected、disabled 等属性】

    【注意】表单内的文本信息是通过 value 属性来获得的
    【例1】

    <button>按钮</button>
    <input type="text" value="输入内容">
    
    // 获取元素对象
    var btn = document.querySelector('button');
    var input = document.querySelector('input');
    // 注册事件、处理程序
    btn.onclick = function ( ) {
    // input.innerHTML = '点击了';  这个是用于修改普通盒子比如 div 标签里面的内容
    // 表单里面的文字内容是通过 value 来修改的
    input.value = '被点击了';
    // 如果想要某个表单被禁用不能再点击 利用 disabled 把 button 禁用
    this.disabled = true;  // 即 btn.disabled = true;  this指向时间调用者btn
    }
    

    【例2】输入框内显示或隐藏密码

    <div class="box">
    <label for="">
      <img src="images/close.png" id="eye">
    </label>
    <input type="password" id="pwd">
    </div>
    
    // 获取元素对象
    var eye = document.getElementById('eye');
    var pwd = document.getElementById('pwd');
    // 注册事件、处理程序
    var flag = 0;   // 核心 -> 利用 flag 变量来标记当前密码的状态
    eye.onclick = function() {
    if (flag==0) {
      pwd.type = 'text';
      eye.src = 'images/open.png';
      flag = 1;
    } else {
      pwd.type = 'password';
      eye.src = 'images/close.png';
      flag = 0;
    }
    }
    

    ④ 操作样式属性【修改元素的大小、颜色、位置等样式】

    【方法1】element.style.xxxxx = '属性值';
    【特点】

  • xxxxx 为该元素的样式属性,采用驼峰命名法,需要把 background-color 写成 backgroudColor;

  • xxxxx 可以是已经定义好的属性,也可以增加新的属性;
  • 该方法修改后的样式为 行内样式 ,权重比较大,需注意样式覆盖问题;
  • 该方法适用于 修改的样式较少 或 功能简单 的情况,功能多样式复杂的话请使用下方属性2。

【例1】可关闭的二维码图片

<div class="box">
  淘宝二维码
  <img src="images/tao.png" alt="">
  <i class="close-btn">x</i>   // 利用“父相子绝”把关闭小叉定位到二维码外侧左上角
</div>
// 获取元素对象
var btn = document.querySelector('.close-btn');
var box = document.querySelector('.box');
// 注册事件、处理程序
btn.onclick = function(){
  box.style.dispaly = 'none';   // 利用 display 属性隐藏该元素,并没有删除该元素
}

【例2】循环精灵图,平铺在不同的 li 标签上
【前提】精灵图的排列有规律(排成一行或者一列)
【核心】利用 for 循环,修改精灵图的位置,即修改 backgroundPosition 属性

// 获取元素对象
var lis = document.querySelectorAll('li');    // 获取所有的 li 标签
// 处理程序(前提:精灵图排成一列)
for (var i = 0; i < lis.length; i++) {
  // 让索引号 i 乘以 44 就是每个 li 标签需要的背景 y坐标 ,而 index 为计算后的 y坐标
  var index = i * 44;
  lis[i].style.backgroundPosition = '0  -' + index + 'px';   // 属性值可由字符串和变量拼接而成
}

【例3】显示或隐藏文本框内容(鼠标点击文本框,里面内容隐藏;鼠标点击文本框外面,内容出现)
【步骤①】首先表单需要注册两个新事件:获取焦点 onfocus、失去焦点 onblur
【步骤②】如果获得焦点,判断表单内容是否为默认文本,如果是默认文本就清空表单内容
【步骤③】如果失去焦点,判断表单内容是否为空,如果为空,则表单内容改为默认文本

// 获取元素对象( <input type="text" value="这就是内容"> )
var text = document.querySelector('input');
// 注册事件、处理程序
text.onfocus = function() {
  if ( this.value === '这就是内容' ) {
    this.value = '';
  }
  // 获得焦点后,需要把文本框里面的文字颜色变黑(默认文本是灰色的)
  this.style.color = '#333';
}
text.onblur = function() {
  if ( this.value === '' ) {
    this.value = '这就是内容';
  }
  // 失去焦点后,需要把文本框里面的文字颜色变浅色
  this.style.color = '#999';
}

【方法2】element.className = '新的类名';
【原理】为某个元素更换类名(覆盖),以达到修改样式的效果。
【注意】

  • 单引号内直接填类名即可,不是填类选择器,所以不需要加 . ;
  • 该方法会直接覆盖原来的类名,如果要保留原类名,可以把原类名和新类名一起写上;
  • 该方法适用于修改大量样式。

【例】密码框 格式要求提示 / 格式错误提示 / 格式正确提示
【要求】如果用户离开密码框时里面输入的字符个数不是6~16,则提示错误信息,否则提示输入正确信息
【分析】

  • 首先判断的事件是表单失去焦点 onblur;
  • 如果输入正确则提示正确的信息颜色为绿色小图标变化;
  • 如果输入不是6到16位,则提示错误信息颜色为红色小图标变化。

    <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: url(images/wrong.png);
    }
    .right {
      color: green;
      background: url(images/right.png);
    }
    </style>
    <body>
    <div class="register">
      <input type="password" class="ipt">
      <p class="message">请输入6~16位密码</p>
    </div>
    </body>
    
    // 获取元素对象
    var ipt = document.querySelector('.ipt');
    var msg = document.querySelector('.message');
    // 注册事件、处理程序
    ipt.onblur = function() {
    if (this.value.length < 6 || this.value.length >16) {
      msg.className = 'messasge wrong';
      msg.innerHTML = '密码位数不正确 要求6~16位';
    } else {
      msg.className = 'messasge right';
      msg.innerHTML = '密码格式正确';
    }
    }
    

    ⑤ 排他算法【选中一个元素后,其余元素变成未选中状态】

    【原理】先把所有元素恢复为未选中状态,再单独给选中的元素更改为选中状态。
    【例1】选中的按钮的背景颜色改为 pink ,未选中的按钮背景色为空(默认色)

    <body>
    <button>按钮1</button>
    <button>按钮2</button>
    <button>按钮3</button>
    <button>按钮4</button>
    <button>按钮5</button>
    </body>
    
    // 获取元素对象(所有按钮)
    var btns = document.getElementsByTagName('button');
    // 注册事件、处理程序
    for (var i = 0; i < btns.length; i++ ) {
    bths[i].onclick = function(){
      // 先把所有按钮恢复为未选中状态
      for (var i = 0; i < btns.length; i++) {
        btns[i].style.backgroundColor = '';
      }
      // 再单独给选中的元素更改为选中状态
      this.style.backgroundColor = 'pink';
    }
    }
    

    【例2】点击图片换皮肤(更换网页背景图片)

    <body>
    <ul class="skin">
      <li><img src="images/1.jpg"></li>
      <li><img src="images/2.jpg"></li>
      <li><img src="images/3.jpg"></li>
      <li><img src="images/4.jpg"></li>
    </ul>
    </body>
    
    // 获取元素对象(所有背景图片,利用嵌套选择使选择更具体更严谨)
    var imgs = document.querySelector('.skin').querySelectorAll('img');
    // 注册事件、处理程序
    for (var i = 0; i < imgs.length; i++ ) {
    imgs[i].onclick = function(){
      document.body.style.backgroundImage = 'url(' + this.src + ')';
    }
    }
    

    【例3】表格变色(鼠标指向哪一行,哪一行就变色,与其他行做出区分效果)
    【回忆】table 表格整体、thead 表头、tbody 表身、tfoot 表尾、tr 表格行、th 表格头、td 表格单元
    【分析】

  • 表格第一行用包裹,第二行开始用包裹;

  • 获取元素对象时只需获取 tbody 内的所有 tr 即可,因为第一行不展示数据,不需要变色;
  • 新的鼠标事件:鼠标经过 onmouseover / 鼠标离开 onmouseout。

    <body>
    <table>
      <thead>
        <tr>
          <th>班级</th>
          <th>学号</th>
          <th>姓名</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>一班</td>
          <td>0101</td>
          <td>张三</td>
        <tr>
          <td>一班</td>
          <td>0102</td>
          <td>李四</td>
        </tr>
        <tr>
          <td>一班</td>
          <td>0103</td>
          <td>王五</td>
        </tr>
      </tbody>
    </table>
    </body>
    
    // 获取元素对象(利用嵌套选择使选择更具体更严谨)
    var trs = document.querySelector('tbody').querySelectorAll('tr');
    // 注册事件、处理程序
    for (var i = 0; i < imgs.length; i++ ) {
    trs[i].onmouseover = function(){
      this.className = 'highlight';      // 为当前行增加一个高光类名实现变色,同时方便后期修改
    }
    trs[i].onmouseout = function(){
      this.className = '';
    }
    }
    

    【例4】表单全选 / 取消全选
    【分析】

  • 全选和取消全选:让下方所有单选按钮的checked属性的值(true/false)跟随全选按钮;

  • 所有单选按钮被选中,全选按钮才被选中:给所有单选按钮注册事件,每次点击都循环查询一次是否所有单选按钮都被选中,是则选中全选按钮,否则不选中全选按钮;
  • 可以设置一个 flag 变量,来控制全选按钮是否被选中。

    <body>
    <table>
      <thead>
        <tr>
          <th>
            <input type="checkbox" id="checkAll">  <!-- 全选按钮 -->
          </th>
          <th>商品</th>
          <th>价格</th>
        </tr>
      </thead>
      <tbody id="checkTable">
        <tr>
          <td>
            <input type="checkbox">  <!-- 每一行的单选按钮 -->
          </td>
          <td>苹果</td>
          <td>100元</td>
        <tr>
          <td>
            <input type="checkbox">  <!-- 每一行的单选按钮 -->
          </td>
          <td>西瓜</td>
          <td>200元</td>
        </tr>
        <tr>
          <td>
            <input type="checkbox">  <!-- 每一行的单选按钮 -->
          </td>
          <td>芒果</td>
          <td>300元</td>
        </tr>
      </tbody>
    </table>
    </body>
    
    // 获取元素对象
    var ckAll = document.getElementById('checkAll');  // 全选按钮对象
    var ckOnes = document.getElementById('checkTable').querySelectorAll('input');  // 单选按钮对象集合
    // 注册事件、处理程序
    // 全选按钮
    ckAll.onclick = function(){
    for (var i = 0; i < ckOnes.length; i++) {
      ckOnes[i].checked = this.checked;
    }
    }
    // 单选按钮
    for (var i = 0; i < ckOnes.length; i++ ) {
    ckOnes[i].onclick = function(){
      // flag 用于控制全选按钮是否被选中
      var flag = true;
      // 每次点击下面的复选框都要循环检查4个单选按钮是否全被选中
      for (var i = 0; i < ckOnes.length; i++) {
        // 如果有未选中的单选按钮 (ckOnes[i].checked = false) ,则更改 flag 变量的状态
        if ( !ckOnes[i].checked ) {
          flag = false;
          break;
        }
      }
      ckAll.checked = flag;  // 最后把 flag 赋值给全选按钮的checked属性
    }
    }
    

    6. 自定义属性的操作

    ① 获取元素的属性值

    【方法1】element.属性名
    【方法2】element.getAttribute('属性名')
    【区别】 方法1 只能获取 内置属性的值 (元素本身自带的属性),只有 方法2 才可以获取程序员 自定义属性的值
    【如何自定义属性?】直接在标签内写上即可,例如


    【例】

    <div id="demo" ljm-index="123"></div>
    <script>
    var div = document. querySelector('div');
    console.log( div.id );   // demo
    console.log( div.getAttribute('id') );   // demo
    console.log( div.getAttribute('ljm-index') );   // 123
    </script>
    

    ② 设置元素的属性值

    【方法1】element.属性名 = '属性值';
    【方法2】element.setAttribute('属性名','属性值');
    【区别】与获取的区别一样
    【例】

    <div id="demo" ljm-index="123"></div>
    <script>
    var div = document. querySelector('div');
    div.id = 'test';
    div.setAttribute('ljm-index','sbljm');
    console.log( div.id );   // test
    console.log( div.getAttribute('ljm-index') );   // sbljm
    </script>
    

    ③ 移除属性

    【方法】element.removeAttribute('属性名');
    【作用】主要用于移除程序员自定义的属性。

    ④ 获取元素的属性集合(属性节点)

    【方法】element.attributes;
    【说明】

  • 能获取元素的所有属性,包括内置属性和自定义属性;

  • 在属性节点内部,属性按照它们在源代码中出现的顺序来排序,索引从0开始;
  • 同样可以用 length 属性返回伪数组的长度。

【例】

<ul id="box" data-url="index.html" node-action="submit">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>
<script>
  var box = document.getElementById('box');
  var attrs = box.attributes;
  console.log( attrs );
  // NamedNodeMap {0: id, 1: data-url, 2: node-action, id: id, data-url: data-url, node-action: node-action, length: 3}
  console.log( attrs.length );          // 3
  console.log( attrs[0] );              // id="box"(这里输出的是键值对)
  console.log( attrs[0].name );         // id(这里输出的是属性名)
  console.log( attrs[0].value );        // box(这里输出的是属性值)
  console.log( attrs.item(1) );         // data-url="index.html"
  console.log( attrs['node-action'] );  // node-action="submit"
</script>

【案例】Tab栏切换(重点必会)
【代码较长,请查看“Tab栏切换案例.html”文件,已做好注释】

7. H5的自定义属性标准

【问题】有些自定义属性很容易引起歧义,不容易判断是元素的内置属性还是自定义属性。
【解决】H5标准规定,自定义属性需要以 data- 开头作为属性名并且赋值。

  • <div data-index="1"></div>
  • div.setAttribute('data-index', 1);

【新增】H5新增的获取自定义属性的方法(只能获取到符合H5标准的自定义属性名,IE11+)

  • 【方法1】element.dataset.去掉data-的自定义属性名
  • 【方法2】element.dataset['去掉data-的自定义属性名']

【例1】dataset 是一个集合,里面存放着所有以 data- 开头的自定义属性

<div data-index="1"></div>
<script>
  var div = document.querySelector('div');
  console.log( div.dataset.index );     // 1
  console.log( div.dataset['index'] );  // 1
</script>

【例2】对于这种有多个 - 的自定义属性,必须使用驼峰命名法才能获取到

<div data-list-name="lsp"></div>
<script>
  var div = document.querySelector('div');
  console.log( div.dataset.listName );     // lsp
  console.log( div.dataset['listName'] );  // lsp
</script>

【总结】

  • 日后实际开发中,自定义属性记得要以 data- 开头;
  • H5 新增的获取自定义属性的方法局限性和兼容性问题较大,不建议使用;
  • 推荐使用 getAttribute 和 setAttribute 方法,它们的兼容性非常好。

    8. 节点操作

    ① 节点概述

    【描述】

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

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

【组成】节点至少拥有 nodeType(节点类型)、nodeName(节点名称)、nodeValue(节点值) 这三个基本属性
【节点类型】

  • 元素节点 nodeType 值为 1(实际开发中主要操作的节点就是元素节点)
  • 属性节点 nodeType 值为 2
  • 文本节点 nodeType 值为 3(文本节点包含文字、空格、换行等)

    ② 获取元素节点

    【原理】基于元素的父子、兄弟关系来获取指定的元素节点
    【特点】逻辑性强,但兼容性稍差
    【方法1】 node.parentNode
    【说明】

  • 该方法可返回某节点的父级节点,注意是距离元素最近的一个父级节点;

  • 如果某节点不存在父级节点则返回 null。

【方法2】 parentNode.childNodes(标准,不常用)
【说明】

  • 该方法可返回所有子节点的集合,该集合是一个能实时更新的集合;
  • 返回的集合里不仅有元素节点,还有文本节点(标签括住的文字、代码中为了换行而敲的回车);
  • 如果只想要获得里面的元素节点则需要专门处理,所以开发中一般不提倡使用 childNodes。

【方法3】 parentNode.children(非标准,常用)
【说明】

  • 它是一个只读属性,只返回所有的子元素节点,其余节点不返回;
  • 虽然它是非标准属性,但是得到了各版本浏览器的攴持,因此可以放心使用。

【方法4】 parentNode.firstChild / lastChild
【说明】

  • 该方法可返回第一个或最后一个子节点;
  • 注意包含元素节点和文本节点;
  • 不存在则返回 null。

【方法5】 parentNode.firstElementChild / lastElementChild(兼容性:IE9+)
【说明】

  • 该方法可返回第一个或最后一个子元素节点;不存在则返回 null;
  • 因为存在兼容性问题,故实际开发中用 parentNode.children[0] 来获取第一个子元素节点;
  • parentNode.children[parentNode.children.length - 1] 来获取最后一个子元素节点。

【方法6】 node.nextSibling / previousSibling
【说明】

  • 该方法可返回当前元素的上 / 下一个兄弟节点;
  • 注意包含元素节点和文本节点;
  • 不存在则返回 null。

【方法7】 parentNode.nextElementSibling / previousElementSibling(兼容性:IE9+)
【说明】

  • 该方法可返回当前元素的上 / 下一个兄弟元素节点,不存在则返回 null;
  • 因为存在兼容性问题,故实际开发中需要自己封装一个兼容性函数,代码如下。

【兼容性函数】获取下一个兄弟元素节点

function getNextElementSibling(element){
  var el = element;
  while(el = el.nextSibling){
    if ( el.nodeType === 1) {
      return el;
    }
  }
  return null;
}

【案例】下拉菜单(重点必会)
【代码较长,请查看“下拉菜单案例.html”文件,已做好注释】

③ 创建和添加节点

【方法1】 document.createElement('标签名');
【说明】

  • 该方法可动态创建元素节点;
  • 使用该方法后,新的元素节点并不会立刻出现在页面中,还需要一个添加节点的操作。

【方法2】 parentNode.appendChild(childNode);
【说明】

  • 该方法可将某个元素节点添加到指定父节点的子节点列表末尾;
  • 类似于 CSS 里面的 after 伪元素。
    <body>
    <ul>
      <li>123</li>
      <!-- 新创建的 li 元素节点会被添加到此处 -->
    </ul>
    </body>
    <script>
    var ul = document.querySelector('ul');   // 获取父级节点
    var li = document.createElement('li');   // 创建新的元素节点
    ul.appendChild(li);
    </script>
    

【方法3】 parentNode.insertBefore(childNode , 指定子级元素节点);
【说明】

  • 该方法可将某个元素节点添加到指定父节点内某个指定子节点的前面;
  • 类似于 CSS 里面的 before 伪元素。
    <body>
    <ul>
      <!-- 新创建的 li 元素节点会被添加到此处 -->
      <li>123</li>
    </ul>
    </body>
    <script>
    var ul = document.querySelector('ul');   // 获取父级节点
    var li = document.createElement('li');   // 创建新的元素节点
    ul.insertBefore(li , ul.chidren[0]);     // ul.chidren[0] 指定了 ul 内的第一个子级节点  
    </script>
    
    【总结】在页面内添加一个新的元素节点分两步:先创建,后添加。
    【案例】发布留言简单版(重点必会)
    【代码较长,请查看“发布留言简单版案例.html”文件,已做好注释】

【补充】三种动态创建元素的区别
【第一种】 document.write('创建内容');

  • 该方法是直接将内容写入页面的内容流;
  • 若是等文档流执行完毕(页面加载完毕)后再执行该方法,则会导致页面全部重绘(当前页面的内容只有该方法中写好的内容);
  • 实际开发中很少使用。

【第二种】 element.innerHTML = '创建内容';

  • innerHTML 是将内容写入某个DOM节点,不会导致页面全部重绘;
  • 在创建多个元素效率更高,但注意不要用 ‘+=’ 来拼接字符串,而是要采取数组形式来拼接。 ```javascript // 用 ‘+=’ 拼接字符串,根据字符串不可变的特性,每次拼接都会消耗大量内存,所以效率特别低 for (var i = 0; i < 1000; i++) { document.body.innerHTML += ‘
    ‘; }

// 用数组形式,先拼接字符串,再一次性写入到 DOM 里,这样效率最高,但是结构比较复杂 var array = []; for (var i = 0; i < 1000; i++) { array.push(‘

‘); } document.body.innerHTML = array.join(‘’);

【第三种】 **`document.createElement('标签名');`**

- 每次只能创建一个标签,效率相比 innerHTML 的数组形式稍微低一点;
- 但结构清晰,容易操作。
<a name="uIbk9"></a>
#### ④ 删除节点
【方法】**`parentNode.removeChild(childNode);`**<br />【说明】

- 该方法可从 DOM 中删除指定父节点中的某个子节点;
- 返回删除的结点。

【案例】删除留言简单版(重点必会)<br />【代码较长,请查看“删除留言简单版案例.html”文件,已做好注释】
<a name="attF5"></a>
#### ⑤ 复制节点
【方法】**`node.cloneNode();`**<br />【说明】返回调用该方法的结点的一个副本。<br />【注意】

- 如果括号内参数为空或者为 false,则是浅复制,即只复制最外层的节点,不复制子节点;
- 如果括号内参数为 true,则是深复制,能够复制调用该方法的结点的所有内容。
```html
<body>
  <ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
</body>
<script>
  var ul = document.querySelector('ul');     // 获取父级节点
  var liClone1 = ul.children[0].cloneNode();   // 浅复制,只复制了 <li></li>,没有内容
  var liClone2 = ul.children[0].cloneNode(true);  // 深复制,复制了 <li>1</li>
</script>

【案例】动态生成表格(重点必会)
【代码较长,请查看“动态生成表格案例.html”文件,已做好注释】

9. 高级事件

① 注册事件的两种方式

【第一种】传统方式:利用 on 开头的事件,例如 onclick、onmouseover、onmouseout 等。
【特点】注册事件的唯一性:同一个对象同一个事件只能设置一个处理函数,最后注册的处理函数将会覆盖前面注册的处理函数。
【第二种】方法监听方式:W3C标准的推荐方式。
【特点】同一个对象同一个事件可以注册多个监听器(处理函数),它们会按注册顺序依次执行。

【方法1】 eventTarget.addEventListener(type , listener [, useCapture])
【参数】

  • type:事件类型字符串,比如 ‘click’、’mouseover’、’mouseout’ 等,注意不需要带 on;
  • listener:事件处理函数,在事件被触发时调用;
  • useCapture:可选参数,是一个布尔值,默认 false,用于指定事件流的方向(具体看第③点)

【说明】

  • 该方法能将指定的 listener (事件处理函数) 注册到 eventTarget (目标对象) 上;
  • 当该对象触发指定事件时,就会依次执行注册的所有事件处理函数;
  • 兼容性,IE9及以上的浏览器才支持,IE7、IE8使用 attachEvent()
    <body>
    <button>方法监听方式</button>
    </body>
    <script>
    var btn = document.querySelector('button');
    // 特别注意,里面的事件类型是字符串,要带单引号 ' ' ,而且不需要写 on
    btn.addEventListener('click' , function(){
      alert( '注册成功!第一次!' );
    })
    // 同一个对象(btn)同一个事件('click')可注册多个监听器,依次执行
    btn.addEventListener('click' , function(){
      alert( '注册成功!第二次!' );
    })
    </script>
    

【方法2】 eventTarget.attachEvent(eventNameWithOn , callback)
【参数】

  • eventNameWithOn:事件类型字符串,比如 ‘onclick’、’onmouseover’ 等,注意需要带 on;
  • callback:事件处理函数,在事件触发时回调函数被调用。

【说明】兼容性:仅支持在IE7、IE8上使用

<body>
  <button>方法监听方式</button>
</body>
<script>
  var btn = document.querySelector('button');
  // 特别注意,里面的事件类型是字符串,要带单引号 '' ,而且需要写 on
  btn.attachEvent('onclick' , function(){
    alert( '仅支持在IE7、IE8上使用!' );
  } )
</script>

【注册事件兼容性解决方案】

function addEventListener(element , eventName , func){
  // 先判断当前浏览器是否支持 addEventListener 方法
  if ( element.addEventListener ) {
    element.addEventListener(eventName , func);  // 第三个参数默认是 false
    // 再判断当前浏览器是否支持 attachEvent 方法
  } else if ( element.attachEvent ) {
    element.attachEvent('on' + eventName , func);
    // 如果前两种都不行,用传统方式来注册事件(无兼容性问题)
  } else {
    element['on' + eventName] = func;   // 相当于 element.onclick = func;
  }

② 删除事件(解绑事件)

【传统删除方式】eventTarget.eventNameWithOn = null;
【方法监听方式】
【方法1】 eventTarget.removeEventListener(type , listener [, useCapture])
【参数】同上
【说明】这里的 listener 不能是匿名处理函数;兼容性同上。

<body>
  <button>方法监听方式</button>
</body>
<script>
  var btn = document.querySelector('button');
  function alertFn( ) {
    alert( '我不是匿名处理函数' );
    btn.removeEventListener( 'click' , alertFn )  // 该处理函数执行一次后就会被删除(解绑)
  }
  // alertFn 后面不需要加括号,因为加括号后是调用函数的意思,而这里只是绑定事件和函数,不是调用
  btn.addEventListener( 'click' , alertFn )
</script>

【方法2】 eventTarget.detachEvent(eventNameWithOn , callback)
【参数】同上
【说明】这里的 callback 不能是匿名处理函数;兼容性同上。
【解绑事件兼容性解决方案】

function addEventListener(element , eventName , func){
  if ( element.removeEventListener ) {
    element.removeEventListener(eventName , func);
  } else if ( element.detachEvent ) {
    element.detachEvent('on' + eventName , func);
  } else {
    element['on' + eventName] = null;
  }

③ DOM事件流

【描述】相同类型的事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程叫做DOM事件流。
【三大阶段】

  • 事件捕获阶段:由 DOM 最顶层节点开始,逐级向下传播,直至目标节点;
  • 当前目标阶段:作为捕获阶段的终点,或者是冒泡阶段的起点;
  • 事件冒泡阶段:由目标节点开始,逐级向上传播,直至 DOM 最顶层节点。

【注意】

  • JavaScript 的事件流只能在捕获或者冒泡其中的一个阶段上执行(即事件流的方向不能中途改变);
  • onclick 和 attachEvent 只能在冒泡阶段上执行;
  • addEventListener( type , listener [, useCapture] ) 的第三个参数就是用于指定事件流的阶段:
    • 参数是 true,表示事件流处于捕获阶段;
    • 参数是 false 或者 省略不写,表示事件流处于冒泡阶段。
  • 【特例】有些事件类型是不会冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave 等。

    <body>
    <div class="father">
      father 盒子
      <div class="son">son 盒子</div>
    </div>
    </body>
    <script>
    var son = document.querySelector('.son');
    var father = document.querySelector('.father');
    
    // 捕获阶段,参数为 true,事件流的顺序为 document -> html -> body -> father -> son
    son.addEventListener( 'click' , funtion(){
      alert('son');        // 出现顺序 3
    } , true );
    father.addEventListener( 'click' , funtion(){
      alert('father');     // 出现顺序 2
    } , true );
    document.addEventListener( 'click' , funtion(){
      alert('document');   // 出现顺序 1
    } , true );
    
    // 冒泡阶段,参数为 false 或 省略,事件流的顺序为 son -> father -> body -> html -> document
    son.addEventListener( 'click' , funtion(){
      alert('son');        // 出现顺序 1
    } , false );
    father.addEventListener( 'click' , funtion(){
      alert('father');     // 出现顺序 2
    } , false );
    document.addEventListener( 'click' , funtion(){
      alert('document');   // 出现顺序 3
    } , false );
    // 注意,以上两个阶段的代码(相同目标节点、相同事件类型)不能同时出现,不然会报错
    </script>A
    

    ④ 事件对象

    【描述】事件对象是事件的一系列相关数据的集合,跟事件类型相关。例如点击事件里就包含有鼠标的坐标,被点击的元素名称等信息。

    var div = document.querySelector('div');
    div.onclick = function(event){
    console.log(event);
    }
    div.addEventListener('click' , function(e){
    console.log(e);
    })
    

    【说明】

  • 上面的 event 和 e 就是所谓的事件对象,被写在处理函数的小括号里,可当作形参来看;

  • 事件对象必须依靠事件才能存在,它是系统根据事件类型自动创建的,不需要我们传递参数;
  • 事件对象的命名没有额外的要求,一般命名为 event、evt、e 。

【兼容性问题】在 IE6,7,8上需要通过 window.event 来获取事件对象。
【问题说明】在处理函数的小括号里可以同上面所述那样写自己命名的事件对象,但获取时只能通过 window.event 来获取。
【解决方案】假设事件对象取名为 e,待事件执行时,通过 e = e || window.event; 来获取事件对象。

div.onclick = function(e) {
  e = e || window.event;   // 根据短路运算原理,若浏览器能识别 e 则会自动忽略 window.event
  console.log(e);   // 此时输出就不会报错了
}

【常见属性和方法】

  • e.target 该属性返回触发事件的对象(标准)
  • e.srcElement 该属性返回触发事件的对象(非标准,IE6 7 8)
  • e.type 该属性返回事件的类型(比如 click、mouseover 不带 on)
  • e.cancelBubble 该属性阻止冒泡,需赋值 true(非标准,IE6 7 8)
  • e.returnValue 该属性阻止默认事件,需赋值 false(非标准,IE6 7 8,比如不允许链接跳转)
  • e.stopPropagation() 该方法阻止冒泡(标准)
  • e.preventDefault() 该方法阻止默认事件(标准,比如不允许提交按钮去提交表单数据)

【e.target 和 this 的区别】

<body>
  <ul>
    <li>1234</li>
    <li>abcd</li>
    <li>&&&</li>
  </ul>
</body>
<script>
  var ul = document.querySelector('ul');
  ul.addEventListener('click' , function(e){
    // e.target 指向用户点击的对象;如果我们点击 li,由于事件冒泡,会触发 ul 的事件,但返回的还是 li
    console.log( e.target );
    // this 指向绑定事件的对象;如果我们点击 li,由于事件冒泡,会触发 ul 的事件,所以返回的是 ul
    console.log( this );
    console.log( e.currentTarget );  // 与 this 同理,实际开发中不常用,不支持 IE6 7 8
  } )
  // e.target 和 this 的妙用:判断 e.target == this ,true 则执行处理函数,false 则终止函数
  // 优点:可阻止事件捕获以及事件冒泡;缺点:所有事件都需要进行一次判断,效率较低。
</script>

【阻止默认行为】

var a = document.querySelector('a');
// DOM 标准写法,常用
a.addEventListener('click' , function(e){
  e.preventDefault();
});
// 传统注册方式
a.onclick = function(e){
  // 兼容性处理
  e = e || window.event;
  // 主流浏览器
  e.preventDefault();
  // 低版本浏览器 IE6 7 8
  e.returnValue = false;
  // 所有浏览器通用方式,但只限于传统注册方式,且该语句后面的代码不会执行
  return false;
}

【阻止事件冒泡】

var a = document.querySelector('a');
// DOM 标准写法,常用
a.addEventListener('click' , function(e){
  e.stopPropagation();
});
// 传统注册方式
a.onclick = function(e){
  // 兼容性处理
  e = e || window.event;
  // 主流浏览器
  e.stopPropagation();
  // 低版本浏览器 IE6 7 8
  e.cancelBubble = true;
}

【小技巧】

// 1. contextmenu 禁用右键菜单
document.addEventListener('contextmenu' , function(e){
  e.preventDefault();
)}

// 2. selectstart 禁止选中文字
document.addEventListener('selectstart' , function(e){
  e.preventDefault();
)}

⑤ 事件委托

【原理】DOM 事件流的冒泡阶段
【做法】不给每个子节点单独设置事件监听器,而是把事件监听器设置在其父节点上,然后利用事件对象的 target 属性,即可获取到用户点击的具体子元素节点。
【作用】简化代码,提高性能。

⑥ 鼠标 / 键盘事件对象(MouseEvent、KeyboardEvent)

【鼠标事件对象的常见属性】

  • e.clientX 返回鼠标相对于 [ 浏览器窗口可视区 ] 的 X 坐标
  • e.clientY 返回鼠标相对于 [ 浏览器窗口可视区 ] 的 Y 坐标
  • e.pageX 返回鼠标相对于 [ 文档页面的 ] 的 X 坐标 ( IE9+支持,开发常用 )
  • e.pageY 返回鼠标相对于 [ 文档页面的 ] 的 Y 坐标 ( IE9+支持,开发常用 )
  • e.screenX 返回鼠标相对于 [ 电脑屏幕 ] 的 Ⅹ 坐标
  • e.screenY 返回鼠标相对于 [ 电脑屏幕 ] 的 Y 坐标

    ⑦ 事件 mouseenter 和 mouseover 的区别

    【事件描述】当鼠标移动到元素上时就会触发该事件,两者的描述一致
    【区别】

  • mouseover 鼠标经过自身盒子时会触发一次,经过自身的子盒子时会再额外触发一次

  • mouseenter 鼠标经过自身盒子时会触发一次,但经过自身的子盒子时不再额外触发
  • 原因:mouseenter 事件不会冒泡,同样的,mouseleave 事件也不会冒泡

【案例】图片跟随鼠标移动
【分析】

  • 鼠标不断移动,使用鼠标移动事件:mousemove;
  • 因为是在页面中移动,所以可以直接给 document 注册事件;
  • 图片移动的同时不占文档流的任何位置,故使用绝对定位;
  • 核心原理:每次移动鼠标都实时获取鼠标的坐标,再把该坐标给图片的 left 和 top 值即可。

    <style>
    img {
      position: absolute;
    }
    </style>
    <body>
    <img src="images/angel.gif" >
    </body>
    <script>
    var img = document.querySelector('img');
    document.addEventListener('mousemove' , function(e){
      // 获取鼠标相对于文档页面的 X 和 Y 坐标
      var x = e.pageX;
      var y = e.pageY;
      // 赋值,记得带上单位 'px'
      img.style.left = x + 'px';
      img.style.top = y + 'px';
      // 使鼠标指向图片的中心
      img.style.transform = 'translate( -50% , -50% )';
    });
    </script>
    

    【常用键盘事件】

  • onkeyup 某个键盘按键被 [ 松开 ] 时触发(常用)

  • onkeydown 某个键盘按键被 [ 按下 ] 时触发(可以识别功能键)
  • onkeypress 某个键盘按键被 [ 按下 ] 时触发(不能识别功能键,比如 alt、ctrl、shift、箭头等)

【注意】

  • 三个事件的执行顺序:keydown -> keypress -> keyup;
  • 通过 keyup 和 keydown 事件获取的键盘事件对象,其英文字母不区分大小写,默认为大写( ASCII );
  • 通过 keypress 事件获取的键盘事件对象,能够区分字母大小写( 1:49 A:65 a:97 );

【特别注意】

  • keydown 或 keypress 事件在文本框里被触发时,用户输入的文本内容实际上还未落入文本框中;
  • 在文本框里,只有 keyup 事件才能实现被触发时,文本框已经获取到文本内容。

【键盘事件对象的常见属性】

  • e.key 该属性返回按键的值(1就是1,2就是2;但兼容性非常差,实际开发不建议使用)
  • e.keyCode 该属性返回按键的 ASCII 码( 1:49 A:65 a:97 )