上QQ阅读APP看书,第一时间看更新
5.2.3 simple-inheritance
作者为大名鼎鼎的John Resig,项目直接放在他的一篇博文中:
http://ejohn.org/blog/simple-javascript-inheritance/
特点是方法链的实现非常优雅,节俭!
源码解读如下。
(function() { // /xyz/.test(function(){xyz;})是用于判定函数的toString是否能暴露里面的实现 // 因为Function.prototype.toString没有做出强制规定如何显示自身,根据浏览器实现而定 // 如果里面能显示内部内容,那么我们就使用 /\b_super\b/来检测函数里面有没有.super语句 // 当然这个也不很充分,只是够用的程度;否则就返回一个怎么也返回true的正则 // 比如一些古老版本的Safari、Mobile Opera与 Blackberry浏览器,无法显示函数体的内容 // 就需要用到后面的正则 var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/; // 所有人工类的基类 this.Class = function() { }; //这是用于生成目标类的子类 Class.extend = function(prop) { var _super = this.prototype;//保存父类的原型 //阻止init被触发 initializing = true; var prototype = new this();//创建子类的原型 //重新打开,方便真实用户可以调用init initializing = false; //将prop里的东西逐个复制到prototype,如果是函数将特殊处理一下 //因为复制过程中可能掩盖了超类的同名方法,如果这个函数里面存在_super的字样,就笼统地 //认为它需要调用父类的同名方法,那么我们需要重写当前函数 //重写函数运用了闭包,因此fnTest正则检测可以减少我们重写方法的个数, //因为不是每个同名函数都会向上调用父类方法 for (var name in prop) { prototype[name] = typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name]) ? (function(name, fn) { return function() { var tmp = this._super;//保存到临时变量中 //当我们调用时,才匆匆把父类的同方法覆写到_super里 this._super = _super[name]; //然后才开始执行当前方法(这时里面的this._super已被重写),得到想要的效果 var ret = fn.apply(this, arguments); //还原this._super this._super = tmp; //返回结果 return ret; }; })(name, prop[name]) : prop[name]; } // 这是目标类的真实构造器 function Class() { // 为了防止在生成子类的原型(new this())时触发用户传入的构造器init // 使用initializing进行牵制 if (!initializing && this.init) this.init.apply(this, arguments); } //将修改好的原型赋值 Class.prototype = prototype; // 确保原型上constructor正确指向自身 Class.prototype.constructor = Class; //添加extend类方法,生于生产它的子类 Class.extend = arguments.callee; return Class; }; })();
创建一个Animal类与一个Dog子类。
var Animal = Class.extend({ init: function(name) { this.name = name; }, shout: function(s) { console.log(s); } }); var animal = new Animal(); animal.shout('animal'); // animal var Dog = Animal.extend({ init: function(name, age) { //调用父类构造器 this._super.apply(this, arguments); this.age = age; }, run: function(s) { console.log(s); } }); var dog = new Dog("dog", 4); console.log(dog.name); //dog dog.shout("xxx"); // xxx dog.run("run"); // run console.log(dog instanceof Dog && dog instanceof Animal);
顺便一提,simple-inheritance的老师有两个,Base2的继承系统与Prototype.js的方法链系统。Prototype.js与simple-inheritance都是对函数的toString进行反编译,看里面有没有_super或$super的字眼才决定重写。不同的是Prototype.js只检测方法的参数列表,因此杂质更少,更加可靠!
下面是Prototype.js方法链演示。
var Person = Class.create(); Person.prototype = { initialize: function(name) { this.name = name; }, say: function(message) { return this.name + ': ' + message; } }; var guy = new Person('Miro'); console.log(guy.say('hi')); // "Miro: hi" //创建子类 var Pirate = Class.create(Person, { say: function($super, message) { //注意这里的传参,$super为超类的同名方法 return $super(message) + ", yarr!"; //这需要外科手术般的闭包来实现 } }); var john = new Pirate('Long John'); console.log(john.say('good bye')) //Long John: good bye, yarr!
当然Prototype.js这样做有个缺憾,导致定义时与使用时的参数不一样。对于大多数用户来说,实现并不是他们所关心的,保持简洁优雅的接口才是重点。如果翻看jQuery UI的源码,它的widget.类工厂已经把方法链设计得登峰造顶了。它会在函数的 this 对象添加两个临时方法,_super 相当于simple-inheritance的_super,它的参数需要用户逐个转手传递。_superApply是_super的强化版,因为如果外围方法是不确定的,那么你也没法为_super传参,因此它只需用户丢个arguments对象进去!