ps: 本文首发于公众号 话说前端
在目前的我的项目开发中,因为应用的 angular 1.x
来进行利用层面的开发,那么在处理事件的时候就不得不应用框架本身所带的 ng-click
指令来进行事件的绑定,应用 ng-click
进行绑定的话,对于长列表,交互事件多的中央来说,不是一个好的抉择。因为会绑定很多的事件。于是就寻求以一种适合的事件代理的形式来尝试代替(这里只探讨 click
)。所以来做个总结
要实现这件事,无妨先回顾一下事件流的一些常识
如上图所示,当你点击页面上的元素的时候,事件会顺次达到这三个阶段,它们别离是:
事件捕捉阶段 -> 指标阶段 -> 冒泡阶段。
单拿出冒泡阶段来说,意味着当你点击了某一个具体的元素后,那么最初你是能够在 document 这个层级接管到事件的,这就形成了代理的根底。
接下来就要思考的问题是:
- 我在根节点上的监听办法如何响应组件外面定义的回调函数
- 咱们的写法如何与
ng-click
保持一致。 - 实现事件合成的冒泡与阻止冒泡
先来看失常的 ng-click
指令是如何应用的,如下:
<example
ng-click="test($event,item)"
></example>
所以只须要实现一个指令,接管一个函数即可(这里只探讨属性为函数的场景)。
回调函数的查找
const clickDirective = function () {
return {
restrict: 'A',
link(scope, element, attr) {
try {if (!attr.mmClick) return;
const {fn, functionName, paramsArr} = service.parseParams(attr.mmClick, scope);
// eslint-disable-next-line
element[0][`rcs-${functionName}`] = {fn, paramsArr};
} catch (e) {console.error(e);
}
},
};
};
如上,咱们能够在指令的初始化中,将咱们获取到的回调函数以及参数以特定名称的形式挂载到 dom
树上
这里以 rcs
字符结尾,这样就能在根节点接管的事件中去寻找到这个回调函数并解决
function executeEvent(target, e) {const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {if (keys[i].indexOf('rcs') === 0) {const { fn, paramsArr} = target[keys[i]];
fn.call(this, ...paramsArr, { e});
}
// 解决是否须要冒泡
if (e.hasOwnProperty('stopBubble') && e.stopBubble) {return;}
}
if (target.parentNode !== null) {return executeEvent(target.parentNode, e);
}
}
能够看到,下面的函数是一种递归的形式,逐级寻找回调函数,并执行它。这样就实现了函数的定义与执行局部。
合成事件的冒泡与阻止冒泡
与此同时,在一个 dom 层级上,能够在不同的层级绑定事件,这样在冒泡阶段,会顺次执行。那么在如上的函数中也体现了这点。
如上的函数,会把事件对象通过参数的模式给到调用方(别问为啥在最初,因为要兼容已有函数定义),
fn.call(this, ...paramsArr, { e});
在回调函数里,只须要设置这个值即可:
$scope.showGroupCard = function (item, chatInfoType, { e}) {e.stopBubble = true;}
当这个值为 true
的时候,那么 executeEvent
就会终止执行,否则会直到 parentNode
为null
。
最初再来看看应用成果:
<example
mm-click="test(item)"
></example>
其实到这里,这个事件合成简易版就算实现了。尽管是利用的 dom 本身的层次性,但查找速度齐全不必放心。
另外就是对于回调函数外面的 this 的问题,回调函数须要应用箭头函数来穿透才行,问题不大
函数参数解析
其实这里最难的局部在于指令的函数解析上,一个函数它的参数能够有如下几种(可能我还没列举完):
- 简略的根本数据类型
- 变量(这个变量可能来自本人组件的作用域,来自父组件传递下来的作用域,也可能来自根作用域)
- 参数是一个函数,或者是一个递归函数
- 参数是一个对象,对象外面有变量
- 等等
所以基于此,再结合实际的业务场景,约定了如下:
- 指令的属性只能是一个函数,不承受其余值
- 只保留了以后作用域变量,以后变量的调用,舍弃了表达式等写法,
- 函数参数只是以后组件作用域及作用域链上的,如果须要应用表达式或者其余写法,就把它挂载到以后作用域上。
end
万物起于微忽,质变引起量变