想要学好一种语言,一定要了解其语言特性,因为我们 not just coding,js本身就是一种函数式的编程语言,它不与java或者其他面向对象一样拥有继承机制,但是我们可以利用其留下的prototype库进行曲线救国,这就不能不找一段经典的继承代码来研究一下了。以下是John Resig实现的经典继承实现代码,
// 自执行的匿名函数创建一个上下文,避免引入全局变量
(function() {
// initializing变量用来标示当前是否处于类的创建阶段,
var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
// 基类构造函数
// 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.Class
this.Class = function() { };
// 继承方法定义
Class.extend = function(prop) {
var _super = this.prototype;
// 通过将子类的原型指向父类的一个实例对象来完成继承
// - 注意:this是基类构造函数(即是Class)
initializing = true;
var prototype = new this();
initializing = false;
for (var name in prop) {
prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" ?
(function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
return ret;
};
})(name, prop[name]) :
prop[name];
}
function Class() {
// 在类的实例化时,调用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
// 子类的prototype指向父类的实例(完成继承的关键)
Class.prototype = prototype;
// 修正constructor指向错误
Class.constructor = Class;
// 子类自动获取extend方法,arguments.callee指向当前正在执行的函数
Class.extend = arguments.callee;
return Class;
};
})();
var Person = Class.extend({
name:null,
init:function(){
alert("init person");
},
alerting:function(){
alert("Person");
}
})
var Student = Person.extend({
init:function(){
this._super();
alert("init");
},
alerting:function(){
this._super();
alert("Student");
}
})
var p = new Student();
p.alerting();
首先通过创建一个自执行函数避免引入全局变量,主要来分析一下Class.extend这个方法,该方法中首先将父类(也就是Class)这个类的原型prototype属性赋给_super这个变量,initializing主要是用于控制父类实例化时,prototype = new this()时,防止进入if条件,也就是这段代码,
if (!initializing && this.init)
this.init.apply(this, arguments);
是否执行接下来声明一个prototype的变量,指向父类的一个实例,prototype = new this(),这句代码其实是将prototype的__proto__属性指向了父类的原型,接下来将initializing这个变量=false,是为了志明自执行方法中的return的Class这个类在后期实例化时进入if条件,例如Person就可以。相信大家都注意到了for循环中的代码,这就是继承机制的核心,看这个条件语句 typeof prop[name] == "function" && typeof _super[name] == "function" ,是说明如果prop对象中的name属性是一个function并且父类中存在同名的属性并且同样是function,那么执行下面这段代码:
(function(name, fn) {
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
return ret;
};
})(name, prop[name])
这段代码是什么意思呢,这里面其实做了两件事情,首先将父类的同名方法赋给了this绑定的_super属性,然后就是将子类的方法绑定到this作用域执行,我们回过头来看看,这段代码是赋给了prototype[name],也就是子类的该同名方法,该方法必定是由定义该方法类的实例调用,因此this作用域指向了目标类的实例,此时,this作用域中还有一个_super属性,该属性此时需要注意的是该属性不是不变的,举例来说,var p = new Student()这个实例,当实例p调用init()函数时,this._super指向了Person的init(),当p调用alerting()时,this._super指向了Person的alerting(),当我们调用p.alerting()的时候,执行this._super()其实就是调用父类的同名方法,若typeof prop[name] == "function" && typeof _super[name] == "function"不为true的时候,那么prototype[name] = proto[name],这其中还有一点点不好理解,如上代码我改动一点点,将Person类中的alerting方法变成一个属性,那么这个条件也不成立,就执行prototype[‘alerting’]=proto['alerting'],这就有问题了,之前我们在prototype=new this()的时候,prototype的__proto__属性指向了父类的prototype,说明其中必定有父类中的alerting属性,这下prototype又有了一个alerting属性,那么我们的对象p在调用p.alerting()的时候调用的到底是alerting方法还是alerting属性呢,其实还是alerting()方法,因为首先对象p会在第一个__proto__属性中找alerting()方法,若找不到,则在下一层中寻找,若找到了该方法,就不会进入下一层的__proto__属性中继续寻找了,所以这里不会找到从父类继承下来的alerting属性,调用的只是自己的方法。到此为止,其实只是实现了将父类的方法属性继承到了我们自定义的局部变量prototype属性中,后面我们定义的Class方法,该方法不同于之前的Class方法,
function Class() {
// 在类的实例化时,调用原型方法init
if (!initializing && this.init)
this.init.apply(this, arguments);
}
我们将Class的prototype指向了我们定义的prototype变量,其实就是继承了父类的方法和属性,此时Class.constructor指向了prototype变量的构造函数,所以我们显式地改回来,Class.constructor=Class,并且使用Class.extend=arguments.callee将Class.extend方法指向了外层的Class.extend方法,方便我们进行深度扩展,最后将Class返回。
这个意思就是无论是Person或者是Student类,本质上都是Class.extend方法内部定义的Class方法(也就是类),只是我们将父类(外层的Class,也就是调用extend方法的类)和子类(也就是prop对象)的属性揉到了内部的Class类中,最后将Class返回给我们外部,就实现了继承了。
以上就是该继承机制的详解,不知道讲清楚了没有,若有疑问或者有不对之处,敬请指正。