上QQ阅读APP看书,第一时间看更新
5.2 各种类工厂的实现
在第1节我们演示各种继承方式的实现,但都很凌乱。我们希望提供一个专门的方法,只要用户传入相应的参数或按一定的简单格式就能创建一个类,特别是子类。
由于主流框架的类工厂实现太依赖于它们庞杂的工具函数,而一个精巧的类工厂也不过百行左右,因此本章就不打算罗列 Prototype.js、Mootools 等的代码了,介绍另外一些不太出名但相当有水准的小库吧。
5.2.1 相当精巧的库——P.js
它的github地址为 https://github.com/jayferd/pjs。
这是一个相当于精巧的库,尤其在调用父类的同名方法时,它直接把父类的原型抛在你眼前,连_super也省了。
它的源码解读如下。
var P = (function(prototype, ownProperty, undefined) { function isObject(o) { return typeof o === 'object'; } function isFunction(f) { return typeof f === 'function'; } function BareConstructor() {}; function P(_superclass /* = Object */ , definition) { //如果只传一个参数,没有指定父类 if(definition === undefined) { definition = _superclass; _superclass = Object; } //C为我们要返回的子类,definition中的init为用户构造器 function C() { var self = new Bare; console.log(self.init) if(isFunction(self.init)) self.init.apply(self, arguments); return self; } function Bare() { //这个构造器是为了让C不用new就能返回实例而设的 } C.Bare = Bare; //为了防止改动子类影响到父类,我们将父类的原型赋给一个中介者BareConstructor //然后再将这中介者的实例作为子类的原型 var _super = BareConstructor[prototype] = _superclass[prototype]; var proto = Bare[prototype] = C[prototype] = new BareConstructor; // //然后C与Bare都共享同一个原型 //最后修正子类的构造器指向自身 proto.constructor = C; //类方法mixin,不过def对象里面的属性与方法糅杂到原型里面去 C.mixin = function(def) { Bare[prototype] = C[prototype] = P(C, def)[prototype]; //Bare[prototype] = return C; } //definition最后延迟到这里才起作用 return(C.open = function(def) { var extensions = {}; //definition有两种形态 //如果是函数,那么子类原型、父类原型、子类构造器、父类构造传进去, //如果是对象则直接置为extensions if(isFunction(def)) { extensions = def.call(C, proto, _super, C, _superclass); } else if(isObject(def)) { extensions = def; } //最后混入子类的原型中 if(isObject(extensions)) { for(var ext in extensions) { if(ownProperty.call(extensions, ext)) { proto[ext] = extensions[ext]; } } } //确保init为一个函数 if(!isFunction(proto.init)) { proto.init = _superclass; } return C; })(definition); //这里为一个自动执行函数表达式,相当于 //C.open = function(){/*....*/} //C.open(definition) //return C; //换言之,返回的子类存在3个类成员,Base, mixin, open } return P; //暴露到全局 })('prototype', ({}).hasOwnProperty);
我们尝试创建一个类。
var Animal = P(function(proto, superProro) { proto.init = function(name) { //构造函数 this.name = name; }; proto.move = function(meters) { //原型方法 console.log(this.name + " moved " + meters + "m."); } }); var a = new Animal("aaa") var b = Animal("bbb");//无“new”实例化 a.move(1) b.move(2)
当然在现在的情景下,我们可以使用更简洁的定义方式。
var Snake = P(Animal, function(snake, animal) { snake.init = function(name, eyes) { animal.init.call(this, arguments); //调用父类构造器 this.eyes = 2; } snake.move = function() { console.log("Slithering..."); animal.move.call(this, 5); //调用父类的同名方法 }; }); var s = new Snake("snake", 1); s.move(); console.log(s.name); console.log(s.eyes);
下面是私有属性的演示,由于放在函数体内集中定义,因此安全可靠!
var Cobra = P(Snake, function(cobra) { var age = 1; //私有属性 //这里还可以编写私有方法 cobra.glow = function() { //长大 return age++; } }); var c = new Cobra("cobra"); console.log(c.glow()); //1 console.log(c.glow()); //2 又长一岁 console.log(c.glow()); //3 又长一岁
此外,它还提供了两个类方法,mixin用于再次添加新的原型成员,open的作用同mixin,但显然它适合于重写父类的方法(在子类方法内部重用父类方法),同时,也可以添加新的私有属性。open这个命名显然是受ruby影响,意为重新打开类,修改其原型。