javascript面向对象包装类Class封装类库剖析

  javascript是个入门门槛很低的语言,甚至一个从来没有接触过javascript的技术人员,几小时内就可以写出一个简单有用的程序代码。

  但是如果因此你就下结论:javascript是门简单的语言。那你就大错特错了。想写出高性能的代码,同样需要具备一个高级程序员的基本素养。

  一个java或者c++程序员,不一定能写出高性能的javascript代码,但更容易写出高性能的javascript代码。

  javascript的简单是基于它“胸襟广阔”的包容性。它声明时,不需要指定类型,甚至可以任意的转换类型。它面向对象,却没有类(Class)的限制。它是一门崇尚自由又非常严谨的语言,如果你是一个自由主义者,那么,拥抱javascript吧!

  面向对象编程(OOP)是一种流行的编程方法。但javascript的OOP,较之JAVA、c++有很大的同,主要体现它的继承方式不同。javascript是基于原型PROTOTYPE继承的。所有对象都是基于原型链,最终追述到Object对象。

  这里不想讨论过多的关于javascript的继承方式和其它语言的继承方式的不同之处。主要讨论如何封装javascript的Class,以便更好的管理和维护基础代码,减少重复代码,以及更好的模块化编程。

  下面是几个github上找到的比较好的Class封装类库:

  一、MY-CLASS

  项目地址:https://github.com/jiem/my-class

  先看基本用法:

  a、新建一个类

  

复制代码 代码如下:

  (function(){

  //新建类

  varPerson=my.Class({

  //添加静态方法

  STATIC:{

  AGE_OF_MAJORITY:18

  },

  //构造函数

  constructor:function(name,age){

  this.name=name;

  this.age=age;

  },

  //实例方法

  sayHello:function(){

  console.log('Hellofrom'+this.name+'!');

  },

  //实例方法

  drinkAlcohol:function(){

  this.age<Person.AGE_OF_MAJORITY?

  console.log('Tooyoung!Drinkmilkinstead!'):

  console.log('Whiskeyorbeer?');

  }

  });

  //暴露给命名空间

  myLib.Person=Person;

  })();

  varjohn=newmyLib.Person('John',16);

  john.sayHello();//log"HellofromJohn!"

  john.drinkAlcohol();//log"Tooyoung!Drinkmilkinstead!"

  b、继承一个类

  

复制代码 代码如下:

  (function(){

  //Dreamer继承Person

  varDreamer=my.Class(Person,{

  //构造方法

  constructor:function(name,age,dream){

  Dreamer.Super.call(this,name,age);

  this.dream=dream;

  },

  //实例方法

  sayHello:function(){

  superSayHello.call(this);

  console.log('Idreamof'+this.dream+'!');

  },

  //实例方法

  wakeUp:function(){

  console.log('Wakeup!');

  }

  });

  //Super访问父类

  varsuperSayHello=Dreamer.Super.prototype.sayHello;

  //暴露给全局命名空间

  myLib.Dreamer=Dreamer;

  })();

  varsylvester=newmyLib.Dreamer('Sylvester',30,'eatingTweety');

  sylvester.sayHello();//log"HellofromSylvester!IdreamofeatingTweety!"

  sylvester.wakeUp();//log"Wakeup!"

  c、给类添加新方法

  

复制代码 代码如下:

  //给myLib.Dreamer添加新方法

  my.extendClass(myLib.Dreamer,{

  //添加静态方法

  STATIC:{

  s_dongSomeThing:function(){

  console.log("dosomething!");

  }

  },

  //添加实例方法

  touchTheSky:function(){

  console.log('Touchingthesky');

  },

  //添加实例方法

  reachTheStars:function(){

  console.log('Sheissopretty!');

  }

  });

  d、实现一个类的方法

  

