【大白话】说JS的实际应用之DOM(减少Dom操作,阻止冒泡,事件委托)

74 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

前言

大白话说JS内容包括:DOM的一些操作,Promise相关, 微任务宏任务,作用域,变量提升,闭包,变量类型,深浅拷贝,原型和作用域链,后续争取把js重点都记录上,深入浅出。

DOM操作

Dom翻译过来就是文档对象模型,它是页面的一个层次化节点数(就是各种嵌套div,标签的树),咱们可以对这树进行常见的增删改查操作,来完善美化页面。

常见操作结点的方式:(非主要讲解内容,大致说下,可以按数组的API来理解)

说些Node类型的方法吧:

  1. appendChild(),跟数组的push方式一样,在在结点末尾添加一个结点
  2. insertBefore(),非末尾插入,在指定地方插入
  3. replaceChild(),可插入可替换,像数组的splice
  4. removeChild()和cloneNode(),见名知意,就不说了

当然,还有Document类型的,这个大伙就很熟悉了,像getElementById, getElementsByName, getElementsByTagName 大家都很熟悉就不说了(Element也不说了🤣)。说点它的实际应用操作吧

它的实际应用操作有:

  1. 使用文档碎片减少DOM操作
  2. 冒泡事件:stopPropagation()阻止向上冒泡
  3. 事件委托: 减少DOM请求次数

使用文档碎片减少DOM操作

假设在一个ul中,如果持续添加一百次插入 li 的DOM操作,那这个过程无疑是非常消耗内存的,这时不妨使用文档碎片 fragment ,将插入的100次操作存储在这,结束后在一并添加到ul中,这样DOM操作从原本的100次变为一次。

 <ul id="list">
  </ul>
  <script src="index.js"></script>
const list = document.getElementById('list');
// 创建文档碎片,内容放在内存里,最后将结果一次插入list,减少DOM操作
const fragment = document.createDocumentFragment();

for (let i = 0; i < 5; i++) {
  const item = document.createElement('li');
  item.innerHTML = `事件${i}`;
  // 每次插入都是一次DOM操作,消耗性能,所以先往文档碎片放入li,循环完后在插入list
  // list.appendChild(item);
  fragment.appendChild(item);
}

list.appendChild(fragment);

阻止冒泡事件

这一切的一切,还得从源头说起:

事件流描述的是从页面中接收事件的顺序,一共三个阶段:捕获阶段,目标阶段,冒泡阶段。一般事件在浏览器中处于冒泡阶段才被执行,如果想在捕获阶段就触发,可用addEventListener 方法,这个方法接收3个参数:要处理的事件名、处理函数和布尔值(true就表示在捕获阶段就触发)。

依旧举个栗子:当点击一个 li , 他没有绑定事件处理函数,那么他会往父级元素(一直往上找直到body)看有没有事件处理函数,有的话同样触发。

这样可以简便的为子元素绑定处理事件,但弊端也很明显:如果父元素有处理函数,即使你给子元素绑定了处理事件,那么在触发子元素的处理事件同时,还会向上冒泡在触发父元素的处理函数!!这就需要到 stopPropagation(); 阻止冒泡行为。

<div id="one">
    <p id="p1">冒泡</p>
  </div>
  <hr>
  <div id="two">
    <p id="p4">冒泡</p>
    <p id="p5">阻止</p>
  </div>
const p3 = document.getElementById('p3');
const one = document.getElementById('one');
const body = document.body;

function bindEvent (elem, type, fn) {
  elem.addEventListener(type, fn);
}

bindEvent(one, 'click', function () {
  console.log("one的click");
})
bindEvent(body, 'click', function () {
  console.log("body的click");
})
bindEvent(p3, 'click', function () {
  console.log("p5的click");
})**

在没有绑定阻止冒泡时,给了body,第一个父元素和第三个子元素绑定处理事件,点击第三个会触发第三个和body的,点击第一个会触发父元素和body的。如果只想触发第三个事件绑定,可将p3的触发函数修改为:传入event,利用event下的 stopPropagation(); 方法。

bindEvent(p3, 'click', function (event) {
  event.stopPropagation();
  console.log("阻止冒泡");
})

事件委托

事件指的是:不在要发生的事件(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,监听子元素,来做出不同的响应。

例如在ul中有这么五个小 li ,要给他们绑定点击事件,可获取他们标签名后,利用slice方法对li的类数组转化为数组,在对其中的 li 添加注册事件。

<ul id="list">
    <li>事件1</li>
    <li>事件2</li>
    <li>事件3</li>
    <li>事件4</li>
    <li>事件5</li>
    <button id="btn">点击添加委托事件</button>
  </ul>
const lis = document.getElementsByTagName('li');
listAray = Array.prototype.slice.call(lis);
listAray.forEach(li => {
  addEvent(li, 'click', () => {
    alert(li.innerHTML);
  })
});

要是有500个li呢,那岂不是遍历绑定500次,这产生的事件监听器非常消耗内存。这时可以找到其父元素,设置事件监听器,利用 target 给 li 绑定事件监听。

// 绑定事件处理函数
function addEvent (elem, type, fn) {
  elem.addEventListener(type, fn);
}

addEvent(list, 'click', (e) => {
  const target = e.target;
  if (target.nodeName === 'LI') {
    alert(target.innerHTML);
  }
})

btn.addEventListener('click', () => {
  const li = document.createElement('li');
  li.innerHTML = '新增绑定事件';
  list.insertBefore(li, btn);
})

这样,即使不循环遍历 ul 下的 li ,也能通过对 ul 的绑定打印出子元素 li 的信息。

以上,就是常见的一些DOM实际应用操作,写的不好,多多包涵