关于js模拟c#的Delegate(委托)实现
这是我的第一篇博文,想来讲一讲js的函数。我的标题是js模拟c#的Delegate。
一、什么是Delegate(委托)
在jquery中有delegate函数,作用是将某个dom元素的标签的事件委托给一个函数队列,在触发这个事件的时候会触发这个函数队列中的所有函数。而c#中的Delegate对象也是如此,将多个方法添加至一个委托对象,或称将多个方法委托给一个委托对象,当调用委托对象的Invoke方法时会调用所有被委托的方法。由此可以看出Delegate的本质应该是一个函数队列,执行委托对象就是遍历执行函数队列。
二、实现委托构造函数
明白了委托对象的本质是一个函数队列,就可以着手建立委托对象了。先写一个简单的委托的构造函数,这个构造函数返回的对象实现以下功能
1、add(); 添加函数
2、remove(); 移除函数
3、(); 调用委托
function SimpleDelegate() {
var _fnQuene = [];
var delegate=function(){
for(var i=0;i<_fnQuene.length;i++){
_fnQuene[i].apply(this,arguments);
}
}
delegate.add = function (fn) {
//只有函数才能被添加到函数队列
if(Object.prototype.toString.call(fn)==='[object Function]'){
_fnQuene.push(fn);
}
}
delegate.remove = function (fn) {
_fnQuene.splice(_fnQuene.indexOf(fn), 1);
}
return delegate;
}
这个函数已经能实现基本的添加移除和调用,如果使用规范,这个函数没有任何问题。但如果使用不规范,会造成严重的后果,比如下面的代码。在后面的代码中执行的时候会不断添加函数去调用add函数,这样一来就造成了死循环。
//正确的使用委托
var d1=new SimpleDelegate();
d1.add(function(){
console.log(1);
})
d1.add(function(){
console.log(2);
})
d1(); //打印1,2 //不规范使用,造成死循环
var d2=new SimpleDelegate();
d2.add(function(){
d2.add(function(){
console.log(1);
})
})
d2();
我们需要修改这个函数,首先我们发现在调用委托的时候会获取_fnQuene的length值,而这个length是不断在变化的,每添加一个函数长度就会增加,于是就产生了循环调用add造成死循环的问题,如果我们在调用前获取length的值并缓存起来就不用担心添加的问题了
var delegate = function () {
var len = _fnQuene.length;
for (var i = 0; i < len; i++) {
_fnQuene[i].apply(this, arguments);
}
}
执行结果变成如下,不再死循环
var d2 = new SimpleDelegate();
d2.add(function () {
console.log(1);
d2.add(function () {
console.log(2);
})
})
d2(); //第一次执行打印1
d2(); //第二次执行打印1,2
d2(); //第三次执行打印1,2,2
但这样还是不够,我们只解决了add的问题,如果在函数中调用remove呢,这样好像缓存length也不行
var d=new SimpleDelegate();
function f1(){
console.log(1);
d.remove(f2);
}
function f2(){
console.log(2);
}
d.add(f1);
d.add(f2)
d(); //执行到索引为1的函数,函数已经被删除,报错
add和remove函数并不靠谱,我们应该修改的是Delegate的机制,之前,我们调用add和remove会直接将操作直接作用在fnQuen上,但当我们开始执行委托后就不应该对fnQuene进行操作。如果添加的函数对fnQuene进行操作,应当把操作缓存下来,并在下一次调用委托之前执行这些操作,并在执行后立刻删除这些操作。如此一想,为什么我们不把每一次操作都缓存下来呢?每次委托执行前添加和移除,委托一旦开始执行,调用的add和remove函数又会缓存相应的操作。
function SimpleDelegate() {
var _fnQuene = [];
var _waitingQuene = [];
var delegate = function () {
var len = _waitingQuene.length; //缓存本次执行委托时已有的操作数量
var i;
//首先调用所有缓存的操作
for (i = 0; i < len; i++) {
_waitingQuene[i]();
}
len = _fnQuene.length; //缓存当前函数队列的长度
for (i = 0; i < len; i++) {
_fnQuene[i].apply(this, arguments);
}
}
delegate.add = function (fn) {
_waitingQuene.push(function(){
_fnQuene.push(fn);
})
}
delegate.remove = function (fn) {
_waitingQuene.push(function(){
_fnQuene.splice(_fnQuene.indexOf(fn),1);
})
}
return delegate;
}
最后为了这个函数使用更加方便,添加链式编程并支持一些重载,以及为对象添加一些属性
function cloneArray(arr) {
var ret = [];
//这里不用map是因为arr可以是类数组对象
for (var i = ; i < arr.length; i++) {
ret[i] = arr[i];
}
return ret;
}
function Delegate() {
var _fnQuene = [];
var _waitingQuene = [];
var delegate = function () {
var len = _waitingQuene.length; //缓存本次执行委托时已有的操作数量
var i;
//首先调用所有缓存的操作
for (i = ; i < len; i++) {
_waitingQuene[i]();
}
_waitingQuene.length = ;
len = _fnQuene.length; //缓存当前函数队列的长度
for (i = ; i < len; i++) {
_fnQuene[i].apply(this, delegate._argIntercept ?
cloneArray(arguments) :
arguments
);
}
}
delegate.add = function (fn) {
var args = arguments;
var arg;
var type;
var self = this;
function add() {
for (var i = ; i < args.length; i++) {
arg = args[i];
type = Object.prototype.toString.call(arg);
if (type === '[object Array]') {
add.apply(self, arg);
}
else if (type === '[object Function]') {
_fnQuene.push(arg);
}
}
}
_waitingQuene.push(add);
return this;
}
delegate.remove = function (fn) {
var args = arguments;
var arg;
var type;
var self = this;
function remove() {
for (var i = ; i < args.length; i++) {
arg = args[i];
type = Object.prototype.toString.call(arg);
if (type === '[object Array]') {
remove.apply(self, arg);
}
else if (type === '[object Function]') {
var idx = _fnQuene.indexOf(arg);
if (idx === -) {
continue;
}
_fnQuene.splice(idx, );
}
}
}
_waitingQuene.push(remove);
return this;
}
//检查某个函数是否委托给了当前委托对象
delegate.has = function (fn) {
return _fnQuene.indexOf(fn) !== -;
}
Object.defineProperties(delegate, {
//委托中函数的数量
_length: {
get: function () {
return _fnQuene.length;
}
},
//是否拦截参数,如果为true,则委托被调用时传给函数的参数为副本
_argIntercept: {
value: false,
}
})
delegate.constructor = Delegate;
return delegate;
}
关于js模拟c#的Delegate(委托)实现的更多相关文章
- 第一百七十一节,jQuery,高级事件,模拟操作,命名空间,事件委托,on、off 和 one
jQuery,高级事件,模拟操作,命名空间,事件委托,on.off 和 one 学习要点: 1.模拟操作 2.命名空间 3.事件委托 4.on.off 和 one jQuery 不但封装了大量常用的事 ...
- js_html_input中autocomplete="off"在chrom中失效的解决办法 使用JS模拟锚点跳转 js如何获取url参数 C#模拟httpwebrequest请求_向服务器模拟cookie发送 实习期学到的技术(一) LinqPad的变量比较功能 ASP.NET EF 使用LinqPad 快速学习Linq
js_html_input中autocomplete="off"在chrom中失效的解决办法 分享网上的2种办法: 1-可以在不需要默认填写的input框中设置 autocompl ...
- js模拟抛出球运动
js练手之模拟水平抛球运动 -匀加速运动 -匀减速运动 模拟运动有些基本的思路,当前所在点的坐标,元素的长宽是多少,向右/向下运动x/y增加,向上/向左运动x/y减少,运动的路程是多少,用什么方程进行 ...
- Gremlins.js – 模拟用户随机操作的 JS 测试库
Gremlins.js 是基于 JavaScript 编写的 Monkey 测试库,支持 Node.js 平台和浏览器中使用.Gremlins.js 随机模拟用户操作:单击窗口中的任意位置,在表格中输 ...
- 快速理解C#高级概念(一) Delegate委托
做.NET开发很久,最近重新温习<C#高级编程>一书.发现很多曾经似懂非懂的问题,其实也是能够慢慢钻研慢慢理解的. 所以,打算开写<C#高级编程系列>博文.其中会借鉴<C ...
- JS 模拟手机页面文件的下拉刷新
js 模拟手机页面文件的下拉刷新初探 老总说需要这个功能,好吧那就看看相关的东西呗 最后弄出了一个简单的下拉刷新页面的形式,还不算太复杂 查看 demo 要在仿真器下才能看到效果,比如chrome的里 ...
- 由chrome剪贴板问题研究到了js模拟鼠标键盘事件
写在前面 最近公司在搞浏览器兼容的事情,所有浏览器兼容的问题不得不一个人包了.下面来说一下今天遇到的一个问题吧 大家都知道IE下面如果要获得剪贴板里面的信息的话,代码应该如下所示 window.cli ...
- node.js模拟qq漂流瓶
(文章是从我的个人主页上粘贴过来的,大家也可以访问我的主页 www.iwangzheng.com) node.js模拟简易漂流瓶,页面有扔瓶子和捡瓶子的功能,一个瓶子只能被捡到一次,阅读完就置状态位, ...
- css配合js模拟的select下拉框
css配合js模拟的select下拉框 <!doctype html> <html> <head> <meta charset="utf-8&quo ...
随机推荐
- Java 原型模式
http://www.cnblogs.com/itTeacher/archive/2012/12/02/2797857.html http://www.cnblogs.com/java-my-life ...
- SQLSERVER如何导入数据保持ID不变(ID为自增主键)
使用SQL SERVER最操蛋的就是导入数据,以前用企业管理器直接导数据,导一次骂N次娘,在骂了微软无数次娘之后总结了一个方法揍合着还算受用. 其核心要点就是要将数据结构导入到目标数据库服务器上,再来 ...
- Nancy简单实战之NancyMusicStore(六):写在最后
前言 由于公司搬家后,住的地方离上班的地方远了N倍,以前是走路十多分钟就可以到公司的,上班时间也从9:00提早到8:30 现在每天上班都是先坐公交,然后再坐地铁,在这段路上比较浪费时间而且每天都是要6 ...
- iOS横向瀑布流的封装
前段时间, 做一个羡慕, 需要使用到瀑布流! 说道瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 从scrollView 到 tableView 书写的瀑布流, 然后再到2012年iOS6 苹 ...
- node之路由介绍
路由介绍 ----路由是指向客户端提供它所发出的请求内容的机制:----对基于 Web 的客户端 / 服务器端程序而言,客户端在 URL 中指明它想要的内容,具体来说就是路径和查询字符串 下面我看看一 ...
- Python单元测试PyUnit框架轻度整改
原理 参考:单元测试原理 背景 年后有段时间没写代码了,所以趁着周末找了个python单元测试玩下,测试自己的Android应用.发现PyUnit虽然在单个脚本文件中添加多个测试用例,比如官网提供的方 ...
- Struts2系列笔记(7)---Struts2类型转换
Struts2类型转换 struts2中内置了大量的类型转换器用来完成数据类型转换的问题,这篇随笔主要通过两个方面来写Struts类型转换 1:Struts2内置的类型转换器 2:如何自定义 ...
- [Kafka] - Kafka 安装介绍
Kafka是由LinkedIn公司开发的,之后贡献给Apache基金会,成为Apache的一个顶级项目,开发语言为Scala.提供了各种不同语言的API,具体参考Kafka的cwiki页面: Kafk ...
- Python之文件与目录操作及压缩模块(os、shutil、zipfile、tarfile)
Python中可以用于对文件和目录进行操作的内置模块包括: 模块/函数名称 功能描述 open()函数 文件读取或写入 os.path模块 文件路径操作 os模块 文件和目录简单操作 zipfile模 ...
- WC2015 k小割(k短路+暴力+搜索)
首先这道题不是非同一般的恶心,三个数据层次对应三个程序= = PROBLEM:http://uoj.ac/problems解法: 1~2直接暴力枚举边的选择与否+判断就行了 7~14可以发现是一个平面 ...