复制代码 代码如下:

  //声明一个新类

  myLib.ImaginaryTraveler=my.Class({

  travel:function(){console.log('Travelingonacarpet!');},

  crossOceans:function(){console.log('SayinghitoMobyDick!');}

  });

  (function(){

  //Dreamer继承Person实现ImaginaryTraveler的方法

  varDreamer=my.Class(Person,ImaginaryTraveler,{

  //构造方法

  constructor:function(name,age,dream){

  Dreamer.Super.call(this,name,age);

  this.dream=dream;

  }

  //...

  });

  //暴露给全局命名空间

  myLib.Dreamer=Dreamer;

  })();

  varaladdin=newDreamer('Aladdin');

  aladdininstanceofPerson;//true

  aladdininstanceofImaginaryTraveler;//false

  aladdin.travel();

  aladdin.wakeUp();

  aladdin.sayHello();

  如果怕忘记new操作符

  

复制代码 代码如下:

  varPerson=my.Class({

  //youcannowcalltheconstructorwithorwithoutnew

  constructor:function(name,city){

  if(!(thisinstanceofPerson))

  returnnewPerson(name,city);

  this.name=name;

  this.city=citye;

  }

  });

  下面看一下my.class的源代码解析:

  my.Class实现思路基本是这样的,如果只有一个参数,那么声明的是一个基础类,这个参数是用来声明新类的方法和属以及构造函数。它不是继承而来,但它可以被继承。

  继承的思路,就是如果有两个参数,第一个参数做为父类被继承,第二参数用来声明新类的方法和属性以及构造函数,它同样可以被继承。

  如果有三个以上参数那么,除出第一个参数做为继承的父类,最后一个参数用声明新类的方法和属性以及构造函数。中间的参数是用类来扩展新类的方法。当然也可以通过my.extendClass扩展新方法。

  同时,类库为commonJS和浏览环境都提供了支持!

  

复制代码 代码如下:

  /*globalsdefine:true,window:true,module:true*/

  (function(){

  //Namespaceobject

  varmy={};

  //保证AMD分模块可用

  if(typeofdefine!=='undefined')

  define([],function(){

  returnmy;

  });

  elseif(typeofwindow!=='undefined')

  //保证客户端可用

  window.my=my;

  else

  //保证后台可用

  module.exports=my;

  //============================================================================

  //@methodmy.Class

  //@paramsbody:Object

  //@paramsSuperClass:function,ImplementClasses:function...,body:Object

  //@returnfunction

  my.Class=function(){

  varlen=arguments.length;

  varbody=arguments[len-1];//最后一个参数是指定本身的方法

  varSuperClass=len>1?arguments[0]:null;//第一个参数是指继承的方法,实例和静态部分均继承

  varhasImplementClasses=len>2;//如果有第三个参数,那么第二个就是implementClass,这里其实只继承实例对象

  varClass,SuperClassEmpty;

  //保证构造方法

  if(body.constructor===Object){

  Class=function(){};

  }else{

  Class=body.constructor;

  //保证后面不覆盖constructor

  deletebody.constructor;

  }

  //处理superClass部分

  if(SuperClass){

  //中间件实现实例属性的继承

  SuperClassEmpty=function(){};

  SuperClassEmpty.prototype=SuperClass.prototype;

  Class.prototype=newSuperClassEmpty();//原型继承,解除引用

  Class.prototype.constructor=Class;//保证constructor

  Class.Super=SuperClass;//父对象访问接口

  //静态方法继承,重载superClass方法

  extend(Class,SuperClass,false);

  }

  //处理ImplementClass部分,其实只继承实例属性部分,除SuperClass#arguments[0]#和body#arguments[length-1]#

  if(hasImplementClasses)

  for(vari=1;i<len-1;i++)

  //implement是继承的实例属性部分,重载父对象implementClass方法

  extend(Class.prototype,arguments[i].prototype,false);

  //处理本身声明body部分,静态要STATIC指定,实例部分要删除STATIC部分

  extendClass(Class,body);

  returnClass;

  };

  //============================================================================

  //@methodmy.extendClass

  //@paramsClass:function,extension:Object,?override:boolean=true

  varextendClass=my.extendClass=function(Class,extension,override){

  //静态部分继承静态部分

  if(extension.STATIC){

  extend(Class,extension.STATIC,override);

  //保证实例部分不继承静态方法

  deleteextension.STATIC;

  }

  //实例属性继继承实例部

  extend(Class.prototype,extension,override);

  };

  //============================================================================

  varextend=function(obj,extension,override){

  varprop;

  //其实这里的flase是表明,覆盖父对象的方法

  if(override===false){

  for(propinextension)

  if(!(propinobj))

  obj[prop]=extension[prop];

  }else{

  //这里其实不覆盖父对象的方法,包括toString

  for(propinextension)

  obj[prop]=extension[prop];

  if(extension.toString!==Object.prototype.toString)

  obj.toString=extension.toString;

  }

  };

  })();

  二、KLASS

  项目地址:https://github.com/ded/klass

  先看使用方法:

  a、新建一个类

  

