js中的继承
面向对象语言中都支持两种继承:接口继承和实现继承,但是由于js中函数没有签名(接收的参数类型和数量不同),所以无法实现接口继承。
下面讲讲js中实现继承的方法:
一、原型链继承
原型链继承:既继承了父类的模板,又继承了父类的原型对象。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.sayName = function(){ alert(this.name); } function Boy(){} Boy.prototype = new Person('a',12); Boy.prototype.sayHello = function(){ alert('hello'); } var boy = new Boy(); alert(boy.name);//a boy.sayName();//a alert(boy.constructor);//function Person(...){...} alert(boy instanceof Person);//true alert(boy instanceof Boy);//true
由上可见,子类实例的构造函数指向了父类的构造函数(Boy.prototype指向了另一个原型,而这个原型的constructor是父类)。子类实例又是Person类又是Boy类。但是,有一个问题:若构造函数中需传参,就必须要在new Person()上传参,若在创建子类实例中传参则会显示undefined,如下:
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.sayName = function(){ alert(this.name); } function Boy(){ } Boy.prototype = new Person(); Boy.prototype.sayHello = function(){ alert('hello'); } var boy = new Boy('a', 12); alert(boy.name);//undefined
为什么不直接Boy.prototype = Person.prototype? 倘若设置成这样当对boy的原型增加共用方法时,父类的原型上也会被影响到。
虽然它既继承了父类的模板又继承了父类的原型对象,但若在子类中传递参数,则会被屏蔽。
原型链图解:
构造函数.prototype=原型
原型.constructor=构造函数
实例中的内部指针指向原型
function Person(){} Person.prototype.setName = function(){}; var person = new Person(); function Boy(){} Boy.prototype = person; var boy = new Boy();
继承调用方法的搜索步骤:①搜索实例 ②搜索子类.prototype ③ 搜索父类.prototype
原型链的两大问题:
1. 由于子类原型指向了父类的一个实例,相当于在子类原型上创建了一个共用属性,子类的所有实例都会共享这个属性,所以当包含引用类型的属性时,多个实例间会互相影响。
2. 在创建子类实例时,不能向父类的构造函数中传递参数。
二、借用构造函数的方式继承
这种继承只继承模板,而不继承原型对象,这就会导致函数无法复用。
function Person(name, age){ this.name = name; this.age = age; } function Boy(name, age){ Person.call(this, 'a');//继承了父类的模板 } var boy = new Boy(); alert(boy.name);//a
三、组合继承(最常用的继承模式)
组合继承是将原型继承和借用构造函数结合起来。
function Person(name, age){ this.name = name; this.age = age; } Person.prototype.sayName = function(){ alert(this.name); } function Boy(name, age, sex){ this.sex = sex; Person.call(this, name, age); // 继承实例属性(覆盖new Person()中继承的实例属性) } Boy.prototype = new Person(); // 继承父类中的所有属性和方法 Boy.prototype.sayHello = function(){alert('hello');} var boy = new Boy('a',12,'男'); alert(boy.name);//a alert(boy.sex);//男 boy.sayName();//a boy.sayHello();//hello
但是这种继承由于Person.call()继承了一次父类的模板(继承构造函数中添加的属性),又因Boy.prototype=new Person(),继承了一次原型对象(继承原型对象上的方法),并且又继承了一次模板。这就导致继承了两次父类的模板。
四、原型式继承
在一个函数内创建一个空构造函数,将传入的对象作为空构造函数的原型,然后返回空构造函数的实例。这种继承必须要有一个对象作为另一个对象的基础。
function object(obj){ function F(){}; F.prototype = obj; return new F(); } var person = { name:'a', age:12 }; var person2 = object(person); alert(person.name);//a alert(person2.name);//a
这种继承适用于只想让一个对象与另一个对象保持类似。
在ECMA5中新增了Object.create(用作新对象原型的对象,为新对象定义额外属性的对象)方法,此方法和我们定义的object()的作用差不多。
支持的浏览器:IE 9+ 、Firefox 4+、Safari 5+、Opera 12+、Chrome。
五、寄生式继承
在内部以某种方式增强对象,再返回此对象。
function object(obj){ function F(){}; F.prototype = obj; return new F(); }
// 创建仅用于封装继承过程的函数 function createAnother(sup){ var o = object(sup); o.sayHello = function(){ alert('hello'); }; return o; } var person = { name:'a', age:12 }; var person2 = createAnother(person); person2.sayHello();//hello
这种继承方式适用于对象不是自定义类型和构造函数的场景。
六、寄生组合式继承
解决上面继承两次父类模板的问题:
我们可以自己定义一个只继承父类原型对象的方法:
// 写法一
function extend(sub, sup){ function F(){};//创建一个空的构造函数 F.prototype = sup.prototype;//将父类的原型赋给空构造函数的原型 sub.prototype = new F(); sub.prototype.constructor = sub;//还原子类的构造函数 sub.superClass = sup.prototype;//保存父类的原型对象 if(sub.prototype.constructor == Object.prototype.constructor){ sub.prototype.constructor = sub;//防止子类的构造函数是object } } function Person(name, age){ this.name = name; this.age = age; } Person.prototype.sayName = function(){ alert(this.name); } function Boy(name, age, sex){ this.sex = sex; Boy.superClass.constructor.call(this, name, age);//之前保存的原型对象在这里就起到了解除耦合的作用了 } extend(Boy, Person); Boy.prototype.sayHello = function(){alert('hello');} var boy = new Boy('a',12,'男'); alert(boy.name);//a alert(boy.sex);//男 boy.sayName();//a boy.sayHello();//hello
// 写法二
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
}
function Boy(name) {
Person.call(this, name);
}
Boy.prototype = Object.create(Person.prototype, {
contructor: {
value: Boy,
emurable: false,
writable: true,
configurable: true
}
});
var boy = new Child('Tom');
boy.sayName(); // Tom
boy instanceof Person; // true
这样就会只继承一次父类的模板和一次原型对象。上面的判断构造函数是为了如果对下面的原型对象上设置了一个以对象字面量形式创建的新对象,那么此原型对象的constructor属性会变成新的constructor属性,不再指向原来的构造函数,而是指向了Object。
上面的这种方法就是所谓的寄生组合式继承。他结合了原型式继承和寄生式继承,是引用类型最理想的继承方式。