0、原型和原型链

没个构造函数都会有一个prototype属性,指向它的原型对象,原型对象有一个constructor属性,指向构造函数,实例对象会有一个内部属性proto指向构造函数的原型对象,原型链就是通过proto一形成的一个链接,链接的顶部是Object.prototype,Object.prototype的proto属性指向null

  1. function Animal(name) {
  2. this.name = name
  3. }
  4. Animal.prototype.getName = function() {
  5. return this.name
  6. }
  7. Animal.prototype.constructor === Animal // true
  8. const cat = new Animal('miao')
  9. cat.__proto__ === Animal.prototype // true
  10. Object.prototype.__proto__ === null // true

1、原型链继承

我们知道原型链是通过对象的proto属性形成的一条链,原型链继承自然是通过proto继承

  1. function Animal(name) {
  2. this.name = name
  3. }
  4. Animal.prototype.getName = function() {
  5. return this.name
  6. }
  7. function Cat(name) {
  8. if(name) {
  9. this.name = name
  10. }
  11. }
  12. // 通过改变Cat的prototype属性,来改变Cat实例的__proto__指向
  13. // 将Animal的实例赋值给Cat的prototype属性
  14. const animal = new Animal('cat')
  15. Cat.prototype = animal
  16. Cat.prototype.constructor = Cat
  17. // 可进一步扩展
  18. // Cat.prototype.getAge = function() {}
  19. const cat = new Cat()
  20. cat.getName() // cat
  21. const myCat = new Cat('myCat')
  22. myCat.getName() // myCat

cat和myCat都是Cat构造函数的实例,我们通过给Cat.prototype重新赋值Animal的实例animal,使得cat和myCat的proto都指向animal,而animal具有name属性,animal.proto指向Animal.prototype,所以最终才可以使用getName获取name。

总结:让子类实例的原型(proto)指向父类的一个实例 优点:可以继承父类原型对象上的属性和方法 缺点:1、父类的引用属性会被所有子类实例共享 2、实例化子类时不能向父类传参

2、构造函数继承

构造函数继承就是在子类构造函数中执行父类构造函数,并绑定this为子类实例(将父类实例的属性复制到在子类上),上代码

  1. function Animal(name) {
  2. this.name = name
  3. this.footer = 'footer'
  4. this.getName = function() {}
  5. }
  6. function Cat(name, age) {
  7. this.age = age
  8. Animal.call(this, name)
  9. }
  10. const cat = new Cat('miao', 3)

如上代码所示,构造函数继承的重点是在子类构造函数中执行父类构造函数Animal.call(this, name),将父类实例该有的属性都复制过来

总结:重点在于Animal.call(this, name) 优点: 缺点:1、只能继承父类实例上该有的属性,无法继承父类实例原型上的属性和方法 2、无法复用共有属性和方法,上述例子中,通过实例化Cat创建3个实例,三个实例中都有getName方 法,造成内存浪费

3、组合继承

组合继承从名字就可以看出来他不是新鲜玩意,他是将上述第一种继承(原型链继承)和第二种继承(构造函数继承)组合起来的一种方法,它既可以继承到父类构造函数原型上的共有属性和方法,也可以继承到父类实例自身该有的属性和方法,不多说上代码

  1. function Animal(name) {
  2. this.name = name
  3. this.footer = 'footer'
  4. }
  5. Animal.prototype.getName = function() {
  6. return this.name
  7. }
  8. function Cat(name, age) {
  9. this.age = age
  10. // 构造函数继承
  11. Animal.call(this, name)
  12. }
  13. // 原型链继承
  14. const animal = new Animal('cat')
  15. Cat.prototype = animal
  16. // 重写Cat.prototype.constructor指向
  17. Cat.prototype.constructor = Cat
  18. const cat = new Cat('miao', 4)

image.pngimage.png

通过上述代码发现,实例cat和其原型cat.proto(animal)上都有父类(Animal)实例该有的属性,只不过cat上的属性会屏蔽其原型上的同名属性和方法

总结:将原型链继承和构造函数继承结合起来 优点:可以将父类原型对象上的属性、方法和父类实例自身该有的属性、方法都继承过来 缺点:子类实例和子类实例原型上存在一部分同名属性和方法,造成内存浪费

4、原型式继承

原型式继承和Obje.create()方法原理一致,但是又有点区别

  1. function extend(obj) {
  2. function F() {}
  3. F.prototype = obj
  4. return new F()
  5. }
  6. const person = {
  7. name: 'streetex',
  8. colors: ['red']
  9. }
  10. const anotherPerson = extend(person) // {name: 'streetex}
  11. anotherPerson.name = '007'
  12. console.log(person.name) // 'streetex'
  13. anotherPerson.colors.push('black')
  14. console.log(person.colors) // ['red', 'black']

原型式继承和原型链继承差不多,都是通过修改构造函数的prototype为指定对象,但是原型式继承将这种操作包在了函数里边,另外原型式继承实际上是将参数对象person进行了一次浅复制

总结 优点:可以复用父类方法 缺点:父对象的引用属性会被所有子实例共享;创建子实例时无法传参

上述缺点已被修正,就是Object.create方法

5、寄生式继承

寄生式继承是在原型式继承的基础上进行了增强,怎么增强?上代码

  1. function extend(obj) {
  2. function F() {}
  3. F.prototype = obj
  4. return new F()
  5. }
  6. function create(obj) {
  7. const anotherObj = extend(obj)
  8. anotherObj.getName = function() {
  9. return this.name
  10. }
  11. return anotherObj
  12. }
  13. const person = {
  14. name: 'streetex',
  15. colors: ['red']
  16. }
  17. const anotherPerson = create(person)

可以看出寄生式继承是在函数内通过原型式继承创建一个对象,然后进行增强,最后返回

总结:在原型式继承的基础上进行了增强 优点:可以复用父类方法,并且可以进行增强 缺点:父对象的引用属性会被所有子实例共享;创建子实例时无法传参(和原型式继承一样)

6、寄生组合式继承

在第3点介绍的组合继是是原型链继承和构造函数继承的组合,它的缺点有一条是父构造函数调用了两次,造成了浪费,我认为寄生组合式继承是寄生式继承、原型链继承和构造函数继承的组合,父构造函数只会调用一次

  1. function inherit(Sub, Sup) {
  2. // Object.create和原型式继承一样,返回一个参数对象的副本
  3. // 寄生式继承+原型链继承
  4. const prototype = Object.create(Sup.prototype)
  5. Sub.prototype = prototype // 改变子类构造函数的prototype
  6. Sub.prototype.constructor = Sub
  7. }
  8. function SupType(name) {
  9. this.name = name
  10. this.colors = ['red']
  11. }
  12. SupType.prototype.getName = function(){
  13. return this.name
  14. };
  15. function SubType(name,age) {
  16. this.age = age
  17. // 构造函数继承
  18. SupType.call(this, name)
  19. }
  20. inherit(SubType, SupType)
  21. SubType.prototype.getAge = function() {
  22. return this.age
  23. }
  24. const sub = new SubType('streetex', 25)

优点: 缺点:无

7、ES6 Class extends继承

JavaScript是基于原型继承的语言,即使ES6新增了class,他依然是基于原型继承的,class只是一种语法糖,es6中的类继承的关键字式extends,并且在子类中通过调用super方法将父类实例对象上的方法和属性加到子类实例上

  1. class Animal {
  2. constructor(name) {
  3. this.name = name
  4. }
  5. }
  6. class Cat extends Animal {
  7. constructor(name) {
  8. super(name)
  9. }
  10. }
  11. typeof Cat // function