Js 继承:extend、mixin和plugin(一)
简介:
我们编写前端控件时,需要给控件建立一个体系,面向对象是一个很合适的方式,但是JS本身对面向对象的一些概念支持偏弱,特别是继承的特性方面,那么我们就必须通过一系列的方式来实现继承。
Extend方式:
Extend方式非常贴近面向对象语言中的类继承,这种方式使用原型链的方式来实现继承。原型链的继承方式有几个缺点:
1)缺少针对父类的引用,例如:
function A(){
//初始化A操作
}
A.prototype = {
method1:function(){
//A的一系列操作
}
}
function B(){
//初始化B的操作
}
B.prototype = new A();
B.method1 = function(){
//先执行 A.method1的方法
//执行自己的方法
}
那么此时如何在调用A的method1方法呢?我们没有java中的’super’对象也缺少C#中的’base’对象,当然我们可以换一种写法,来解决这个问题:
var aobj = new A();
B.prototype = aobj;
B.method1 = function(){
aobj.method1.call(this);//先执行 A.method1的方法
//执行自己的方法
}
但是我们必须能缓存刚才的 aobj对象,方便在调用B.method1时取得到,所以我们就引入’superclass’字段,放在构造函数B上作为静态属性,调用时:B.superclass.method1.call(this),下面是实现:
function extend(subclass,superclass){
var superObj = new superclass();
subclass.prototype = superObj;
subclass.superclass = superObj;
return subclass;
}
上面的函数解决了superclass的问题但是又引入了新的问题,下面我们一一来讲。
2)破坏了对象的constructor属性,这个属性在使用继承的过程中非常有用,它指向的是对象的构造函数,例如:
function A(){}
//未继承 A时
function B(){}
var b = new B();
alert(b.constructor === B) //true
//B继承A
extend(B,A);
b1 = new B();
alert(b1.constructor === B) // false
alert(b1.constructor === b.constructor) //false
这样的结果绝对不是我们需要的,否则我们在编写方法的过程中调用,B.supercalss.constructor.call(this) 或者 B.superclass.constructor.superclass 会出现混乱,我们怎么办呢,继续改进extend方法,矫正constructor 属性:
function extend(subclass,superclass){
var superObj = new superclass();
subclass.prototype = superObj;
superObj.constructor = subclass;//矫正 constructor属性
subclass.superclass = superObj;
return subclass;
}
到这里看似解决了继承链的问题,但是此时我们来看一下下面情形:
function C (){}
extend(C,B);
var c = new C();
alert(c.constructor.superclass.constructor);//C
上面的结果意外的不是B 而是 C,这是什么原因呢,我们看刚才矫正constructor 属性以及上面一句:
subclass.prototype = superObj;
superObj.constructor = subclass;
我们将C的prototype的constructor修改为了C ,由于subclass.superclass = superObj;那么C的superclass跟C 的prototype是同一个对象,那么 c.constructor.superclass.constructor等同于C.prototype.constructor 结果就是 C,那么我们需要做一下修改:
function extend(subclass,superclass){
var superObj = new superclass();
subclass.prototype = superObj;
superObj.constructor = subclass;//矫正 constructor属性
subclass.superclass = new superclass();
return subclass;
}
此时我们再来调用一下:
alert(c.constructor.superclass.constructor);//B
此时的还有什么问题呢?我们来做一下实验:
function A (config){
console.log('a constructor,config:' + config);
}
A.prototype = {
method : function(){
console.log('a runing');
}
};
function B (config){
this.constructor.superclass.constructor.call(this,config);
console.log('b constructor,config:' + config);
}
extend(B,A);
function C (config){
this.constructor.superclass.constructor.call(this,config);
console.log('c constructor,config:' + config);
}
extend(C,B);
var c = new C('hello');
c.method();
输出结果:
a constructor,config:undefined
a constructor,config:undefined
b constructor,config:undefined
b constructor,config:undefined
b constructor,config:undefined
Uncaught RangeError: Maximum call stack size exceeded
我们看到没有输出我们希望的结果,这里有2个问题,
1. 产生了循环调用
2. 未实例化对象前,实例化了多个父类对象,而且这些实例初始化时传入的参数为空,极易出现错误。
解决死循环我们只需要把this.constructor.superclass 替换为 对应的 B.superclass即可:
function B (config){
B.superclass.constructor.call(this,config);
console.log('b constructor,config:' + config);
}
function C (config){
C.superclass.constructor.call(this,config);
console.log('c constructor,config:' + config);
}
针对调用extend时生成多个实例我们引入方法:
function create(proto, c) {
function F() {
}
F.prototype = proto;
var o = new F();
o.constructor = c;
return o;
}
在extend方法中我们这样调用:
var superObj = create(superclass.prototype,subclass);
subclass.prototype = superObj;
subclass.superclass = create(superclass.prototype,superclass);
此时我们的prototype 属性和superclass属性都不需要实例化对象,完整的extend方法如下:
function extend(subclass,superclass){
function create(proto, c) {
function F() {
}
F.prototype = proto;
var o = new F();
o.constructor = c;
return o;
}
var superObj = create(superclass.prototype,subclass);
subclass.prototype = superObj;
subclass.superclass = create(superclass.prototype,superclass);
return subclass;
}
这样在执行上面的代码即得到:
a constructor,config:hello
b constructor,config:hello
c constructor,config:hello
a runing
今天先写到这里,接下来我会把控件的继承机制一下,如果有时间,把一些编写js控件的经验分享出来,希望对大家有帮助。