对于javascript中继承的一些看法

今天在图书馆看《Ajax企业级开发》这本书时,看到它关于javascript中继承的一些见解,觉得非常不错。并且提出了几点关于在编写javascript继承封装函数的方式时候应该达到的几个目标:

  1. 避免在原型阶段调用基类的构造函数。就是在子类实现继承的时候,避免在子类中调用了基类的构造函数来实现继承基类的属性。这点考虑是并无道理的,如果基类的构造函数中有关于DOM操作的执行代码,并且修改了页面的DOM结构,这就会失去了继承的意义了。
  2. 避免子类对父类方法的全局调用。就是避免在子类中直接使用基类的名称去调用基类的方法,这样造成对基类名称的依赖性过大,扩展性和维护性不佳。
  3. 允许调用基类方法和构造函数。
  4. 不要扩展Object.prototype属性。这个主要是避免增加Object类的方法,使得Object类越来越难操纵,尽量保持它的“纯净”,把对性能的影响效果降到最低。
  5. 确保不会严重影响性能。

上面罗列的五点,非常具有参考意义。虽然是一些细节问题,可是对于企业级的应用来说,有时候可能就是问题所在。在继承方面,Dean Edwards和Douglas Crockford也做了比较详细的叙述:《http://dean.edwards.name/weblog/2006/03/base/》,《Classical Inheritance in JavaScript》,同时,John Resig也做了相关的叙述:《Simple JavaScript Inheritance》,都非常经典而且具有代表意义。

书中也根据上面的五点,给出了一个extend函数示例:

  var extend=function(subClass,baseClass){
     var F=function(){};
     F.prototype=baseClass.prototype;
     subClass.prototype=new F();

     subClass.prototype.constructor=subClass;

     subClass.base=baseClass; //本人修改,书中是baseClass.prototype
     return subClass;
  }

上面使用了一个临时类F,通过这个临时类,避免了在extend函数内部调用基类的构造函数。通过设置subClass的一个静态方法base来储存基类的引用,这样就可以在子类中调用基类的方法和构造函数,也避免了子类对基类方法的全局调用。extend内部也没有扩展Object.prototype属性。在性能方面,也没有造成什么过大的影响。看起来是比较良好的了。

但是我的问题来了,如果要实现“避免在原型阶段调用基类的构造函数”这个目标,对于在继承基类中通过this声明的属性是行不通的,比如:

var baseClass=function(name,age){
   this.name=name;
   this.age=age;
}

就像上面代码所示,如果要避免在子类中调用基类的构造函数的话,上面的name和age属性是不可能被子类继承的。

extend(subClass,baseClass);
function subClass(){};

var sub=new subClass();
alert(sub.name); // return "undefined"

要继承基类中通过this声明的属性,就必须要在子类中调用基类的构造函数:

extend(subClass,baseClass);
function subClass(name,age,grade){
  subClass.base.call(this,name,age);//调用基类的构造函数来继承属性
  this.grade=grade;
};

如果没必要通过在基类里通过this来声明属性的话,可以通过prototype在构造函数外部来实现定义属性和方法,这也是从一个原因出发的:尽量保持构造函数的简单,使得类的实例化更加快。这样也可以避免在子类中调用基类的构造函数

var baseClass=function(){};
baseClass.prototype={
   name:"supesha",
   age:25,
   getName:function(){
      return this.name;
   }
}

extend(subClass,baseClass);
function subClass(grade){
  this.grade=grade;
};
alert((new subClass("two")).name);

如果是如上所述的情况的话,不需要在基类中通过this来声明属性,那么使用foreach循环也是可以实现继承方法的,这样就可以避免使用一个临时类来做“中间人”啦,缺点就是兼容性不好,不能同时兼容使用this声明属性的情况:

var extend=function(subClass,baseClass){
   for(var key in baseClass.prototype){
	subClass.prototype[key]=baseClass.prototype[key];
   }
   subClass.base=baseClass;
   return subClass;
}

var subClass=extend(function(){},baseClass);

使用哪种方式,这个可能需要在应用的整体中去考究了。

因此,选择实现继承的方式应该遵循上面罗列的五点目标是十分必要的。实现方式多种多样,但是每一种都是在改善原来的继承方式,使得javascript继承更加的强大。

元编程的思想

2010年3月13日的那个中午,踏上火车,回到学校,回到图书馆了,离别了有段日子了,也甚是思念在图书馆的点点滴滴。

“在图书馆翻书看的时候无意中看到一个比较有意思的名词:元编程。自由百科全书的定义如下:编写一些程序来提前生成一些数据或代码供运行时使用,用来生成这些数据信息或者代码的程序称为元程序,编写这种程序就称为元编程。”

“接着它又说啦:在Javascript中实践元编程,其中所谓的元程序就是function,而供运行时使用的数据或代码就是Function或function引出的一系列扩展。因为作为一个function,既可以直接执行,有可以充分利用function代码体的上下文,如享受apply或call等方法带来的便利,而作为一个构造方法使用,则受到比较大的限制,缺少了function的灵活性。”

暂且不说它是否说的对与不对,先看它举例的代码再说:

//createConstructor
function createConstructor(fn){
     var p=fn.prototype;
     function func(){}
     func.prototype=p;
     func.prototype.constructor=p.constructor;
     //定义新对象的构造
     func.prototype.a="a";
     func.prototype.show=function(){alert(this.a);}
}

//createFunction
var createFunction=function(fn){
    var f = fn;
    var func=function(){
         return f.apply(this,arguments);
    }
    func.prototype=fn.prototype;
    return func;

上面既是原书中举的两个例子,初看起来,还比较新颖,可是测试一下发现了一个不足之处,就是创建出来的function或者构造函数,它一变,原始的function或者构造函数也都变了,这个实用性不大。我将它修改一下,就可以实现类似于C++中的基类、子类之间的继承关系了:

function createFunc(fn){
   var f = fn;
   var func = function(){
      return f.apply(this, arguments);
   }
   for (var key in f.prototpe) {   //复制
      func.prototype[key] = f.prototype[key];
   }
   return func;
}

上面修改过后的是对“基类”的复制,“子类”的修改,不会影响到“基类”。

函数化实现“伪继承机制”

步骤:
1.创建一个新对象
2.有选择性的定义私有变量和方法,就是函数中通过var声明的普通变量
3.给这个新对象扩充方法
4.返回这个新对象

原理:在继承的时候,在子类中首先获得父类定义的新对象,之后在这个新对象的基础上扩充其他的方法或者重定义父类的方法.
从而达到始终都在操作同一个对象来实现“伪继承”。

Function.prototype.method=function(name,fn){
 if(!this.prototype[name]){
  this.prototype[name]=fn;
 }
 return this;
}
var mammal = function(spec){
      var that = {}; //创建一个新对象
      //给新对象扩充方法
      that.get_name = function(){
            return spec.name;
      };
      that.says = function(){
            return spec.saying || "";
      };
      //返回这个新对象
      return that;
}

var cat=function(spec){
 spec.saying=spec.saying || meow ;

 //调用mammal方法,获得它生成的对象
 var that=mammal(spec); 

 //在mammal方法返回的对象基础上扩展方法
 that.purr=function(n){
  var i,s="";
  for(i=0;i<n;i++){
   if(s){
    s+="-";
   }
   s+="r";
  }
  return s;
 };
 //重定义get_name方法
 that.get_name=function(){
  return that.says()+" "+spec.name +" "+that.says();
 }
 //返回扩展后的对象
 return that;
}

//Object扩展方法superior,通过参数名称访问父类方法
Object.method( superior ,function(name){
 var that=this,method=that[name];
 return function(){
  return method.apply(that,arguments);
 }
});

var coolcat=function(spec){
 var that=cat(spec);
 var super_get_name=that.superior( get_name ); //获得父类的get_name方法的引用
 //重定义get_name方法
 that.get_name=function(n){
  return Like +super_get_name()+" baby,"+n;
 };
 //返回扩展后的对象
 return that;
}

var myCoolCat=coolcat({name:"Bix"});
alert(myCoolCat.get_name("supersha"));

连缀语法的方式实现继承

javascrpt里继承的方式很多种,有类式继承和原型继承(权威的解释:Prototypal Inheritance in JavaScript Classical Inheritance in JavaScript)。js没有像java那种只要通过一个extends关键字来声明继承的方式,它的实现方式是比较丑陋的,所以封装这个继承过程就显得尤为重要了。

下面我来说明一种链式封装继承的方式,这种方式需要扩展鼎鼎大名的method和inherits扩展方法。method是douglas crockford通过扩展Function类来方便声明方法从而出名。

//首先来实现method扩展方法:
Function.prototype.method=function(name,fn){
     if(!this.prototype.name){  //验证是否已经存在该方法
            this.prototype[name]=fn;
     }
     return this;
}

//接下来实现inherits扩展方法,它主要是用于封装继承:
Function.method("inherits",function(parent){
     this.prototype=new parent;
     this.prototype.constructor=this; //纠正子类constructor的指向错误
     return this;
}

(全文…)

Javascript“继承”的简单理解方式

Javascript里面没有类、继承这样的概念,但是并不代表Javascript不能够实现“继承”。Javascript是一种基于原型链的解释语言,当然也是一种面向对象的语言。既然它面向对象,就不得不需要使用OOP这样的编程方式来编写良好的Javascript代码,编写OOP方式的代码的好处就在于易封装、广重用、易维护等等。Javascript里的知识繁多,现在来简单的讲一下我对Javascript继承的理解。

首先,我们先写一个简单的“父类”Person:

var Person=function(n){
     this.name=n;
}
Person.prototype={   //这里Person.prototype给赋值了一个对象,有意思
   getName:function(){
     return this.name;
   }
}

我相信上面的代码是Front-End都能看懂的。上面的代码有意思的地方是Person.prototype给赋值了一个对象。好,下面来简单实现一个“子类”Student:

var Student=function(n,a){
     Person.call(this,n); //这里也非常有意思
     this.age=a;
}
Student.prototype=new Person(); //给Student.prototype赋值了一个Person实例对象,记住,是实例对象。
Student.prototype.getAge=function(){
     return this.age;
}

(全文…)