复制代码 代码如下:

  //声明一个类

  varPerson=klass(function(name){

  this.name=name

  })

  .statics({//静态方法

  head:':)',

  feet:'_|_'

  })

  .methods({//实例方法

  walk:function(){}

  })

  b、继承一个类

  

复制代码 代码如下:

  //SuperHuman继承Person

  varSuperHuman=Person.extend(function(name){

  //自动调用父类的构造方法

  })

  .methods({

  walk:function(){

  //显式声明调用父类的walk方法

  this.supr()

  this.fly()

  },

  fly:function(){}

  })

  newSuperHuman('Zelda').walk()

  c、字面量方式声明一个类

  

复制代码 代码如下:

  varFoo=klass({

  foo:0,

  initialize:function(){

  this.foo=1

  },

  getFoo:function(){

  returnthis.foo

  },

  setFoo:function(x){

  this.foo=x

  returnthis.getFoo()

  }

  })

  d、实现一个类的方法

  因为有时候你可能希望覆写或者混合一个实例方法,可以这样:

  

复制代码 代码如下:

  //可以传递一个字面量去继承

  varAlien=SuperHuman.extend({

  beam:function(){

  this.supr()

  //beamintospace

  }

  })

  varSpazoid=newAlien('Zoopo')

  if(beamIsDown){

  //覆写beam方法

  Spazoid.implement({

  beam:function(){

  this.supr()

  //fallbacktojets

  this.jets()

  }

  })

  }

  下面看一下klass源代码解析

  klass的基本设计思路很明确,极力的模仿其它语言的继承方式。比如:子类构造方法调用父类的构造方法,还可以显式的声明调用父类的方法。

  这种判断都是基于正则匹配:fnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/;关键字"super"

  如果显示的声明了要调用父类的方法,那么声明方法的时候,就包装成一个内部调用父类方法且返回相同值的函数,给当前类的方法。

  另一方面,构造方法,也是比较灵活的。如果显示的声明了initialize,那么这就是构造方法。否则如果参数是个function那么它就做为构造方法,否则就用父类的构造方法。

  通过statics方式添加静态方法,通过实例的implements和静态方法methods添加实例方法。

  通过父类的extend实现继承。

  同时,类库为commonJS和浏览环境都提供了支持!

  

