浅谈继承的7种方式

浅谈继承的7种方式

最近总结了一下继承的4种方式,主要包括:原型链继承、构造函数继承、组合继承、以及class类继承(ES6提出)。

1,原型链继承

原型链继承的本质:子类的原型等于父类的实例。

  //父类:人
  function Person1 () {
    this.head = '我是父类person1';
  }
  //子类:学生,继承了“人”这个类
  function Student1(studentID) {
    this.studentID = studentID;
  }
  //本质:子类学生Student1的prototype值为父类 Person1的实例
  Student1.prototype = new Person1();
  
  var stu1 = new Student1(1001);
  console.log(stu1.head); //我是父类person1

  stu1.head = '你猜我是谁父类person1还是stuent1';
  console.log(stu1.head); //你猜我是谁父类person1还是stuent1
  
  var stu2 = new Student1(1002);
  console.log(stu2.head); //我是父类person1

子类实例对象在查找对象上的某个属性,会沿着__proto__属性,到其构造函数的原型中寻找。(这里只分析在上一层原型中找到属性的情况)
1,子类Student1的实例stu1,查找head属性,先在自身找,没有找到(只有studentID属性),会沿着__proto__属性到上一层原型对象(Student1的prototype值,原型链继承的本质 Student1.prototype = new Person1())中找,这里在Person1的实例中寻找,找到head属性(打印stu1.head值为我是父类person1)。
2,修改stu1.head = ‘你猜我是谁父类person1还是stuent1’ ,相当于在stu1实例自身添加了head属性。此时对其他的Student1的实例查找head属性,没有影响。

 //父类:人
  function Person () {
    this.head = '我是父类person';
    this.emotion = {
        name: 'x',
        age: 18
    }
  }
  //子类:学生,继承了“人”这个类
  function Student(studentID) {
    this.studentID = studentID;
  }
  Student.prototype = new Person();

  var stu1 = new Student(1001);
  console.log(stu1.emotion); //{ name: 'x', age: 18 }

  stu1.emotion.age = 17;
  console.log(stu1.emotion); //{ name: 'x', age: 17 }
  
  var stu2 = new Student(1002);
  console.log(stu2.emotion); //{ name: 'x', age: 17 } 

与上一个原型链继承不同的是,这里父类实例的包含一个属性值为引用类型的值emotion。当stu1修改emotion.age时,不是在实例stu1自身添加属性,而是修改了父类的值。
总结:原型上任何类型的属性值都不会通过实例被重写,但是引用类型的属性值会受到实例的影响而修改。

2,构造函数继承

构造函数继承本质:子类构造函数中改变父类构造函数的this指向。

  //父类:人
  function Person () {
    this.head = '我是父类person';
    this.emotion = {
        name: 'x',
        age: 18
    }
  }
  //子类:学生,继承了“人”这个类
  function Student(studentID) {
    this.studentID = studentID;
    Person.call(this)
  }

  var stu1 = new Student(1001);
  console.log(stu1.head);//我是父类person
  console.log(stu1.emotion); //{ name: 'x', age: 18 }

  stu1.head = '你猜我是啥'
  stu1.emotion.age = 17;
  console.log(stu1.head);//你猜我是啥
  console.log(stu1.emotion); //{ name: 'x', age: 17 }
  
  var stu2 = new Student(1002);
  console.log(stu2.head);//我是父类person
  console.log(stu2.emotion); //{ name: 'x', age: 18 }

由以上可以看出,构造函数继承,子类实例对属性值为引用类型的值进行修改,并不会对父类的值产生影响。每new一次实例,就会创建一个新的实例对象,在实例上进行修改并不会其他实例产生影响。
缺点:对于父类中定义的方法,new一次子类的实例,都会重新定义方法,占的内存较大。

3,组合继承

解决构造函数和原型链继承的缺点。

  function Person(){
    this.head = '我是person'
    this.emotaion = {
        age: 18
    }
}
Person.prototype.eat = function () {
    console.log('我喜欢吃')
}
Stuent.prototype = new Person();
Stuent.prototype.constructor = Stuent;

function Stuent(stuentID){
    this.stuentID = stuentID
    Person.call(this)
}


const stu1 = new Stuent(100);
console.log(stu1.emotaion)//{ age: 18 }
stu1.eat()//我喜欢吃

stu1.emotaion.age = 17;
console.log(stu1.emotaion)//{ age: 17 }

const stu2 = new Stuent(200);
console.log(stu2.emotaion)//{ age: 18 }

在组合继承中,将统一的方法定义到父类的原型里面,然后定义子类的原型等于父类的实例,这样子类就能访问到父类上面的方法了。
为保证子类的实例都拥有各自父类的副本,在子类的构造函数中,使用 Person.call(this)
这样就解决了问题。

4,class类继承

  //es6中的class类继承
