[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;// 事件对象或者是truece = 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();var a = arguments;Array.prototype.slice.call(a, 1, a.length);