js中的继承
继承
OO语言分为接口继承和实现继承,ECMAScript支持实现继承,主要是依靠原型链来实现的
1 原型链
利用原型,让一个引用类型继承令一个引用类型的属性和方法
实现原型链的一种模式:
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subProperty = false;
}
SubType.prototype = new SuperType();
let instance = new SubType();
console.log(instance.getSuperValue()) // true
1.1 别忘记默认的原型 Object
1.2 确定原型和实例的关系
只要是原型链中出现的原型 都可以说是该原型链派生的实例的原型
instanceOf
console.log(instance instanceof Object) //true
console.log(instance instanceof SuperType) // true
console.log(instance instanceof SubType) // true
isPropertytypeOf
console.log(Object.prototype.isPrototypeOf(instance)) //true
console.log(SubType.prototype.isPrototypeOf(instance)) //true
console.log(SuperType.prototype.isPrototypeOf(instance)) //true
1.3 谨慎的定义方法
给原型添加的方法一定要放在替换原型的语句之后
注意尽量不要重写原型
如: Person.propertype = { } // 这样会重写原型 谨慎操作
1.4 原型链的问题
1 由于 包含引用类型值的原型属性会被所有实例共享
用代码说明问题
function SuperType(){
this.colors = ['red' , 'yellow']
}
function SubType(){ }
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors) // ["red", "yellow", "black"]
let instance2 = new SubType();
console.log(instance2.colors) // ["red", "yellow", "black"]
// 两个实例公用一个colors了
2 在创建子类型时的实例时,没法在不影响所有对象实例的情况下 向超类型的构造函数中传递参数
所以,实践中很少会单独使用原型链
2 借用构造函数
即在子类型构造函数内部调用超类型构造函数 call apply
为了解决原型中包含引用类型值的问题
function SuperType(colors){
this.colors = colors;
}
function SubType(colors){
SuperType.call(this, colors);
this.age = 10;
}
let instance1 = new SubType(['1' , '2']);
console.log(instance1.colors)
问题:方法都在构造函数中定义,所以函数复用也无从谈起
且超类型中定义的方法,对子类型而言也是不可见的,结果所有类型只能使用构造函数模式
3 组合继承
将原型链与组合继承结合的一种集成方法
使用原型链实现对原型属性和方法的继承,通过构造函数实现对实例属性的继承
// 组合继承
// 使用原型链实现原型属性和方法的继承 使用构造函数实现实例属性的继承
function SuperType(name){
this.name = name;
this.colors = [1,2,3]
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name , age){
this.age = age;
SuperType.call(this , name)
}
// 继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
console.log(this.age)
}
let a = new SubType('a' , 10)
a.colors.push(4)
console.log(a.colors)
a.sayName()
a.sayAge();
let b = new SubType('b' , 10)
console.log(b.colors)
b.sayName()
b.sayAge();
4 原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必创建自定义类型
这种原型式继承,必须有一个对象作为另一个对象的基础,把它传进object()函数,然后根据需求对得到的对象加以修改
function object(o){
function F(){}
F.prototype = o;
return new F();
}
// 例子
let person = {
name: 'nicolas',
friends: ['jobs']
}
let person1 = object(person);
person1.name = 'p1';
// person1.friends.push('p1fw')
person1.friends = [1,2,3]
let person2 = object(person);
person2.name = 'p2';
person2.friends.push('p2fw')
console.log(person)
console.log(person1)
console.log(person2)
不过,ES5通过新增Object.create()方法规范了原型式继承,这个方法接受两个参数,一个用作新对象原型的对象和(可选择的)一个为新对象定义额外属性的对象
在传入一个参数的情况下,Object.create()作用与上面object()作用相同
Object.create()第二个参数与Object.defineProperties()方法第二个参数格式相同
let person = {
name: 'jobs',
friends: ['f']
}
let person1 = Object.create(person , {
name:{
value: 'jack'
},
friends:{
value: ['d']
}
})
console.log(person1)
在没有必要创建构造函数,而只想让一个对象与另一个对象类似的情况下,原型式继承是完全可以胜任的,但要注意包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样。
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,然后返回一个对象
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function createAnother(origin){
let clone = object(origin); // 通过调用函数创建一个新对象
clone.sayHi = function(){ // 增强这个新对象
console.log('hi')
}
return clone; // 返回创建的对象
}
let person = {
name: 'a'
}
let anotherPerson = createAnother(person);
anotherPerson.sayHi()
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似
6 寄生组合式继承
前面说过,组合式继承是最常用的继承方式,不过组合继承最大的问题是无论什么情况下,都会调用两次超类型的构造函数
// 这是组合继承
function SuperType(name){
this.name = name
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name , age ){
SuperType.call(this , name); // 第二次调用SuperType()
this.age = age
}
SubType.prototype = new SuperType() // 第一次调用SuperType()
// 两次调用SuperType()的后果是 SubType()里会有两个name属性,一个在实例上,一个在SubType()原型上
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age)
}
let sub = new SubType('wu' , 20);
console.log(sub)
sub.sayAge()
sub.sayName()
// 所以用寄生组合式继承解决这个问题
// 即 借用构造函数来继承属性,用原型链来继承方法
function SuperType(name){
this.name = name;
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name , age){
this.age = age;
SuperType.call(this, name)
}
// inheritPrototype()函数实现了寄生组合式继承的最简单形式
function inheritPrototype(SubType , SuperType){
// 第一步 创建对象 创建超类型原型的一个副本
let prototype = Object.create(SuperType.prototype);
// 第二步 为创建的副本添加constructor 属性,弥补因重写原型原型而失去默认的constructor属性
prototype.constructor = SubType;
// 第三步 将创建的对象赋值给子类型的原型
SubType.prototype = prototype;
}
inheritPrototype(SubType , SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age)
}
let sub = new SubType('wang' , 33);
console.log(sub)
sub.sayName()
sub.sayAge()
``