面向对象语言有两种继承方式:接口继承(只继承方法名);实现继承(继承实际的方法)。但在ECMAScript中,函数名没多大含义,只是函数体的引用而已,因此,ECMAScript无法实现接口继承,只支持实现继承。实现继承,主要是依靠原型链来完成的。
2.构造函数、原型、实例之间的关系
(1)每个构造函数都有一个原型对象,Func.prototype指向了这个原型对象。
(2)原型对象都包含一个指向构造函数的指针,Func.prototype.constructor等于Func,可以理解为prototype和constructor是两个方向相反的指针,在构造函数和原型之间搭建起桥梁。
(3)每个实例,都包含一个指向原型对象的内部指针__proto__,当然这个对开发人员是不可见的。
(4)如果把构造函数A的原型,设成构造函数B的一个实例,那么构造函数A的原型里就包含了指向另一个原型的指针,从而形成了原型链。这样就实现了继承。使用对象的属性或方法时,解析器先搜索实例内部,找不到就搜索实例的原型内部,再找不到就搜索实例的原型的构造函数的原型内部,一级一级向上查找。
(5)原型链的最顶端是Object,这就是为什么所有引用类型instanceof Object都会返回true。换句话说,只要instanceof后面的构造函数在实例的原型链中,都会返回true。
(6)注意,由于继承的时候,原型被重写了,所以tom的constructor指像的不再是Male,而是Male.prototype的构造函数Human。
3.使用原型链做继承的问题。
(1)和原型模式创建对象一样,使用原型链做继承,也会遇到多个实例共享object(引用类型)属性的情况。这也就是为啥引用类型属性要放在构造函数内部。
(2)更严重一个问题是,使用原型链做继承时,必须用基类的实例去替换派生类的原型,产生基类的实例的过程是不能使用参数的,这就大大降低了继承的灵活性。
2.当然,构造时不传递参数用处还是不大,幸好apply和call都是支持传参的。
3.借用构造函数解决了引用类型属性的问题,但没有解决方法的问题。
一、原型链
1.原型链的基本思想是:利用原型让一个引用类型继承另一个引用类型的属性和方法。2.构造函数、原型、实例之间的关系
(1)每个构造函数都有一个原型对象,Func.prototype指向了这个原型对象。
(2)原型对象都包含一个指向构造函数的指针,Func.prototype.constructor等于Func,可以理解为prototype和constructor是两个方向相反的指针,在构造函数和原型之间搭建起桥梁。
(3)每个实例,都包含一个指向原型对象的内部指针__proto__,当然这个对开发人员是不可见的。
(4)如果把构造函数A的原型,设成构造函数B的一个实例,那么构造函数A的原型里就包含了指向另一个原型的指针,从而形成了原型链。这样就实现了继承。使用对象的属性或方法时,解析器先搜索实例内部,找不到就搜索实例的原型内部,再找不到就搜索实例的原型的构造函数的原型内部,一级一级向上查找。
//小实验
function Human(){
}
Human.prototype.getSex=function(){return this.sex;}
Human.prototype.setSex=function(v){this.sex=v;}
function Male(){
this.setSex("male");
}
Male.prototype=new Human();
Male.prototype.getAge=function(){return this.age;}
Male.prototype.setAge=function(v){this.age=v;}
var tom=new Male();
tom.setAge(20);
alert(tom.getSex());//male
alert(tom.getAge());//20
alert(tom instanceof Male);//true
alert(tom instanceof Human);//true
alert(tom instanceof Object);//true
alert(tom.constructor == Male);//false
alert(tom.constructor == Human);//true
(6)注意,由于继承的时候,原型被重写了,所以tom的constructor指像的不再是Male,而是Male.prototype的构造函数Human。
3.使用原型链做继承的问题。
(1)和原型模式创建对象一样,使用原型链做继承,也会遇到多个实例共享object(引用类型)属性的情况。这也就是为啥引用类型属性要放在构造函数内部。
(2)更严重一个问题是,使用原型链做继承时,必须用基类的实例去替换派生类的原型,产生基类的实例的过程是不能使用参数的,这就大大降低了继承的灵活性。
二、借用构造函数
1.为了解决原型链做继承遇到的第一个问题,出现了借用构造函数的方法。思想是把基类的构造函数当作普通函数执行一次,但执行的时候,必须把派生类的执行空间传过去。
//小实验
function Human(){
this.sex="Please input";
this.age=-1;
}
function Male(){
Human.call(this);//显示地把基类构造函数当作普通函数调用。
}
Male.prototype=new Human();
var tom=new Male();
var jim=new Male();
tom.sex="male";
alert(tom.sex);//male
alert(jim.sex);//Please input
//小实验
function Human(sex,age){
this.sex=sex;
this.age=age;
}
function Male(age){
Human.apply(this,["male",age]);
}
Male.prototype=new Human();
var tom=new Male(30);
var jim=new Male(25);
alert(tom.sex);//male
alert(jim.age);//25
alert(tom.age);//30
三、组合继承(伪经典继承)
1.这种继承是把原型链和借用构造函数结合起来。用借用构造函数处理属性,用原型链处理方法。其实就相当于创建对象时提到的“组合模式”
//小实验
function Human(sex){
this._sex=sex;
}
Human.prototype.getSex=function(){return this._sex;}
Human.prototype.setSex=function(v){this._sex=v;}
function Male(name,age){
Human.call(this,"male");
this.name=name;
this.age=age;
}
Male.prototype=new Human();
Male.prototype.sayHello=function(){
alert("Hello, my name is "+this.name+", I'm "+this.age+" years old, I'm a "+(this.getSex()=="male"?"boy":"girl")+".");
}
var tom=new Male("tom",15);
var jan=new Male("jan",13);
jan.setSex("female");
tom.sayHello();
jan.sayHello();
jan.sayHello();
2.这是目前最常用,也是解决最完美的继承写法了,没有之一。