@[toc] 关于 自定义JS工具类 相关类似的源码请看 https://gitee.com/ykang2020/my_utils (opens new window)

# 1. 封装DOM事件监听addEventListener

# 1.1 复习DOM事件流

基础知识可以参考这篇博文

【DOM】JavaScript-事件高级-注册事件-事件流-事件对象-事件冒泡-委派-鼠标键盘事件 (opens new window)

简单复习一下DOM事件流

  1. 捕获阶段
  2. 当前目标阶段
  3. 冒泡阶段

在这里插入图片描述 JS代码中只能执行捕获或者冒泡其中的一个阶段

addEventListener(type, listener[, useCapture]) 中的第三个参数 如果是 true,表示在事件捕获阶段调用事件处理程序; 如果是 false(默认值),表示在事件冒泡阶段调用事件处理程序。

let innerBox = document.getElementById('inner')
let outterBox = document.getElementById('outter')

outterBox.addEventListener('click', function () {
  console.log('捕获 outter')
},true)

innerBox.addEventListener('click', function () {
  console.log('捕获 inner')
},true)

outterBox.addEventListener('click', function () {
  console.log('冒泡 outter')
})

innerBox.addEventListener('click', function () {
  console.log('冒泡 inner')
})

点击蓝色盒子产生的效果 在这里插入图片描述

# 1.2 事件冒泡

事件在目标元素上处理后, 会由内向外(上)逐层传递

事件冒泡的应用场景: 事件代理/委托/委派

# 1.3 事件委托

没有将事件绑定在目标元素身上,而是 将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上

这样做的好处:

  1. 减少内存占用(事件监听回调从n变为1)
  2. 动态添加的内部元素也能响应

# 1.4 自定义DOM事件监听

/**
 * 自定义事件监听
 * @param {String} el 父元素选择器
 * @param {String} type 事件类型
 * @param {Function} fn 回调函数
 * @param {String} selector 子元素选择器
 */
export default function myAddEventListener(el, type, fn, selector) {
  // 判断el的类型,获取元素
  if (typeof el === "string") {
    el = document.querySelector(el);
  }
  // 事件判定
  // 若没有传第四个参数(子元素选择器),则给el元素绑定事件
  if (!selector) {
    el.addEventListener(type, fn);
  } else {
    el.addEventListener(type, function(e) {
      // 获取点击的目标事件源
      const target = e.target;
      // 判断选择器与目标元素是否相符
      if (target.matches(selector)) {
        // 相符就执行回调,否则就什么都不做
        fn.call(target,e);
      }
    });
  }
}
<ul>
  <li>AAA</li>
  <li>BBB</li>
  <li>CCC</li>
  <li>DDD</li>
  <div>EEE div</div>
</ul>
import myAddEventListener from "./eventBind/myAddEventListener";

myAddEventListener(
  "ul",
  "click",
  function () {
    console.log(this.innerHTML);
  },
  "li"
);

在这里插入图片描述

# 2. 自定义事件总线eventBus

  • eventBus: 包含所有功能的事件总线对象
  • eventBus.on(eventName, callback): 绑定事件监听
  • eventBus.emit(eventName, data): 分发事件
  • eventBus.off(eventName): 解绑指定事件名的事件监听, 如果没有指定解绑所有

# 定义

// 定义事件总线
const eventBus = {
  // 保存eventName类型与回调callback的容器
  callbackObj: {},
};

/**
 * 绑定事件监听,保存回调函数
 * @param {*} eventName 事件名称
 * @param {*} callback 回调函数
 */
eventBus.on = function (eventName, callback) {
  if (this.callbackObj[eventName]) {
    // 如果 callbackObj 属性中存在该类型事件,就压入
    this.callbackObj[eventName].push(callback);
  } else {
    // 如果 callbackObj 属性中不存在该类型事件,就添加成数组
    this.callbackObj[eventName] = [callback];
  }
};

/**
 * 分发事件,触发容器中的该属性eventName的回调函数
 * @param {*} eventName 事件名称
 * @param {*} data 数据
 */
eventBus.emit = function (eventName, data) {
  // 判断callbackObj中有没有回调函数
  if (this.callbackObj[eventName] && this.callbackObj[eventName].length > 0) {
    // 遍历数组
    this.callbackObj[eventName].forEach((callback) => {
      // 执行回调函数
      callback(data);
    });
  }
};

/**
 * 移除事件监听
 * @param {*} eventName 事件名称
 */
eventBus.off = function (eventName) {
  // 若传入了 eventName 事件类型
  if (eventName) {
    // 只是删除 eventName 对应的事件回调
    delete this.callbackObj[eventName];
  } else {
    // 否则全部删除
    this.callbacksObj = {};
  }
};