复制代码 代码如下:

  /**

  *Klass.js-copyright@dedfat

  *version1.0

  *https://github.com/ded/klass

  *Followoursoftwarehttp://twitter.com/dedfat:)

  *MITLicense

  */

  !function(context,f){

  //fnTest用来验证是否可能通过正则找出调用super父类方法的方法

  varfnTest=/xyz/.test(function(){xyz;})?/\bsupr\b/:/.*/,

  noop=function(){},

  proto='prototype',

  isFn=function(o){

  returntypeofo===f;

  };

  //基础类

  functionklass(o){

  returnextend.call(typeofo==f?o:noop,o,1);

  }

  //包装成一个借用super同名方法的函数

  functionwrap(k,fn,supr){

  returnfunction(){

  //缓存原this.super

  vartmp=this.supr;

  //暂把this.super改造成借用super的同名方法above

  //供o里显式的声明(fnTest.text(fn)==true)要借用super的同名方法使用

  this.supr=supr[proto][k];

  //借用执行并保存返回值

  varret=fn.apply(this,arguments);

  //恢复原this.super

  this.supr=tmp;

  //返回返回值,保证wrap后的返回值跟原来一致

  returnret;

  };

  }

  //如果o和super有同名方法,且o显式声明借用super的同名方法,就wrap成一个待执行函数供使用

  //如果没有显式的声明借用super的同名方法,或者是o独有的方法,或者不是方法就直接用

  functionprocess(what,o,supr){

  for(varkino){

  //如果是非继承方法,按方法注释规则执行,最终都放进what

  if(o.hasOwnProperty(k)){

  what[k]=typeofo[k]==f

  &&typeofsupr[proto][k]==f

  &&fnTest.test(o[k])

  ?wrap(k,o[k],supr):o[k];

  }

  }

  }

  //继承方法的实现,fromSub是用来控制是否继承而来,上面的klass里面fromSub是1,表明非继承而来,构造函数不借用super执行

  functionextend(o,fromSub){

  //noop做为媒介类实现原型继承的解除引用

  noop[proto]=this[proto];

  varsupr=this,

  prototype=newnoop(),//创建实例对象供原型继承使用,解除引用

  isFunction=typeofo==f,

  _constructor=isFunction?o:this,//如果o是一个构造方法就用,否则由this来决定构造函数

  _methods=isFunction?{}:o,//如果o是一个{...}应该用methods放到fn原型里,如果里面有initialize就是构造函数,如果o是函数就由上面_constructor决定o是构造函数

  fn=function(){//因为kclass借助了kclass,所以最终实际上返回的就是fn,fn其实就新类的构造函数

  //1如果o是{...}就会被methods直接过滤并添加到fn的原型里,如果o里面有initialize,那么fn的原型里就有initialize,那么它就是构造方法

  //2如果o是function,methods什么也添加不到fn的原型里,但是_constructor会接受o当构造函数

  //3如果o是{....},同时里面也没有initialize,那么就是this当构造函数,如果在klass里由call决定,显然构造函数是noop,如果在非基础类里,构造函数就是父类的构造函数

  //由于o不是函数不会自动调用父类的构造函数,只是把父类的构造函数当做当前类的构造函数----这都是由于this的指向决定的

  console.log(this);

  if(this.initialize){

  this.initialize.apply(this,arguments);

  }else{

  //调用父类构造方法

  //如上面3,o不是函数,不会调用父类的构造方法

  //基础类无父类,不会调用父类构造方法

  fromSub||isFn(o)&&supr.apply(this,arguments);

  //调用本类构造方法

  //参考上面2,3要么是noop要么是o

  console.log(_constructor==noop);

  _constructor.apply(this,arguments);

  }

  };

  //构造原型方法的接口

  fn.methods=function(o){

  process(prototype,o,supr);

  fn[proto]=prototype;

  returnthis;

  };

  //执行实现新类原型,保证新类的constructor

  fn.methods.call(fn,_methods).prototype.constructor=fn;

  //保证新类可以被继承

  fn.extend=arguments.callee;

  //添加实例方法或者静态方法,statics:静态方法,implement实例方法

  fn[proto].implement=fn.statics=function(o,optFn){

  //保证o是一个object对象,如果o是一个字符串,那么就是添一个方法的情况,如果o是一个object对象说明是批量添加的

  //因为要从o里面拷贝

  o=typeofo=='string'?(function(){

  varobj={};

  obj[o]=optFn;

  returnobj;

  }()):o;

  //添加实例方法或者静态方法,statics:静态方法,implement实例方法

  process(this,o,supr);

  returnthis;

  };

  returnfn;

  }

  //后台用,nodejs

  if(typeofmodule!=='undefined'&&module.exports){

  module.exports=klass;

  }else{

  varold=context.klass;

  //防冲突

  klass.noConflict=function(){

  context.klass=old;

  returnthis;

  };

  //前台浏览器用

  //window.kclass=kclass;

  context.klass=klass;

  }

  }(this,'function');

  三、还有一种简单实现

  实现思路很简单,就是利用ECMAScript5原型式继承Object.create方法,封装成一个方法,如果不支持ECMAScript5的环境,就平移退化到

  

