解读rightjs的继承机制3
佛经里有句话,叫功不唐捐。意思是所有努力的效果不一定产生在当下,可能产生在未来,但你只要做了,它一定就有意义。我们心态平和一点,逐步去推进它,逐步去改良它,对未来必定很有意义。
有人总很狭隘地支持某一类库或框架,其实好的东西都是互相渗透的。像jQuery,它提供了对DOM的操作,但仅仅这样是不够的。请不要用后台的增删改查的目光来看前台,前台的展现是非常丰富多彩的。后台也不是那么枯燥,当然对无能的人怎么也一样。他们总是有借口逃避学习,总是问人来解决项目上的问题。这样的IT生涯真的只能撑到30岁。我们专注一个东西,其他同类型的产品都是非常有参考价值,能扩宽我们的思维。比如最早称霸javascript界的Prototype.js,它是作为rails的一个辅助项目诞生。借鉴于真正的一切都是对象的ruby的设计,Prototype.js当年横扫天下,无人可敌。我们可以看到其许多方法名都是取自ruby标准库的。像Base2这样强大的库,我觉得它也从Prototype.js吸取了不少灵感。mootools号称是Prototype.js,框架是Prototype,但许多实现却改自Base2。这三者都拥有强大的继承机制,反正学了其中一个,对其他两个就很容易上手。换言之,功不唐捐,你的智慧投资不会白费的。
西方的开源世界非常活跃,新兴类库都是有着浓厚的已知框架的影子。rightjs就是一个例子。今天讲解其两个模块,不由得感概万分。
Options模块,专门来处理参数。在Prototype著名的特效库中,已经有这种迹象了。mootools干脆交由Object.reset来做这粗活。为什么要这样做呢?因为通常我们要传入一个属性包来配置新生成的类,为它添加类成员,实例成员什么的,在mootools2的蓝图中,甚至计划配置友元成员。这个其实在mootools1.2.4中已用protect方法实现了,不过它想搞得更smart一些。
Options = { setOptions: function(options) { //整合两个对象的options属性,一个是配置用的属性包,一个是新类,注意Options模块是不能独立运作的, //它肯定用于其他模块或类中。 var options = this.options = Object.merge(Class.findSet(this, 'options'), options), match, key; //如果它的on属性为函数,执行方法,如this.on("click",function(){}) if (isFunction(this.on)) { for (key in options) { if (match = key.match(/on([A-Z][A-Za-z]+)/)) { this.on(match[1].toLowerCase(), options[key]); delete(options[key]); } } } return this; }, //由于rightjs的类工厂最多只有两个参数,父类与作为属性包的对象,现在的目的是取得那个属性包 cutOptions: function(args) { var args = $A(args); this.setOptions(isHash(args.last()) ? args.pop() : {}); return args; } };
下面是观察者模块,它的许多方法都支持多种传参形式,像jQuery一样,把逻辑搞得很复杂,实在恶心。
Observer = new Class({ include: Options, /** * general constructor * * @param Object options */ initialize: function(options) { this.setOptions(options); Observer.createShortcuts(this, Class.findSet(this, 'events')); }, //this.on("click", fnCallback) //添加回调函数与其事件名,额外参数 on: function() { var args = $A(arguments), event = args.shift(); if (isString(event)) { //创建一个$listeners存放一个个特别的对象 if (!defined(this.$listeners)) this.$listeners = []; var callback = args.shift(), name; switch (typeof callback) { case "string": name = callback; callback = this[callback]; case "function": var hash = {}; // DON'T move it in the one-line hash variable definition, // it causes problems with the Konqueror 3 later on hash.e = event; hash.f = callback; hash.a = args; hash.r = name; this.$listeners.push(hash); break; default: if (isArray(callback)) { for (var i=0; i < callback.length; i++) { //看它是否为二维数组 this.on.apply(this, [event].concat( isArray(callback[i]) ? callback[i] : [callback[i]] ).concat(args)); } } } } else { //处理参数只有一个属性包的情形 for (var name in event) { this.on.apply(this, [name].concat( isArray(event[name]) ? event[name] : [event[name]] ).concat(args)); } } return this; }, //没有好说的,与Prototype的observes差不多 observes: function(event, callback) { if (!isString(event)) { callback = event; event = null; } if (isString(callback)) callback = this[callback]; return (this.$listeners || []).some(function(i) { return (event && callback) ? i.e === event && i.f === callback : event ? i.e === event : i.f === callback; }); }, stopObserving: function(event, callback) { if (isHash(event)) { for (var key in event) { this.stopObserving(key, event[key]); } } else { if (!isString(event)) { callback = event; event = null; } if (isString(callback)) callback = this[callback]; this.$listeners = (this.$listeners || []).filter(function(i) { return (event && callback) ? (i.e !== event || i.f !== callback) : (event ? i.e !== event : i.f !== callback); }, this); } return this; }, //用于返回不重复回调函数,如果不指明事件名则返回全部 listeners: function(event) { return (this.$listeners || []).filter(function(i) { return !event || i.e === event; }).map(function(i) { return i.f; }).uniq(); }, //强制执行其对象上的事件 fire: function() { var args = $A(arguments), event = args.shift(); (this.$listeners || []).each(function(i) { if (i.e === event) i.f.apply(this, i.a.concat(args)); }, this); return this; }, extend: { //让所有对象都具有观察者模式 create: function(object, events) { $ext(object, Object.without(this.prototype, 'initialize', 'setOptions'), true); return this.createShortcuts(object, events || Class.findSet(object, 'events')); }, createShortcuts: function(object, names) { //为目标对象object扩展像onLayoutChange的自定义事件 (names || []).each(function(name) { var method_name = 'on'+name.replace(/:/g, '_').camelize().capitalize(); if (!defined(object[method_name])) { object[method_name] = function() { return this.on.apply(this, [name].concat($A(arguments))); }; } }); return object; } } }); //对observe进行别名 $alias(Observer.prototype, { observe: 'on' });
观察者模式让自定类也拥有像事件系统的关联操作,一变大家跟着变,非常有利于解耦。从最初的Class到Options到Observer,它把能拆解的东西都拆解了。这种模块化设计非常有利于我们学习。像jQuery,它走的是另一条路,通过大量的插件来弥补其核心库的功能。当然,人多好办事,它也不时吸纳一些优秀的方法加入核心库了。但无论怎么折腾,类库的能力还是很有限,与这种全面走扩展道路的框架没得比。与对于学习javascript来说,像Prototype这样的框架能让你收获更大。