JS DOM 常用节点操作 (下)

本文详细介绍了JSDOM中的节点操作,包括DOM事件的概述、事件注册(标签内、传统方式、监听方式)、删除事件、事件的捕获与冒泡阶段,以及如何禁止默认事件。此外,还探讨了DOM操作的代码书写位置,并列举了常见的JS事件列表。文章通过实例代码深入解析了事件流和事件委托的概念,有助于读者更好地理解和应用DOM事件。

关联:

JS DOM 常用节点操作 (上)

目录

DOM 事件

事件概述

注册事件

1. 标签内注册方式

2. 传统注册方式

3. 注册监听方式

删除事件

1. 标签内注册方式和传统注册方式的删除

2. 注册监听方式的删除

事件的捕获与冒泡

1. 事件流

2. 冒泡阶段

3. 捕获阶段

4. 加深理解 - 元素结构中捕获阶段和冒泡阶段混合存在

5. 事件流常见使用技巧 - 事件委托

禁止默认事件

1. 禁止捕获或冒泡

2. 禁止链接跳转事件

3. 禁止弹出右键菜单

DOM 操作的代码书写位置

JS 常用事件列表


DOM 事件

事件概述

1. 事件

可以感知用户操作和浏览器行为, 然后做出相应的回应,如下图就是一个用户点击事件

2. 事件的 3 要素:

① 事件源(触发事件的元素)

② 事件类型(单击事件、双击事件、加载事件等)

③ 事件处理程序(对事件做出的响应)

3. 事件对象

当事件发生时会创建一个事件对象(Event 对象),  它包含事件源、事件类型、鼠标位置、页面位置等信息。

直接在事件处理函数的参数列表中声明形参, 就可以获取事件对象

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div{
      width: 100px;
      height: 100px;
      border:1px solid black;
    }
  </style>
</head>
<body>
<div></div>
</body>
<script>
  let div = document.querySelector('div')
  div.onclick = function(e){
    console.log(e instanceof Event) // 结果: true
  }
</script>
</html>

事件对象常用属性:

属性名说明
target触发事件的元素对象
clientX鼠标在可视窗口的 x 轴坐标  不受滚动条滑动影响
clientY鼠标在可视窗口的 y 轴坐标  不受滚动条滑动影响
pageX鼠标在 html 文档页的 x 轴坐标  受滚动条滑动影响
pageY鼠标在 html 文档页的 y 轴坐标  受滚动条滑动影响
screenX鼠标在电脑屏幕的 x 轴坐标
screenY鼠标在电脑屏幕的 y 轴坐标

注册事件

注册事件有多种方式, 下面举例用不同的方式实现如下相同的效果

1. 标签内注册方式

写法简单, 但是同一元素的同一事件只能指定一个事件处理程序

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
  <script>
    function changeColor() {
      let obj = document.querySelector('div')
      obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
    }
  </script>
</head>
<body>
<div onclick="changeColor()">点击变色</div>
</body>
</html>

步骤:

1. 声明事件处理函数 changeColor()

2. 标签内指定事件类型( onclick )和事件处理函数( changeColor() )

2. 传统注册方式

和标签内注册的方式一样, 同一元素的同一事件也只能指定一个事件处理程序

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<div>点击变色</div>
<script>
  let obj = document.querySelector('div')
  obj.onclick = function () {
    obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
  }
</script>
</body>
</html>

步骤:

1. 获取标签元素对象

2. 为其指定事件和事件处理程序

3. 注册监听方式

可以对同一元素的同一事件添加多个处理程序, 但是有浏览器兼容问题, 低版本 IE 就不支持该方式

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<div>点击变色</div>
<script>
  let obj = document.querySelector('div')
  obj.addEventListener('click', function () {
    obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
  })
  obj.addEventListener('click', function () {
    alert('可以对同一元素的同一事件添加多个处理程序')
  })
</script>
</body>
</html>

步骤:

1. 获取标签元素对象

2. 为其指定事件和事件处理程序(指定事件时, 不需要写 on,比如 onclick、ondblclick 写成 click、dblclick 就可以)