复制代码 代码如下:

  functionF(){};

  F.prototype=superCtor.prototype;

  ctor.prototype=newF();

  ctor.prototype.constructor=ctor;

  同样的,除最后一个参数是当前类的方法声明,其它参数均做为继承父类,需要循环继承,但当这里处理的相对比较简单,没涉及到覆盖。你可以自己动手添加。

  

复制代码 代码如下:

  varClass=(function(){

  /**

  *Inheritsfunction.(node.js)

  *

  *@paramctorsubclass'sconstructor.

  *@paramsuperctorsuperclass'sconstructor.

  */

  varinherits=function(ctor,superCtor){

  //显式的指定父类

  ctor.super_=superCtor;

  //ECMAScript5原型式继承并解除引用

  if(Object.create){

  ctor.prototype=Object.create(superCtor.prototype,{

  constructor:{

  value:ctor,

  enumerable:false,

  writable:true,

  configurable:true

  }

  });

  }else{

  //无Object.create方法的平稳退化

  functionF(){};

  F.prototype=superCtor.prototype;

  ctor.prototype=newF();

  ctor.prototype.constructor=ctor;

  }

  };

  /**

  *Classfunction.

  */

  returnfunction(){

  //最后一个参数是新类方法、属性和构造函数声明

  varsubClazz=arguments[arguments.length-1]||function(){};

  //initialize是构造函数,否构造函数就是一个空函数

  varfn=subClazz.initialize==null?function(){}:subClazz.initialize;

  //继承除最一个参数以的类,多继承,也可以用作扩展方法

  for(varindex=0;index<arguments.length-1;index++){

  inherits(fn,arguments[index]);

  }

  //实现新类的方法

  for(varpropinsubClazz){

  if(prop=="initialize"){

  continue;

  }

  fn.prototype[prop]=subClazz[prop];

  }

  returnfn;

  }

  })();

  看下面实例:

  

复制代码 代码如下:

  /**

  *ThedefinitionofCatClass.

  */

  varCat=Class({

  /**

  *Constructor.

  *

  *@paramnameCat'sname

  */

  initialize:function(name){

  this.name=name;

  },

  /**

  *Eatfunction.

  */

  eat:function(){

  alert(this.name+"iseatingfish.");

  }

  });

  /**

  *ThedefinitionofBlackCatClass.

  */

  varBlackCat=Class(Cat,{

  /**

  *Constructor.

  *

  *@paramnameCat'sname.

  *@paramageCat'sage.

  */

  initialize:function(name,age){

  //calltheconstructorofsuperclass.

  BlackCat.super_.call(this,name);

  this.age=age;

  },

  /**

  *Eatfunction.

  */

  eat:function(){

  alert(this.name+"("+this.age+")iseatingdog.");

  }

  });

  /**

  *ThedefinitionofBlackFatCatClass.

  */

  varBlackFatCat=Class(BlackCat,{

  /**

  *Constructor.

  *

  *@paramnameCat'sname.

  *@paramageCat'sage.

  *@paramweightCat'sweight.

  */

  initialize:function(name,age,weight){

  //calltheconstructorofsuperclass.

  BlackFatCat.super_.call(this,name,age);

  this.weight=weight;

  },

  /**

  *Eatfunction.

  */

  eat:function(){

  alert(this.name+"("+this.age+")iseatingdog.Myweight:"+this.weight);

  }

  });

  /**

  *ThedefinitionofDogClass.

  */

  varDog=Class({});

  varcat=newBlackFatCat("John",24,"100kg");

  cat.eat();

  //true

  alert(catinstanceofCat);

  //true

  alert(catinstanceofBlackCat);

  //true

  alert(catinstanceofBlackFatCat);

  //true

  alert(cat.constructor===BlackFatCat);

  //false

  alert(catinstanceofDog);

  四、mootools类库的Class

  源码解析可以看这里:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html

  看具体用法:

  a、新建一个类

  