class Person {
    constructor(){
        this.head = '脑袋瓜子';
        this.emotion = {
            age: 18
        }
    }
    eat(){
        console.log('吃')
    }
}
class Stuent extends Person{
    constructor(studentID){
        super()
        this.studentID = studentID;
    }
}

new Stuent(1001).eat()//吃

class类继承相对于之前的组合继承功能一样,但是其语法更为简洁。类中,constructor相当于之前介绍的构造函数,new一个类,会调用此方法。类中定义的其他方法,是定义在类的原型上面。

5,原型式继承

思想:没有严格意义上的构造函数,而是借助原型可以基于现有对象创建一个新的对象,同时还不必创建自定义类型。

function object(o){
	function F(){} //创建了临时的构造函数
	F.prototype = o; //将传入的对象作为构造函数的原型
	return new F(); //返回构造函数的实例
}
let o = {
	name: '小明',
	play: ['足球','篮球','乒乓球']
}
let obj1 = object(o) //以o为原型对象
obj1.name = '小1'
obj1.play.push('排球')

let obj2 = object(o) //以o为原型
obj2.name = '小2'
obj2.play.push('羽毛球')

console.log(o.name) // '小明'
console.log(o.play) // ['足球','篮球','乒乓球',‘排球’,‘羽毛球’]

以上就是原型式继承的大致思路,ES5对通过Object.create方规范了原型式继承。接受两个参数,一个式用作新对象原型的对象,第二个是,为新对象定义额外属性的对象。
传入一个参数时,此种方法与object()方法行为相同。

let o = {
	name: '小明',
	play: ['足球','篮球','乒乓球']
}
let obj1 = Object.create(o) //以o为原型对象
obj1.play.push('排球')

let obj2 = Object.create(o) //以o为原型
obj2.play.push('羽毛球')

console.log(o.name) // '小明'
console.log(o.play) // ['足球','篮球','乒乓球',‘排球’,‘羽毛球’]

let obj2 = Object.create(o,{
	name: {
		value: '小三'
	}
}) //以o为原型
console.log(obj3.name) // '小三'

Object.create方法的第二个参数与 Object.defineProperties方法的第二个参数相同,即每个属性均是通过自己的属性描述符定义的,以这种方式定义的任何属性都会覆盖原型对象上的同名属性

原型式继承的使用场景:

没必要创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,这种方式是完全可以胜任的。

原型式继承缺点:

与原型链继承一样,如果原型包含引用类型的值的属性始终会共享相应的值。

6,寄生式继承

此种继承方式与原型式继承紧密相关。
思路:创建一个仅用于封装继承过程的函数(在内部以某种方式来增强对象),最后再像是真的是它做了所有工作一样返回对象。


function createAnother(originol){
	var clone = object(origiinal)
	clone.sayHi = function () {
		console.log('hi')
	}
	return clone
}
var person = {
	name: '小明',
	play: ['篮球','排球']
}
var another = createAnother(person)
another.sayHi() //'hi'

上述返回的新对象不仅具有person的所有属性和方法,而且还有自己的sayHi方法。
适用: 能返回新对象的函数都适用此模式。

5,寄生组合式继承

组合式继承的缺点:调用两次超类型构造函数。一次是在创建子类型构造函数原型时,第二次是在子类型函数内部。
子类型最终会包含超类型的所有属性,但我们不得不在调用子类型构造函数时重写这些属性。

  function Person(){
    this.head = '我是person'
    this.emotaion = {
        age: 18
    }
}
Person.prototype.eat = function () {
    console.log('我喜欢吃')
}
Stuent.prototype = new Person(); //第一次调用超类型构造函数,Stuent.prototype拥有了head和emotion属性,他们是Person的实例属性,只不过位于Person的原型中。
Stuent.prototype.constructor = Stuent;

function Stuent(stuentID){
    this.stuentID = stuentID
    Person.call(this) //第二次调用超类型构造函数。在新对象上添加了实例属性head和emotion。覆盖了第一次原型中的两个同名属性
}


const stu1 = new Stuent(100);//第二次调用超类型构造函数
console.log(stu1.emotaion)//{ age: 18 }
stu1.eat()//我喜欢吃

stu1.emotaion.age = 17;
console.log(stu1.emotaion)//{ age: 17 }

const stu2 = new Stuent(200);
console.log(stu2.emotaion)//{ age: 18 }

利用寄生组合式继承解决这个问题。

解决思路:

不必为了指定子类型的原型而去调用超类型的构造函数,我们需要的就是超类型构造函数原型的一个副本而已。

本质:

适用寄生式继承来继承超类型的原型,然后再将结果子类型的原型。

基本模式

function type(Person,Stuent){
	let prototype = object(Person.prototype) //创建超类型原型的一个副本
	prototype.constructor = Student //为创建的副本添加构造器属性
	Student.prototype = prototype //将新创建的对象赋值给子类型的原型
}

这样就可以用这个函数type()替换上面为子类型的原型赋值的语句了。
高效原因是:调用了一次超类型构造函数。

参考:JavaScript高级程序设计第三版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值