兼容低版本 IE 的写法, 个人不建议兼容低版本IE, 太老的浏览器很多语法都不支持, 难道为了兼容都不用了么?

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<div>点击变色</div>
<script>
  var obj = document.getElementsByTagName('div')[0]
  var process = function () {
    obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
  }

  // 注册事件兼容写法
  if (obj.addEventListener) {
    obj.addEventListener('click', process)
  } else {
    obj.attachEvent('onclick', process)
  }
</script>
</body>
</html>

步骤:

1. 判断浏览器是否支持 addEvenetListener 

2. 支持:使用 addEvenetListener 注册事件,注意事件类型不用写 on

3. 不支持:使用 attachEvent 注册事件,注意事件类型必须写 on

删除事件

1. 标签内注册方式和传统注册方式的删除

设置指定事件类型的事件处理程序为 null 即可

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<div onclick="changeColor()">点击变色</div>
<script>
  
  let obj = document.querySelector('div')
  
  function changeColor() {
    obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
  }
  
  // 删除注册的事件
  obj.onclick = null;
</script>
</body>
</html>

2. 注册监听方式的删除

① 注册时, 设置的指定事件类型的事件处理程序, 不能是匿名函数

② 使用 removeEventListener( 注册时的事件类型, 注册时的函数对象地址 ) 方法移除注册的事件监听

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    div {
      width: 100px;
      height: 100px;
      text-align: center;
      line-height: 100px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<div>点击变色</div>
<script>
  let obj = document.querySelector('div')

  // 声明函数变量
  let process = function () {
    obj.style.backgroundColor = obj.style.backgroundColor === 'skyblue' ? 'greenyellow' : 'skyblue'
  }
  // 注册事件监听
  obj.addEventListener('click', process)

  // 移除事件监听
  obj.removeEventListener('click', process)
</script>
</body>
</html>

事件的捕获与冒泡

1. 事件流

事件发生时,JS引擎会找到目标区域内的元素及元素间的关系, 如下图:

事件流的知识点:

1. 事件是具备传播性的, 传播路径就是沿着这条 DOM 树, 先自上而下, 再自下而上的往返一次。

2. 自上而下的传播阶段称为捕获阶段,自下而上的传播阶段称为冒泡阶段。

3. 传播时, 每经过一个元素就会判断该元素是否设置了相同事件, 且是否可以在该阶段被触发。

4. 元素的事件只能在一个阶段内被触发, 要么捕获阶段触发, 要么冒泡阶段触发。

2. 冒泡阶段

1. 冒泡阶段是事件的默认执行阶段

2. 手动设置冒泡阶段执行:使用注册监听的方式添加事件 addEventListener, 第三个参数传 false

3. resize scroll focus blur mouseenter mouseleave load unload media 不支持冒泡阶段

效果:

代码结构:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<div>
  <p>点我事件冒泡</p>
</div>
<script>
  let html = document.documentElement
  let body = document.body
  let div = document.querySelector('div')
  let p = document.querySelector('p')

  html.onclick = () => {
    alert('html')
  }
  body.addEventListener('click', () => {
    alert('body')
  }, false)
  div.addEventListener('click', () => {
    alert('div')
  })
  p.onclick = () => {
    alert('p')
  }

</script>
</body>
</html>

理解代码中的事件流过程:

先执行捕获阶段

html (冒泡可触发) → body (冒泡可触发) → div (冒泡可触发) → p (冒泡可触发)

在捕获阶段没有可以被触发的事件, 然后在执行冒泡阶段

p (冒泡可触发) → div (冒泡可触发) → body (冒泡可触发) → html (冒泡可触发)

所有的元素都可以在冒泡阶段触发事件, 所以会按顺序执行他们的事件处理程序。

3. 捕获阶段

设置捕获阶段执行:使用注册监听的方式添加事件 addEventListener, 第三个参数传 true

效果:

代码结构:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<div>
  <p>点我事件冒泡</p>
</div>
<script>
  let html = document.documentElement
  let body = document.body
  let div = document.querySelector('div')
  let p = document.querySelector('p')

  html.addEventListener('click', () => {
    alert('html')
  }, true)
  body.addEventListener('click', () => {
    alert('body')
  }, true)
  div.addEventListener('click', () => {
    alert('div')
  }, true)
  p.addEventListener('click', () => {
    alert('p')
  }, true)
</script>
</body>
</html>

理解代码中的事件流过程:

先执行捕获阶段

html (捕获可触发) → body (捕获可触发) → div (捕获可触发) → p (捕获可触发)

所有的元素都可以在捕获阶段触发事件, 所以会按顺序执行他们的事件处理程序

然后在执行冒泡阶段

p (捕获可触发) → div (捕获可触发) → body (捕获可触发) → html (捕获可触发)

在冒泡阶段没有可以被触发的事件。

4. 加深理解 - 元素结构中捕获阶段和冒泡阶段混合存在

例子中, 目标区域内的元素, 既有捕获阶段执行的又有冒泡阶段执行的, 帮助加深理解。

效果:

代码结构:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<div>
  <p>点我事件冒泡</p>
</div>
<script>
  let html = document.documentElement
  let body = document.body
  let div = document.querySelector('div')
  let p = document.querySelector('p')

  html.addEventListener('click', () => {
    alert('html')
  }, true)
  body.addEventListener('click', () => {
    alert('body')
  }, false)
  div.addEventListener('click', () => {
    alert('div')
  }, true)
  p.addEventListener('click', () => {
    alert('p')
  }, false)
</script>
</body>
</html>

理解代码中的事件流过程:

先执行捕获阶段

html (捕获可触发) → body (冒泡可触发) → div (捕获可触发) → p (冒泡可触发)

该阶段中事件会执行的元素有 html 和 div , 然后在执行冒泡阶段

p (冒泡可触发) → div (捕获可触发) → body (冒泡可触发) → html (捕获可触发)

会执行事件的元素有 p 和 body,所以最后的执行顺序为 html → div → p → body

5. 事件流常见使用技巧 - 事件委托

事件委托就是多个同根元素间, 事件和事件处理程序都相同时, 可以利用冒泡的特性, 只对他们的父节点元素设置事件即可, 而不需要每个子元素都单独设置。

一般常用的场合就是列表,  如下图效果:

实现上图效果有三种常见的方式:

① 最笨的方式 - 硬编码。 为每个元素依次添加事件,但是不够灵活, 扩展麻烦

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
  <style>
    table {
      border-collapse: collapse;
    }

    td, th {
      width: 100px;
      text-align: center;
      border: 1px solid black;
    }
  </style>
</head>
<body>
<table id="score_table">
  <thead>
  <tr>
    <th>序号</th>
    <th>名字</th>
    <th>成绩</th>
    <th>操作</th>
  </tr>
  </thead>
  <tbody>
  <tr>
    <td>1</td>
    <td>赵</td>
    <td>97</td>
    <td><input type="button" value="删除"></td>
  </tr>
  <tr>
    <td>2</td>
    <td>钱</td>
    <td>95</td>
    <td><input type="button" value="删除"></td>
  </tr>
  <tr>
    <td>3</td>
    <td>孙</td>
    <td>99</td>
    <td><input type="button" value="删除"></td>
  </tr>
  <tr>
    <td>4</td>
    <td>李</td>
    <td>98</td>
    <td><input type="button" value="删除"></td>
  </tr>
  </tbody>
</table>
</body>
<script>
  // 获取元素对象
  let scoreTBody = document.querySelector('#score_table tbody')
  let btnFirst = document.querySelector('#score_table  tr:first-of-type input[type=button]')
  let btnSecond = document.querySelector('#score_table  tr:nth-of-type(2) input[type=button]')
  let btnThird = document.querySelector('#score_table  tr:nth-of-type(3) input[type=button]')
  let btnLast = document.querySelector('#score_table  tr:last-of-type input[type=button]')

  // 声明点击事件处理程序
  let process = (e) => {
    scoreTBody.removeChild(e.target.parentElement.parentElement)
  }

  // 依次设置点击事件
  btnFirst.addEventListener('click', process)
  btnSecond.addEventListener('click', process)
  btnThird.addEventListener('click', process)
  btnLast.addEventListener('click', process)
</script>
</html>

② 常见方式 - 遍历。获取元素集合,通过遍历添加事件, 但是当列表内容过多时, 这种方式会影响页面性能。

将上面 script 部分替换为下面代码

<script>
  // 获取元素集合
  let scoreTBody = document.querySelector('#score_table tbody')
  let btnArray = document.querySelectorAll('#score_table tr td input[type=button]')

  // 遍历添加事件
  for (let i = 0; i < btnArray.length; i++) {
    btnArray[i].addEventListener('click', (e) => {
      scoreTBody.removeChild(e.target.parentElement.parentElement)
    })
  }
</script>

③ 最好的方式 - 事件委托。利用冒泡原理,只给外层元素设置事件即可。

替换 script 代码为

<script>
  // 获取外层元素对象并设置事件
  let scoreTBody = document.querySelector('#score_table tbody')
  scoreTBody.addEventListener('click', (e) => {
    scoreTBody.removeChild(e.target.parentElement.parentElement)
  })
</script>

禁止默认事件

1. 禁止捕获或冒泡

禁止捕获: e.stopPropagation() 写在上游元素中, 且该元素的事件允许捕获阶段触发

禁止冒泡: e.stopPropagation() 写在下游元素中, 且该元素的事件允许冒泡阶段触发

效果:

2. 禁止链接跳转事件

① 单纯禁止跳转行为

效果:

代码:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<a href="javascript:void(0)">阻止链接跳转 javascript:void(0) 方式</a><br>
<a href="javascript:">阻止链接跳转 javascript: 方式</a>
</body>
</html>

② 禁止跳转行为, 并附加自定义功能

效果:

代码:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<a href="http://www.baidu.com">点我不会跳转, 并且有自己的功能</a>
</body>
<script>
  let a = document.querySelector('a')
  a.addEventListener('click', (e) => {
    alert('弹出框')
    e.preventDefault()
  })
</script>
</html>

3. 禁止弹出右键菜单

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body></body>
<script>
  document.addEventListener('contextmenu', function (e) {
    e.preventDefault();
  })
</script>
</html>

DOM 操作的代码书写位置

因为代码是按顺序执行, 当还没渲染完 DOM 元素, 就用 JS 操作该 DOM 时, 会获取 DOM 对象失败,所以 JS 操作 DOM 的代码的书写位置很重要。

JS 写在欲操作的元素代码之前,会获取元素失败,  因为还未渲染就要获取

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<script>
  console.log(document.querySelector('#container')) // 结果:null
</script>
<div id="container"></div>
</body>
</html>

JS 代码写在欲操作元素的后面, 可以成功获取元素, 因为元素已经渲染完成

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<div id="container"></div>
<script>
  console.log(document.querySelector('#container')) // 结果:获得元素对象成功
</script>
</body>
</html>

JS 代码写在页面加载完成事件中, 这样不管 JS 的位置写在哪里都不影响获取 DOM 对象, 因为是加载完成后才执行

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>DOM</title>
</head>
<body>
<script>
  document.addEventListener('load', () => {
    console.log(document.querySelector('#container')) // 结果:获得元素对象成功
  })
</script>
<div id="container"></div>
</body>
</html>

JS 常用事件列表

事件名描述事件类型
onclick鼠标单击鼠标事件
ondblclick鼠标双击鼠标事件
onmousedown鼠标压下鼠标事件
onmouseup鼠标弹起鼠标事件
onmousemove鼠标移动鼠标事件
oncontextmenu鼠标右键鼠标事件
onwheel鼠标滚轮滑动鼠标事件
onmouseover鼠标移入鼠标事件
onmouseout鼠标移开鼠标事件
onmouseenter鼠标移入, 不支持冒泡鼠标事件
onmouseleave鼠标移开, 不支持冒泡鼠标事件
onkeydown按键压下键盘事件
onkeyup按键弹起键盘事件
onkeypress按键压下, 不支持功能键键盘事件
onfocus获得焦点表单事件
onblur失去焦点表单事件
onchange内容改变表单事件
onsubmit点击 submit 按钮表单事件
onreset点击 reset 按钮表单事件
onselect选中文字表单事件
onload页面元素加载完成浏览器事件
onresize改变浏览器窗口大小浏览器事件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值