复制代码 代码如下:

  varCat=newClass({

  initialize:function(name){

  this.name=name;

  }

  });

  varmyCat=newCat('Micia');

  alert(myCat.name);//alerts'Micia'

  varCow=newClass({

  initialize:function(){

  alert('moooo');

  }

  });

  b、继承的实现

  

复制代码 代码如下:

  varAnimal=newClass({

  initialize:function(age){

  this.age=age;

  }

  });

  varCat=newClass({

  Extends:Animal,

  initialize:function(name,age){

  this.parent(age);//callsinitalizemethodofAnimalclass

  this.name=name;

  }

  });

  varmyCat=newCat('Micia',20);

  alert(myCat.name);//alerts'Micia'.

  alert(myCat.age);//alerts20.

  c、扩充类的实现

  

复制代码 代码如下:

  varAnimal=newClass({

  initialize:function(age){

  this.age=age;

  }

  });

  varCat=newClass({

  Implements:Animal,

  setName:function(name){

  this.name=name

  }

  });

  varmyAnimal=newCat(20);

  myAnimal.setName('Micia');

  alert(myAnimal.name);//alerts'Micia'.

  五、悟透javascript:语法甘露

  先看用法实例

  a、创建类

  

复制代码 代码如下:

  //创建类Person

  varPerson=Class(object,{

  Create:function(name,age){

  this.name=name;

  this.age=age;

  },

  SayHello:function(){

  alert("Hello,I'm"+this.name+","+this.age+"yearsold.");

  }

  });

  varBillGates=New(Person,["BillGates",53]);

  BillGates.SayHello();

  b、继承类

  

复制代码 代码如下:

  //Employee继承Person

  varEmployee=Class(Person,{

  Create:function(name,age,salary){

  Person.Create.call(this,name,age);

  //调用基类的构造函数

  this.salary=salary;

  },

  ShowMeTheMoney:function(){

  alert(this.name+"$"+this.salary);

  }

  });

  varSteveJobs=New(Employee,["SteveJobs",53,1234]);

  SteveJobs.SayHello();

  SteveJobs.ShowMeTheMoney();

  下面是源码分析:显然,多了一个New方法,创建类和新建类的实例都被巧妙的封装了。形成了一个有意义的整体!还有一点不同的地方,所有的类都基于字面量,而不是基于函数。代码很简短,但其中原理却很丰富也很巧妙,可以细细品味一番!

  

复制代码 代码如下:

  //创建类的函数,用于声明类及继承关系

  functionClass(aBaseClass,aClassDefine){

  //创建类的临时函数壳

  functionclass_(){

  this.Type=aBaseClass;

  //我们给每一个类约定一个Type属性,引用其继承的类

  for(varmemberinaClassDefine)

  this[member]=aClassDefine[member];

  //复制类的全部定义到当前创建的类

  };

  class_.prototype=aBaseClass;

  returnnewclass_();

  };

  //创建对象的函数,用于任意类的对象创建

  functionNew(aClass,aParams){

  //创建对象的临时函数壳

  functionnew_(){

  this.Type=aClass;

  //我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类

  if(aClass.Create)

  aClass.Create.apply(this,aParams);

  //我们约定所有类的构造函数都叫Create,这和DELPHI比较相似

  };

  new_.prototype=aClass;

  returnnewnew_();

  };

  由于写的比较笼统,可能有很多地方没有解析到,也可能有不准确的地方,还望指正。

  看完上面几种解析,相信息自己也可以写出一个自己的封装类库出来,至于,怎么实现看个人喜好了。但基本的思都是一样的基于原型的继承方式和循环拷贝新方法。

  原文来自:穆乙 http://www.cnblogs.com/pigtail/