原生JS技巧(1):实现js对函数的事件监听
在HTML的Dom元素中,每个element都可以绑定监听 domNode.addEventListener ,但如果想要对自定义的function添加一个监听却不能用addEventListener,因为这个函数本身就没有绑定过addEventListener。
1、为什么要对函数进行监听?
设想一个场景:当用户登录网站后,页面会发一个ajax请求,当请求成功并返回这个用户所对有模块的操作权限的map数据或者用户的权限级别(比如说一些论坛,有些板块你能看却不能发表),这时,我们执行一个函数,并把取到的数据传给这个函数。如果其他的模块在这之前监听这个函数,当这个函数执行时,触发事件并接收函数传进来的用户数据,进而判断用户有没有访问这个模块的权限,如果没有权限就隐藏或删除这个模块内容。
这样一来,我们只要能监听这个函数就可以了(但除了监听函数这种方式,我们也可以用对事件的发布订阅方式解决这个问题)
2、接下来就是如何实现这一功能
起初,细想这个问题时并不好实现,很多人的思路是如何判断函数被执行,可当你想破脑袋估计都没有一个很好的解决方案,当然这个函数名和参数都是随机的,我们不能把任何一个定死。(也可能是我技术知识有限想不到)
换个思路,函数肯定会被调用,只要调用前我们重写这个函数,重写的这个函数内部让它执行两个函数,一个是重写前的这个函数,另一个是事件触发时我们要做的处理。
而监听的时候刚好在执行这个函数之前,只要通过监听函数来重写这个函数就可以了。
1 /** 2 * 用js创建一个类 3 * 方式: 4 * var func = (function(){ 5 * //构造方法 6 * function func(a,b){ 7 * this.a = a; 8 * this.b = b; 9 * } 10 * //成员方法 11 * func.prototype.get(type){ 12 * return this[type]; 13 * } 14 * func.prototype.set(type,value){ 15 * this[type] = value; 16 * } 17 * }()); 18 */ 19 var funcConnect = (function(){ 20 function funcConnect(){ 21 //这是一个构造方法 22 } 23 24 /** 25 * obj:函数所在的对象 26 * type:函数名(事件类型) 27 * callback:回调函数(函数执行时要触发的这个函数) 28 */ 29 funcConnect.prototype._connect(obj,type,callback){ 30 var _func = obj[type]; // --第3步-- 我们要备份的函数 31 /* 32 --第1步-- 33 有了这三个参数我们就能获取到要重写的函数,即 obj[type] 34 接下来是重写这个函数: 35 */ 36 obj[type] = function(){ 37 /* 38 --第2步-- 39 当这个函数被调用时,不管原始定义的参数是什么,我都可以通过下面的方式获取参数 40 */ 41 var _args = arguments; //获取的参数是个数组 42 /* 43 --第3步-- 44 在重写的这个函数里我还要执行以下重写前的这个函数 45 所以在重写前把这个函数备份一下:var _func = obj[type]; 46 */ 47 } 48 } 49 }());
接下来是第四步,这里需要解释一下call()和apply():
每个函数原型都有call和apply方法,它们用来触发这个函数执行,并把this传给这个函数
举个例子:
直接函数 _get()是,get里的this指的是_f,如果通过call来调用,便可以把this指定为_e了。
apply和call功能一样,不同的是传参的方式不同:
call(obj,_arg1,_arg2,...) 有几个参数必须要写几个
apply(obj, _args) 把_args参数数组传入,这样就能实现通过arguments接收的参数直接传递,不用知道arguments里到底有几个参数
1 obj[type] = function(){ 2 /* 3 --第2步-- 4 当这个函数被调用时,不管原始定义的参数是什么,我都可以通过下面的方式获取参数 5 */ 6 var _args = arguments; //获取的参数是个数组 7 /* 8 --第3步-- 9 在重写的这个函数里我还要执行以下重写前的这个函数 10 所以在重写前把这个函数备份一下:var _func = obj[type]; 11 */ 12 /* 13 --第4步-- 14 通过apply执行重写前的这个函数 15 通过apply执行callback回调函数 16 */ 17 _func.apply(obj,_args); 18 callback.apply(obj,_args); 19 } 20 接下来可以做个测试: 21 22 var _t = {}; 23 _t.test = function(a,b,c){ 24 alert("_t:"+a+"-"+b+"-"+c); 25 } 26 var fc = new funcConnect(); 27 fc._connect(_t,"test",function(a,b,c){ 28 alert("_fc:"+a+"-"+b+"-"+c); 29 }); 30 _t.test(1,2,3);
最终在执行_t.test(1,2,3)函数的时候,会弹出两个alert
3、再一次进行优化
现在有2个问题:
1、每次调用_connect的时候,我都需要new funcConnect(),既然是公共组件,如何直接把这个方法拿来就用?
2、现在通过fc._connect()的方式来调用,既然是监听,如何做到_t._connect("test",function(){ })?这样不就更明确是监听谁的哪个方法了吗。
要解决上面两个问题,我想到的就是继承,既然_t想要调用_connect函数,那就让_t继承funcConnect类,那样他就有了_connect,也就可以随意调用。
1 var extended = function(parent,child){ 2 var p = new parent(); 3 for(var pFuncs in p){ 4 child.prototype[pFuncs] = p[pFuncs]; 5 } 6 return child; 7 }
上面这段是实现继承的代码,js类中所有的成员(变量和函数)都存放在prototype里,即它的原型中,因此,只要把需要继承的父级对象中的所有成员变量及函数添加到被继承的子集prototype中,子集就含有了父级所有的成员,又由于js对象是以指针传递,所以子集上的新成员和父级上的成员同属一个(即子集上的变了会影响父级的,父级变了会影响子集的)。如此一来便实现了继承。
下面我在funcConnect类里再添加一个connect()来封装一下_connect()
1 funcConnect.prototype.connect(type,callback){ 2 //既然本身会被继承,那this同样会被指向继承这个函数的子集 3 this._connect(this,type,callback); 4 }
接下来再重写一个测试的类,以及测试方法,让它实现继承
1 //这是通过extended来继承funcConnect类 2 var connectTest = extended(funcConnect,(function(){ 3 function connectTest(a,b){ 4 this.a = a; 5 this.b = b; 6 } 7 connectTest.prototype.test(c){ 8 alert("connectTest:"+c); 9 } 10 }())); 11 //实例化一个继承后的connectTest 12 var ct = new connectTest("1","2"); 13 //监听这个类中的test方法 14 ct.connect("test",function(c){ 15 alert("ct:"+this.a+"-"+this.b+"-"+this.c); 16 }); 17 //通过点击按钮来调用test方法,当然这个方法也可以在connectTest内调用 18 document.getElementById("ClickButton2").onclick = function(){ 19 ct.test("3"); 20 }
运行后,会先后弹出两个alert窗口,第一个:connectTest:3,第二个:ct:1-2-3
4、附上完整的代码
1 /** 2 * 用js创建一个类 3 * 方式: 4 * var func = (function(){ 5 * //构造方法 6 * function func(a,b){ 7 * this.a = a; 8 * this.b = b; 9 * } 10 * //成员方法 11 * func.prototype.get(type){ 12 * return this[type]; 13 * } 14 * func.prototype.set(type,value){ 15 * this[type] = value; 16 * } 17 * }()); 18 */ 19 var funcConnect = (function(){ 20 function funcConnect(){ 21 //这是一个构造方法 22 } 23 funcConnect.prototype.connect(type,callback){ 24 //既然本身会被继承,那this同样会被指向继承这个函数的子集 25 this._connect(this,type,callback); 26 } 27 /** 28 * obj:函数所在的对象 29 * type:函数名(事件类型) 30 * callback:回调函数(函数执行时要触发的这个函数) 31 */ 32 funcConnect.prototype._connect(obj,type,callback){ 33 var _func = obj[type]; // --第3步-- 我们要备份的函数 34 /* 35 --第1步-- 36 有了这三个参数我们就能获取到要重写的函数,即 obj[type] 37 接下来是重写这个函数: 38 */ 39 obj[type] = function(){ 40 /* 41 --第2步-- 42 当这个函数被调用时,不管原始定义的参数是什么,我都可以通过下面的方式获取参数 43 */ 44 var _args = arguments; //获取的参数是个数组 45 /* 46 --第3步-- 47 在重写的这个函数里我还要执行以下重写前的这个函数 48 所以在重写前把这个函数备份一下:var _func = obj[type]; 49 */ 50 /* 51 --第4步-- 52 通过apply执行重写前的这个函数 53 通过apply执行callback回调函数 54 */ 55 _func.apply(obj,_args); 56 callback.apply(obj,_args); 57 } 58 } 59 }()); 60 61 var extended = function(parent,child){ 62 var p = new parent(); 63 for(var pFuncs in p){ 64 child.prototype[pFuncs] = p[pFuncs]; 65 } 66 return child; 67 }; 68 //这是通过extended来继承funcConnect类 69 var connectTest = extended(funcConnect,(function(){ 70 function connectTest(a,b){ 71 this.a = a; 72 this.b = b; 73 } 74 connectTest.prototype.test(c){ 75 alert("connectTest:"+c); 76 } 77 }())); 78 //实例化一个继承后的connectTest 79 var ct = new connectTest("1","2"); 80 //监听这个类中的test方法 81 ct.connect("test",function(c){ 82 alert("ct:"+this.a+"-"+this.b+"-"+this.c); 83 }); 84 //通过点击按钮来调用test方法,当然这个方法也可以在connectTest内调用 85 document.getElementById("ClickButton2").onclick = function(){ 86 ct.test("3"); 87 }