`

[Ext源码解读]事件的注册、添加与触发是如何实现的

    博客分类:
  • Ext
阅读更多
Ext提供了一套强大的事件处理机制,每个组件都有许多事件,用户可以很方便通过动态的方式为对象添加/删除事件监听函数(在实例化的时候不是必须的),从而动态的改变或添加对象的行为,而这一切又是如何实现的呢?
阅读前请您准备好Javascript基础知识(包括:prototype属性、Functin对象的apply和call方法、函数的作用域等)。
该脚本剥离了许多分支逻辑,修改了大多数函数的实现仅保留最基本逻辑,如需详细了解Ext内部请阅读Ext源代码。Enjoy it!
该脚本脱离了Ext库的依赖,可直接复制在firbug下运行,(推荐chrome的javascript控制台,功能更强大)
预期运行结果:

/**
 * 观察者模式,也称作订阅发布模式,体现一种一对多关系,一般用在一个对象的改变将影响其它多个对象的情况下。
 * 
 * 
 */
Observable = function(name) {
	this.name = name;
	this.events = {};
	this.addEvents('before', 'after');

	this.getName = function() {
		if (this.fireEvent('before', this.name) !== false) {
			console.log('My name is ' + this.name);
			this.fireEvent('after', this.name);
			return this.name;
		}
	}

}
Observable.prototype = {
	
	/**
	 * 添加事件名,就是个注册罢了,可以同时注册多个;试想一下如果添加就初始化了Event对象,如果一个组件有很多事件类型,
	 * 那么每次组件初始化它所具备的事件时要把每个事件组件都是实例化,那该是一件多么费内存的操作啊!
	 */
	addEvents : function() {
		var a = arguments, i = a.length;
		while (i--) {
			this.events[a[i]] = true;// 这里可不是数组,这是变量的形式访问属性
		}
	},

	addListener : function(eventName, fn, scope) {
		var ce;// 事件对象或者是true
		ce = this.events[eventName] || true;
		if (ce == true) {//事件默认是没有初始化的
			this.events[eventName] = ce = new Event(this, eventName);
		}
		ce.addListener(fn, scope);
	},
	/**
	 * @augments 第一个是事件名,之后是监听方法需要的参数
	 * @return ret 进一步执行的标实
	 */
	fireEvent : function() {
		var a = arguments, ret = true, ce;
		ename = a[0], ce = this.events[ename];
		if (Object.prototype.toString.call(ce) === '[object Object]') {
			ret = ce.fire.apply(ce, Array.prototype.slice.call(a, 1, a.length));
		}
		return ret;
	}
};

Event = function(obj, name) {
	this.name = name;
	this.obj = obj;
	this.listeners = [];
};

Event.prototype = {
	addListener : function(fn, scope, options) {
		scope = scope || this.obj;
		if (this.firing) { // 如果正在触发监听事件,则用slice方法创建一个与原对象一样的新对象,这样不会影响正在触发的监听方法链
			this.listeners = this.listeners.slice(0);
		}
		this.listeners.push({
					fireFn : fn,
					scope : scope,
					options : options
				});
	},
	fire : function() {
		var listeners = this.listeners, 
			args = Array.prototype.slice.call(arguments, 0, arguments.length), 
			len = listeners.length, i = 0, l;
		if (len > 0) {
			this.firing = true;
			for (; i < len; i++) {
				l = listeners[i];
				// 添加监听时设置的作用域具有最高优先级,其次是当前Event对象的目标对象,都没有就是window对象
				if (l&& l.fireFn.apply(l.scope || this.obj || window, args) === false) {
					return (this.firing = false);
				}
			}
		}
		this.firing = false;
        return true;
	}
};

var obj1 = new Observable('bob');

obj1.addListener('before', function() {
			console.log('First before listener my name is ' + this.name);
		});

obj1.addListener('before', function() {
			console.log('Second before listener my name is ' + this.obj1.name);
			return false;
		}, this);
obj1.addListener('after', function() {
			console.log('First after listener my name is ' + this.name);
		});
obj1.addListener('after', function() {
			console.log('Second after listener my name is ' + this.name);
		});
obj1.getName();
  • 大小: 4.4 KB
分享到:
评论
6 楼 chemzqm 2010-04-17  
hommy8 写道
var a = arguments;
Array.prototype.slice.call(a, 1, a.length);


请教大家,以上代码,a就是一个数组,为什么还要通过继承的方式调用Array的slice方法呢?这不是多此一举吗?看得我头都晕了。  请问是不是有特殊作用呢?

错了 a不是数组 arguments是函数执行时的参数对象,它只是具有array的某些方法属性罢了。还有一点,这种方式方式不能叫做继承,call方法的目的是装换作用域并传参,这里是把Array.prototype.slice这个函数的作用域转到a这个对象上;这行代码意义在于创建一个新的数组对象,它的起点和终点是原来的1到a.length,这么做是因为写fireEvent这个方法的时候第一个参数始终都是事件名,之后才是监听函数所需要的参数
5 楼 hommy8 2010-04-16  
var a = arguments;
Array.prototype.slice.call(a, 1, a.length);


请教大家,以上代码,a就是一个数组,为什么还要通过继承的方式调用Array的slice方法呢?这不是多此一举吗?看得我头都晕了。  请问是不是有特殊作用呢?
4 楼 chemzqm 2010-04-15  
beck5859509 写道
观察者模式,也称作订阅发布模式,体现一种一对多关系,一般用在一个对象的改变将影响其它多个对象的情况下。

我又重新读了以便代码,发现上面写的代码和这句话关系好象不大,还是太抽象了,
比如
var obj1 = new Observable('bob');  
obj1是观察者还是目标啊,它注册的before事件并没有影响到其它多个对象.
期待LZ详解.

obj1是观察者,被影响的目标是那些监听函数(在javascript中函数也是对象),你可以把这些监听函数想象成订阅者;在这里可以看到,Observable的fireEvent方法可以同时影响到多个注册到相同事件的方法上
3 楼 beck5859509 2010-04-15  
观察者模式,也称作订阅发布模式,体现一种一对多关系,一般用在一个对象的改变将影响其它多个对象的情况下。

我又重新读了以便代码,发现上面写的代码和这句话关系好象不大,还是太抽象了,
比如
var obj1 = new Observable('bob');  
obj1是观察者还是目标啊,它注册的before事件并没有影响到其它多个对象.
期待LZ详解.
2 楼 chemzqm 2010-04-15  
beck5859509 写道
写得不错,不过有处不是很清楚。
this.getName = function() { 
        if (this.fireEvent('before', this.name) !== false) { 
            console.log('My name is ' + this.name); 
            this.fireEvent('after', this.name); 
            return this.name; 
        } 
    } 
从楼主的执行结果来看,上面的 this.fireEvent("before',this.name)返回的是false,所以根本不会去执行after这个事件,楼主连续注册两次before事件,就是想让fire方法中的           return (this.firing = false)  语句返回 false,不知道我的理解对不对

更确切的说我是想说明通过在监听设置返回值为false有时是可以打断原本函数的流程的。当然这种做法实现需要 return (this.firing = false);这段代码来实现
另外一点可以从代码中看出:只要一个监听返回了false,其他后续注册在这个事件上的监听也不会被触发,譬如说我在最后面再添加一个before事件的监听,那它也会因为前面相同事件的监听返回false而不被执行
1 楼 beck5859509 2010-04-14  
写得不错,不过有处不是很清楚。
this.getName = function() { 
        if (this.fireEvent('before', this.name) !== false) { 
            console.log('My name is ' + this.name); 
            this.fireEvent('after', this.name); 
            return this.name; 
        } 
    } 
从楼主的执行结果来看,上面的 this.fireEvent("before',this.name)返回的是false,所以根本不会去执行after这个事件,楼主连续注册两次before事件,就是想让fire方法中的           return (this.firing = false)  语句返回 false,不知道我的理解对不对

相关推荐

Global site tag (gtag.js) - Google Analytics