export default eventBus;

# 使用

import eventBus from "./eventBind/eventBus";

eventBus.on("login", (data) => {
  console.log(data + "用户已经登陆");
});
// 可以为一个事件绑定多个回调函数
eventBus.on("login", (data) => {
  console.log(data + "登陆数据已经写入");
});

// 触发事件
setTimeout(() => {
  eventBus.emit("login", "yk");
}, 2000);

// 绑定 + 触发
eventBus.on("delete", (data) => {
  console.log("delete", data);
});
eventBus.emit("delete", "hehe");

// 绑定+触发+删除+触发
eventBus.on("add", (data) => {
  console.log("add", data);
});
eventBus.emit("add", "YK");
eventBus.off("add");
console.log(eventBus);
eventBus.emit("add", "ykykyk");

在这里插入图片描述

# 3. 自定义消息订阅与发布PubSub

PubSub: 包含所有功能的订阅/发布消息的管理者 PubSub.subscribe(msg, subscriber): 订阅消息: 指定消息名和订阅者回调函数 PubSub.publish(msg, data): 异步发布消息: 指定消息名和数据 PubSub.publishSync(msg, data): 同步发布消息: 指定消息名和数据 PubSub.unsubscribe(flag): 取消订阅: 根据标识取消某个或某些消息的订阅

# 定义

const PubSub = {
  // 订阅编号
  id: 1,
  // 频道与回调保存的容器
  callbacks: {
    // 例子
    // pay: {
    //   token_1: fn,
    //   token_2: fn2,
    // },
  },
};

/**
 * 订阅频道
 * @param {*} channel 频道
 * @param {*} callback 回调
 */
PubSub.subscribe = function (channel, callback) {
  // 为每一个订阅创建唯一的编号
  let token = "token_" + this.id++;
  if (this.callbacks[channel]) {
    // 如果callbacks中有这个频道就直接压入
    this.callbacks[channel][token] = callback;
  } else {
    // 没有就创建一个
    this.callbacks[channel] = {
      [token]: callback,
    };
  }
  // 返回频道订阅的ID
  return token;
};

/**
 * 异步执行 发布消息
 * @param {*} channel 频道
 * @param {*} data 数据
 */
PubSub.publish = function (channel, data) {
  // 获取当前频道所有回调
  if (this.callbacks[channel]) {
    // 启动定时器异步执行任务
    setTimeout(() => {
      // Object.values() 返回对象值的数组
      Object.values(this.callbacks[channel]).forEach((callback) => {
        callback(data);
      });
    }, 0);
  }
};

/**
 * 同步执行 发布消息
 * @param {*} channel
 * @param {*} data
 */
PubSub.publishSync = function (channel, data) {
  // 获取当前频道所有回调
  if (this.callbacks[channel]) {
    // Object.values() 返回对象值的数组
    Object.values(this.callbacks[channel]).forEach((callback) => {
      callback(data);
    });
  }
};

/**
 * 取消订阅
 * 三种情况
 * 1. 没有传值 全删
 * 2. 传的是id 删id对应的频道
 * 3. 传的是频道名 删该频道
 * @param {*} flag
 */
PubSub.unsubscribe = function (flag) {
  if (flag === undefined) {
    // 清空所有订阅
    this.callbacks = {};
  } else if (typeof flag === "string") {
    // 判断是否是 token_ 开头的字符串
    if (flag.indexOf("token_") === 0) {
      // 是订阅id
      // 遍历callbacks 找到具体的token 对象值的数组用find方法找到有flag的对象
      let callbackObj = Object.values(this.callbacks).find((obj) =>
        obj.hasOwnProperty(flag)
      );
      // 判断是不是存在
      if (callbackObj) {
        // 有这个对象
        delete callbackObj[flag];
      }
    } else {
      // 是频道名称
      delete this.callbacks[flag];
    }
  } else {
    throw new Error("如果传入参数, 必须是字符串类型");
  }
};

export default PubSub;

# 使用

import PubSub from "./eventBind/myPubSub";

// 订阅消息
let pId1 = PubSub.subscribe("pay", (data) => {
  console.log("商家接到订单,准备开始制作", data);
});
let pId2 = PubSub.subscribe("pay", (data) => {
  console.log("骑手接到订单,准备开始取餐", data);
});

// 异步发布一个消息
PubSub.publish("pay", {
  title: "盖浇饭",
  price: 18,
  pos: "学校",
});

// 取消订阅
PubSub.unsubscribe(pId1);

console.log(PubSub);

在这里插入图片描述 关于 自定义JS工具类 相关类似的源码请看 https://gitee.com/ykang2020/my_utils (opens new window)

上次更新: 2022/4/22 15:59:56