2023-03-29 10:30:43 +08:00

115 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 合成事件对象类
*/
class SyntheticEvent {
constructor(e) {
// 保存原生的事件对象
this.nativeEvent = e;
}
// 合成事件对象需要提供一个和原生 DOM 同名的阻止冒泡的方法
stopPropagation() {
// 当开发者调用 stopPropagation 方法,将该合成事件对象的 _stopPropagation 设置为 true
this._stopPropagation = true;
if (this.nativeEvent.stopPropagation) {
// 调用原生事件对象的 stopPropagation 方法来阻止冒泡
this.nativeEvent.stopPropagation();
}
}
}
/**
*
* @param {*} paths 收集到的事件回调函数的数组
* @param {*} type 事件类型
* @param {*} se 合成事件对象
*/
const triggerEventFlow = (paths, type, se) => {
// 挨着挨着遍历这个数组,执行回调函数即可
// 模拟捕获阶段的实现,所以需要从后往前遍历数组并执行回调
for (let i = paths.length; i--; ) {
const pathNode = paths[i];
const callback = pathNode[type];
if (callback) {
// 存在回调函数,执行该回调
callback.call(null, se);
}
if (se._stopPropagation) {
// 说明在当前的事件回调函数中,开发者阻止继续往上冒泡
break;
}
}
};
/**
* 该方法用于收集路径中所有 type 类型的事件回调函数
* @param {*} type 事件类型
* @param {*} begin FiberNode
* @returns
* [{
* CLICK : function(){...}
* },{
* CLICK : function(){...}
* }]
*/
const collectPaths = (type, begin) => {
const paths = []; // 存放收集到所有的事件回调函数
// 如果不是 HostRootFiber就一直往上遍历
while (begin.tag !== 3) {
const { memoizedProps, tag } = begin;
// 如果 tag 对应的值为 5说明是 DOM 元素对应的 FiberNode
if (tag === 5) {
const eventName = "bind" + type; // bindCLICK
// 接下来我们来看当前的节点是否有绑定事件
if (memoizedProps && Object.keys(memoizedProps).includes(eventName)) {
// 如果进入该 if说明当前这个节点绑定了对应类型的事件
// 需要进行收集,收集到 paths 数组里面
const pathNode = {};
pathNode[type] = memoizedProps[eventName];
paths.push(pathNode);
}
begin = begin.return;
}
}
return paths;
};
/**
*
* @param {*} e 原生的事件对象
* @param {*} type 事件类型,已经全部转为了大写,比如这里传递过来的是 CLICK
*/
const dispatchEvent = (e, type) => {
// 实例化一个合成事件对象
const se = new SyntheticEvent(e);
// 拿到触发事件的元素
const ele = e.target;
let fiber;
// 通过 DOM 元素找到对应的 FiberNode
for (let prop in ele) {
if (prop.toLocaleLowerCase().includes("fiber")) {
fiber = ele[prop];
}
}
// 找到对应的 fiberNode 之后,接下来我们需要收集路径中该事件类型所对应的所有的回调函数
const paths = collectPaths(type, fiber);
// 模拟捕获的实现
triggerEventFlow(paths, type + "CAPTURE", se);
// 模拟冒泡的实现
// 首先需要判断是否阻止了冒泡,如果没有,那么我们只需要将 paths 进行反向再遍历执行一次即可
if(!se._stopPropagation){
triggerEventFlow(paths.reverse(), type, se);
}
};
/**
* 该方法用于给根元素绑定事件
* @param {*} container 根元素
* @param {*} type 事件类型
*/
export const addEvent = (container, type) => {
container.addEventListener(type, (e) => {
// 进行事件的派发
dispatchEvent(e, type.toUpperCase());
});
};