zhaobinglong / myBlog

https://zhaobinglong.github.io/myBlog/
MIT License
7 stars 0 forks source link

js关键词之继承 #16

Open zhaobinglong opened 4 years ago

zhaobinglong commented 4 years ago

面向对象的缺点

面向对象继承的缺点:无法决定继承哪些属性,所有属性都得继承。

参考:

https://zhuanlan.zhihu.com/p/47703221 https://www.jianshu.com/p/d5299ca26e4e

zhaobinglong commented 4 years ago

原型链继承

• 子类去new实例时,无法向父类构造函数传参 • 父类的引用类型的属性被所有实例共享(最大的缺点)

  //  父类构造函数
  function Parent2() {
    this.name = 'parent2';
    this.play = [1, 2, 3] // 数组类型的属性会被所有的实例共享
  }
  Parent2.prototype.age='100' // 给构造函数添加原型属性(原型属性会直接显示在子类的属性中)

  // 子类
  function Child2() {
    this.type = 'child2';
  }

  // 核心代码,原型继承的本质是把父类的实例赋值给子类的原型对象prototype
  Child2.prototype = new Parent2(); 

  var s1 = new Child2(); // 实例化一个Child2出来,并不能向Parent2传递参数
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play);

缺点

思考

为什么不指向父类的原型,而是父类的一个实例?因为如果子类的原型直接指向父类的原型,那么子类就无法获取父类构造函数定义的属性和函数,同时修改子类的原型也会影响到父类的原型,这是违背继承的原则的。

zhaobinglong commented 4 years ago

构造函数继承:

缺点:

  1. 每个实例都拷贝一份,占用内存大,尤其是方法过多的时候。(函数复用又无从谈起了,本来我们用 prototype 就是解决复用问题的)
  2. 方法都作为了实例自己的方法,当需求改变,要改动其中的一个方法时,之前所有的实例,他们的该方法都不能及时作出更新。只有后面的实例才能访问到新方法。
  3. 父类的原型方法和原型属性没有得到继承,比如说eat方法子类不能使用
    
    function Animal(name) {
    this.name = name; 
    }
    Animal.prototype.eat= function () {
    console.log(this.name + '正在吃东西')
    };

function Cat(furColor){ Animal.call(this,'小花猫'); // 核心代码, 利用call和apply在子类的构造函数中调用父类的构造函数。,这样子类就可以使用父类的属性 // Animal.apply(this, ['小花猫']);
this.name = '' this.furColor = furColor ; }; let tom = new Cat('black'); console.log(tom); console.log(tom instanceof Animal); // false,实例并不是父类的实例,只是子类的实例,原因也是因为没有继承Animal的原型 console.log(tom instanceof Cat); // true.

zhaobinglong commented 4 years ago

组合继承(原型继承+构造继承)

缺点:没有缺点,解决了前面两个方法的问题

//父类:人
    function Person () {
      this.head = '脑袋瓜子';
      this.emotion = ['喜', '怒', '哀', '乐']; //人都有喜怒哀乐
    }
    //将 Person 类中需共享的方法放到 prototype 中,实现复用
    Person.prototype.eat = function () {
      console.log('吃吃喝喝');
    }
    //子类:学生,继承了“人”这个类
    function Student(studentID) {
      this.studentID = studentID;
      Person.call(this);
    }

   //核心代码1: 此时 Student.prototype 中的 constructor 被重写了,会导致 stu1.constructor === Person
    Student.prototype = new Person();  

    //核心代码2: 将 Student 原型对象的 constructor 指针重新指向 Student 本身
    Student.prototype.constructor = Student;  
    var stu1 = new Student(1001);
    console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

    stu1.emotion.push('愁');
    console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]

    var stu2 = new Student(1002);
    console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

    stu1.eat(); //吃吃喝喝
    stu2.run(); //快跑
    console.log(stu1.constructor);  //Student
zhaobinglong commented 4 years ago

ES6 class继承

实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。 需要注意的是,class关键字只是原型的语法糖,JavaScript继承仍然是基于原型实现的。

缺点:不是所有浏览器都支持(随着时间的推移这个问题就不存在了)

class Animal {
  constructor(name) {
    this.name = name; 
  };
  eat() {
    console.log(this.name + '正在吃东西');
  };
}
var animal = new Animal('小猫咪');
animal.eat();// 小猫咪正在吃东西
//.....省略上面Animal类
class Cat extends Animal {
  // Tips: 这里有一个小的知识点,就是ES6的继承方法中如果子类没有写构造函数的话就一般默认添加构造。
  constructor(name) {
    //super作为函数调用时,代表父类的构造函数。
    super(name);
  }
    catchMouse(){
        console.log(`${this.name}正在捉老鼠`);
    }
}
var cat= new Cat('Tom猫');
cat.catchMouse();// Tom猫正在捉老鼠

再看一个例子

关键字extends 允许一个子类继承父类,需要注意的是,子类的constructor 函数中需要执行 super() 函数。 当然,你也可以在子类方法中调用父类的方法,如super.parentMethodName()。

class Phone { constructor(name) { this.name = name console.log("I'm a phone."); } }

class MI extends Phone { constructor() { // 类似于call的继承:在这里super相当于把父类的constructor给执行了,并且让方法中的this是B的实例, // super当中传递的实参都是在给A的constructor传递。 super('kk'); console.log("I'm a phone designed by xiaomi"); } }

let mi8 = new MI();