本文深入解析 JavaScript 自定义事件的实现原理,基于观察者模式(发布/订阅模式)逐步构建事件对象。通过从硬编码触发到动态管理回调数组,再到优化执行效率与模块化封装的完整演进过程,展示如何解耦事件绑定与触发,实现灵活的事件管理机制。
JavaScript 自定义事件原理
JavaScript 自定义事件的本质是观察者模式,也称为发布/订阅模式。这种机制的核心优势在于将事件的绑定与触发过程相互隔离,从而支持动态地添加或删除事件监听。
接下来,我们将通过具体实例,逐步构建一个完整的 JavaScript 自定义事件对象。
假设存在一个名为 action1 的函数,我们希望每次执行完 action1 后,自动触发另一个名为 service1 的函数。初始实现代码如下:
//服务service1
function service1(){
}
//函数action1
function action1(){
//other things
//then 启动service1
service1();
}
上述代码实现了基本功能,但随后需求发生变化:在 action1 执行完毕后,除了触发 service1,还需要依次触发 service2 和 service3。
按照直观思路,只需在 action1 函数的末尾依次调用 service2 和 service3 即可。
具体实现如下:
function service1(){}
function service2(){}
function service3(){}
function action1(){
//other things
service1();
service2();
service3();
}
然而,需求再次变更:在执行 action1 后,需要动态添加 service4,同时希望移除不再需要的 service2。此时该如何处理?
有人可能会建议直接修改 action1 代码,移除 service2 并添加 service4。
但在实际项目中,随着代码量激增,手动查找并修改相关代码变得困难且容易出错。
因此,我们需要一种更灵活的解决方案。
初步思路是定义一个数组(例如 servicearray)来统一管理所有需要触发的服务函数。
在 action1 执行结束时,遍历该数组并依次调用其中的函数即可实现目标。
此外,如果不再需要执行 service2,只需从数组中将其移除;若想添加新的 service,只需将其追加到数组末尾。
这种方案看起来非常可行,具体代码如下:
动态事件管理实现
var servicearray = [];
function service1(){}
function service2(){}
function service3(){}
//将所有service添加到servicearray中
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
//del:用于删除一个指定的service
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//action1后,执行所有的service
function action1(){
//other things
//遍历serviceaary,执行所有service函数。(servicearray在action1内)
for(var i =0; i < servicearray.length; i++){
servicearray[i]();
}
}
//添加service4
function service4(){}
servicearray.push(service4);
//删除service2
del(servicearray, service2);
虽然上述代码能正常运行,但其复用性较差,且 servicearray 与 action1 函数耦合紧密,不利于维护。我们需要进一步优化代码结构。
优化后的代码如下:
var servicearray = [];
function service1(){}
function service2(){}
function service3(){}
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//添加一个service4
function service4(){}
servicearray.push(service4);
//删除一个service2
del(servicearray, service2);
//添加一个触发函数hanldeAction,分离action与service
function hanldeAction(actionName,serviceArr){
if(typeof actionName === 'function'){
actionName();
for(var i =0; i < serviceArr.length; i++){
serviceArr[i]();
}
}
}
//执行
handleAction(action1,servicearray);
这段代码与回调函数的思想有异曲同工之妙,旨在 action 执行完毕后运行一系列 service。
但如果需求变为在 action 执行前或执行过程中触发一系列 service,则需要修改 handleAction 回调函数。在大型项目中频繁修改此类核心逻辑并不现实。
因此,我们需要设计一个更强大的机制,允许在任意指定位置触发事件。
具体实现如下:
事件机制优化与解耦
function service1(){}
function service2(){}
function service3(){}
var servicearray = [];
servicearray.push(service1);
servicearray.push(service2);
servicearray.push(service3);
function del(arr, fn){
for(var i = 0; i < arr.length; i++){
if( arr[i] == fn ){
arr.splice(i,1);
break;
}
}
}
//添加一个service4
function service4(){}
servicearray.push(service4);
//删除一个service2
del(servicearray, service2);
/*
actionObj用于存储所有action与service关联的对象。
如:{
action1:[service1,service2],
action2:[...]
}
*/
var actionObj = {};
/*
修改代码,增加一个actionName与serviceArr关联事件。
如,action1关联所有service,这样再结合下方的trigger事件就完美了
Params:
actionName --> actionObj的属性
serviceArr --> 包含所有与actionName相关的service数组
*/
function addAction(actionName, serviceArr){
if(typeof actionObj[actionName] === 'undefined' ){
actionObj[actionName] = [];
}
if(typeof serviceArr === 'object'){
actionObj[actionName].push(serviceArr);
}
}
/*
修改代码,增加一个触发actionName事件
如,当我想触发action1中的所有service时,调用trigger(action1)就OK啦
*/
function trigger( actionName ){
var act = actionObj[actionName];
if(act instanceof Array){
for(var i = 0, len = act.length; i < len; i++){
for(var j =0, arrlen = act[i].length; j++){
((act[i])[j])();
}
}
}
}
虽然上述代码可行,但存在性能问题。在 addAction 中,我们将 service 存储在一个数组中,导致 trigger 时需要两层循环遍历。优化方案是移除中间的 servicearray,直接将每个 service 推入 actionObj[actionName] 对应的数组中。这样可以将 trigger 的效率从两层循环降低为单层循环,同时增加一个 remove 方法用于删除特定的 service。
整理后的完整代码如下:
var actionObj = {};
//修改代码,增加一个actionName与service函数直接关联事件
function addAction(actionName, fn){
if(typeof actionObj[actionName] === 'undefined' ){
actionObj[actionName] = [];
}
if(typeof fn === 'function'){
actionObj[actionName].push(fn);
}
}
//修改代码,增加一个触发actionName事件
function trigger( actionName ){
var actionarray = actionObj[actionName];
if(actionarray instanceof Array){
for(var i = 0, len = actionarray.length; i < len; i++){
if(typeof actionarray[i] === 'function'){
actionarray[i]();
}
}
}
}
//修改代码,增加一个删除actionName中的service事件
function remove(actionName, fn){
var actionarray = actionObj[actionName];
if(typeof actionName === 'string' && actionarray instanceof Array){
if(typeof fn === 'function'){
//清除actionName中对应的fn方法
for(var i=0, len = actionarray.length; i < len; i++){
if(actionarray[i] === fn){
actionObj[actionName].splice(i,1);
}
}
}
}
}
经过优化,action 与 service 之间实现了完全解耦,代码结构更加清晰。
这体现了什么?
这就是我们共同构建的自定义事件机制,实现起来其实并不复杂。
在这个过程中,我们实际上应用了观察者模式这一设计模式。
为了进一步提升代码质量,我们需要考虑全局变量对命名空间的污染问题。在模块化开发日益普及的背景下,使用全局变量显然不合适,因此需要再次重构代码。
修改后的代码如下:
模块化封装与总结
var EventTarget = function(){
this.listener = {};
}
EventTarget.prototype = {
constructor:EventTarget,
addAction: function(actionName, fn){
if(typeof actionName === 'string' && typeof fn === 'function'){
//如果不存在actionName,就新建一个
if(typeof this.listener[actionName] === 'undefined'){
this.listener[actionName] = [fn];
}
//否则,直接往相应actinoName里面塞
else{
this.listener[actionName].push(fn);
}
}
},
trigger: function(actionName){
var actionArray = this.listener[actionName];
//触发一系列actionName里的函数
if(actionArray instanceof Array){
for(var i = 0, len = actionArray.length; i < len; i++){
if(typeof actionArray[i] === 'function'){
actionArray[i]();
}
}
}
actionArray = null;
},
remove: function(actionName, fn){
var actionArray = this.listener[actionName];
if(typeof actionName === 'string' && actionArray instanceof Array){
if(typeof fn === 'function'){
//清除actionName中对应的fn方法
for(var i=0, len = actionArray.length; i < len; i++){
if(actionArray[i] === fn){
this.listener[actionName].splice(i,1);
}
}
}
}
actionArray = null;
}
};至此,一个完整的 JavaScript 自定义事件对象便诞生了。
以上就是本文的全部内容,希望能为读者提供有益参考,欢迎继续支持本站。