tiantingrui / daily-harvest

记录每日收获
MIT License
2 stars 0 forks source link

DOM 编程艺术 #17

Open tiantingrui opened 2 years ago

tiantingrui commented 2 years ago
  1. DOM 基础
  2. DOM 事件体系
  3. 事件的防抖与节流
tiantingrui commented 2 years ago

DOM 基础

什么是 DOM

DOM(Document Object Model,文档对象模型)是 JavaScript 操作 HTML 的接口(这里只讨论属于前端范畴的 HTML DOM),属于前端的入门知识,同样也是核心内容,因为大部分前端功能都需要借助 DOM 来实现,比如:

实现动态展开树组件,表单组件级联等这类复杂的操作。

如果你查看过 DOM V3 标准,会发现包含多个内容,但归纳起来常用的主要由 3 个部分组成:

选择区域的使用场景有限,一般用于富文本编辑类业务,我们不做深入讨论;DOM 事件有一定的关联性,将在下一课时中详细讨论;对于 DOM 节点,需与另外两个概念标签和元素进行区分:

举例说明,在下面的代码中,“p” 是标签, 生成 DOM 树的时候会产生两个节点,一个是元素节点 p,另一个是字符串为“Terry”的文本节点。

<p>Terry</p>

DOM 常用操作

查:DOM 节点获取

- getElementById // 按照 id 查询
- getElementsByTagName // 按照标签名查询
- getElementsByClassName // 按照类名查询
- querySelectorAll // 按照 css 选择器查询

增:DOM 节点的创建

请你创建一个新节点,并把它添加到指定节点的后面。

<html>
  <head>
    <title>DEMO</title>
  </head>
  <body>
    <div id="container"> 
      <h1 id="title">我是标题</h1>
    </div>   
  </body>
  <script>
   // 要求你添加一个有内容的 span 节点到 id 为 title 的节点后面,那么我们的做法就是:
// 首先获取父节点
var container = document.getElementById('container')

// 创建新节点
var targetSpan = document.createElement('span')
// 设置 span 节点的内容
targetSpan.innerHTML = 'hello world'

// 把新创建的元素塞进父节点里去
container.appendChild(targetSpan) 
  </script>
</html>

删: DOM节点的删除

删除指定的 DOM 节点

// 获取目标元素的父元素
var container = document.getElementById('container')
// 获取目标元素
var targetNode = document.getElementById('title')
// 删除目标元素
container.removeChild(targetNode)   

改:修改DOM元素

修改 DOM 元素这个动作可以分很多维度,比如说移动 DOM 元素的位置,修改 DOM 元素的属性等。 将指定的两个 DOM 元素交换位置

<html>
  <head>
    <title>DEMO</title>
  </head>
  <body>
    <div id="container"> 
      <h1 id="title">我是标题</h1>
      <p id="content">我是内容</p>
    </div>   
  </body>
</html>

现在需要你调换 title 和 content 的位置,我们可以考虑 insertBefore 或者 appendChild。这里我们给出 insertBefore 的操作示范:

// 获取父元素
var container = document.getElementById('container')   

// 获取两个需要被交换的元素
var title = document.getElementById('title')
var content = document.getElementById('content')

// 交换两个元素,把 content 置于 title 前面
container.insertBefore(content, title)

DOM 元素属性的获取和修改 仍然是上面这个 HTML 结构,现在假如需要你获取并修改 title 元素的 id 名,我们可以让 getAttribute 和 setAttribute 来帮忙:

var title = document.getElementById('title')

// 获取 id 属性
var titleId = title.getAttribute('id')

// 修改 id 属性
title.setAttribute('id', 'anothorTitle')
tiantingrui commented 2 years ago

DOM 事件

自定义事件

<!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>
    #divA,
    #divB,
    #divC {
      width: 100px;
      height:100px;
    }

    #divA {
      background-color: #333;   
    }

    #divB {
      background-color: #dd5990; 
    }

    #divC {
      background-color: #ccc990; 
    }
  </style>
</head>
<body>
  <div id="divA">我是A</div>
  <div id="divB">我是B</div>
  <div id="divC">我是C</div>
</body>
</html>

如果我们仅仅想监听 divA 这个元素上的点击行为,我们可以用 addEventListener 来安装监听函数:

<script>
  var divA = document.getElementById('divA')
  document.addEventListener('click',function(){
    console.log('我是小A')

  })  
</script>

这是大家非常熟悉的操作。但是,如果我现在想实现这样一种效果:

在点击A之后,B 和 C 都能感知到 A 被点击了,并且做出相应的行为——就像这个点击事件是点在 B 和 C 上一样。

是不是觉得有点意思了?我们知道,借助时间捕获和冒泡的特性,我们是可以实现父子元素之间的行为联动的。但是此处,A、B、C三者位于同一层级,他们怎么相互感知对方身上发生了什么事情呢?

看看自定义事件怎么解决 首先大家需要了解的是,自定义事件的创建。比如说咱们要创建一个本来不存在的"clickA"事件,来表示 A 被点击了,咱们可以这么写:

var clickAEvent = new Event('clickA');

OK,现在事件有了,我们来完成事件的监听和派发:

// 获取 divB 元素 
var divB = document.getElementById('divB')
// divB 监听 clickA 事件
divB.addEventListener('clickA',function(e){
  console.log('我是小B,我感觉到了小A')
  console.log(e.target)
}) 

// 获取 divC 元素
var divC = document.getElementById('divC')
// divC 监听 clickA 事件
divC.addEventListener('clickA',function(e){
  console.log('我是小C,我感觉到了小A')
  console.log(e.target)
}) 

// A 元素的监听函数也得改造下
divA.addEventListener('click',function(){
  console.log('我是小A')
  // 注意这里 dispatch 这个动作,就是我们自己派发事件了
  divB.dispatchEvent(clickAEvent)
  divC.dispatchEvent(clickAEvent)
})  

可以看到被正确响应了。 大家可能也注意到了,这里我们安装和派发事件必须得拿到 divC、divA 这样确切的元素才行。有的时候,为了进一步解耦,我们也会考虑把所有的监听函数都塞到 document 这个元素上去,由它来根据不同类型的自定义事件采取不同的动作。这种思路,就和事件代理非常相似了。

事件代理

事件代理,又叫事件委托。 在如下的 HTML 里,我希望做到点击每一个 li 元素,都能输出它内在的文本内容。你会怎么做?

<!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>
</head>
<body>
  <ul id="poem">
    <li>鹅鹅鹅</li>
    <li>曲项向天歌</li>
    <li>白毛浮绿水</li>
    <li>红掌拨清波</li>
    <li>锄禾日当午</li>
    <li>汗滴禾下土</li>
    <li>谁知盘中餐</li>
    <li>粒粒皆辛苦</li>
    <li>背不动了</li>
    <li>我背不动了</li>
  </ul>
</body>
</html>

按照事件的代理的思路我们可以把事件绑定到父元素 ul 上

var ul = document.getElementById('poem')
ul.addEventListener('click', function(e){
  console.log(e.target.innerHTML)
}) 

e.target 就是指触发事件的具体目标,它记录着事件的源头。所以说,不管咱们的监听函数在哪一层执行,只要我拿到这个 e.target,就相当于拿到了真正触发事件的那个元素。拿到这个元素后,我们完全可以模拟出它的行为,实现无差别的监听效果。 像这样利用事件的冒泡特性,把多个子元素的同一类型的监听逻辑,合并到父元素上通过一个监听函数来管理的行为,就是事件代理。通过事件代理,我们可以减少内存开销、简化注册步骤,大大提高开发效率。

tiantingrui commented 2 years ago

防抖与节流