很久以来就想写这么一篇了,不过这个话题实在比较大,不管怎么说,谈谈我的理解吧。
js本身不是面向对象(object oriented)的语言,更流行的说法是,它是基于对象的语言(object based)
js有类和对象的概念,也有类成员、对象成员变量和方法的区别,也提供了一套继承机制,但这套继承机制比较简陋,比不上C++或是java中的继承,于是种种的修补方案都出现了。
首先说说最为“正统”的javascript继承的实现吧,是基于一个叫"prototype"的东西实现的。通过创建构造函数,可以通过this关键字指定对象变量和对象方法。通过改变构造函数的prototype的属性,可以为该类的每个实例加入一个对象方法,所以可以通过自定义prototype,来实现基于prototype的继承。另外要注意的是,prototype中的方法和变量、对象方法和变量、类方法和变量是处理类的时候需要分清的三个概念。类方法和变量是构造函数的属性,与实例化后的变量无关系,prototype的相当于继承中的对象父类构建的方法和变量。
很明显的可以看出来这种的继承机制的局限性
- 没有私有变量和公共变量的区别
- 无法将一个类写在一个构造函数中
- 无法实现多级继承和从多个父类中继承
有一些解决方案出来解决如上的问题。比如使用闭包(enclosure)的一种编程样式,可以将不能被外界访问的私有变量放入闭包中,解决第一点。但这种实现破坏了javascript本身的基于prototype的基础机制,代价比较大,js本身的instanceof将失效。
YUI中使用了大量的这种方法,并且提供了对应的基于拷贝父类方法到子类的继承机制。这种实现方法,比较适用于同时存在一个或是几个的对象的类,因为类的每个实例将复制一份所有的对象方法,占用额外的内存空间。而基于prototype的继承中,所有实例可以共享同一个prototype方法。在有成百上千个对象被实例化的应用中,效率的区别很明显。
第二点关于将一个类写入构造函数中,是很多C和java开发者的愿望,因为他们不习惯在构造函数之后再加入一堆prototype的定义语句,仿佛这样会让他们搞不清这个类的边界…… what ever, 能在一起当然是更好一点的。写在一起的方法当然是有的,不过会导致每次实例化一个对象的时候,将prototype再赋值一次,而这时不必要的。于是出现了一些解决方法,某种特定的构造函数写法,引入一个额外的判断是否已初始化的变量,ugly,嗯。
关于第三点,也有一些实现方式,但都涉及到两个个很核心的问题:
- 如何保证基于prototype的继承的时候,父类的构造函数不被执行?
- 如何存储原型链?或者说,继承的每一个方法的继承链
关于第一个问题,YUI里面有很漂亮的解决方法,通过新建一个空函数F来实现。而YUI继承的一个不足就是,完全没有保存原型链,无法调用父类方法。
其实解决方案非常多,有js库
Prototype.js的,yui的,dean edwards的
Base2,等等(dean的已经相当好了,就是太复杂。dean就是一天才)…… 另外,我自己还弄个两个(不过很差劲,就不拿出来献丑了……)
最近看到 John Resig 的一个
方法,感觉非常的好,基本满足了所有需求,而且结构简单,决定加入到自己的库里面。
特点是:
- 有私有变量
- 可以写在一个构造函数中
- 可以多级继承,可以有多个父类(需要某种特定格式)
- 继承基于prototype,且不会执行父类的构造函数
- 保持了父类的函数链,可以在子类中用this._super简单调用
- instanceof 对子类、父类都生效
确实,这是目前为止我看到的最好的方法。除了定义了一个全局变量Class外
我会改编一下这点的,呵呵
再贴一下他的实现方法吧:
// Inspired by base2 and Prototype
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
this.Class = function(){};
// Create a new Class that inherits from this class
Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if ( !initializing && this.init )
this.init.apply(this, arguments);
}
// Populate our constructed prototype object
Class.prototype = prototype;
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
})();用法:
var Person = Class.extend({
init: function(isDancing){
this.dancing = isDancing;
},
dance: function(){
return this.dancing;
}
});
var Ninja = Person.extend({
init: function(){
this._super( false );
},
dance: function(){
// Call the inherited version of dance()
return this._super();
},
swingSword: function(){
return true;
}
});
var p = new Person(true);
p.dance(); // => true
var n = new Ninja();
n.dance(); // => false
n.swingSword(); // => true
// Should all be true
p instanceof Person && p instanceof Class &&
n instanceof Ninja && n instanceof Person && n instanceof Class