/** * 合成事件对象类 */ 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()); }); };