@[toc] 关于 自定义JS工具类 相关类似的源码请看 https://gitee.com/ykang2020/my_utils (opens new window)
# 1. 封装DOM事件监听addEventListener
# 1.1 复习DOM事件流
基础知识可以参考这篇博文
【DOM】JavaScript-事件高级-注册事件-事件流-事件对象-事件冒泡-委派-鼠标键盘事件 (opens new window)
简单复习一下DOM事件流
- 捕获阶段
- 当前目标阶段
- 冒泡阶段
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 事件委托
没有将事件绑定在目标元素身上,而是 将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上
这样做的好处:
- 减少内存占用(事件监听回调从n变为1)
- 动态添加的内部元素也能响应
# 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)