继承

一、JS中的继承方式:

面向对象:(类和实例)

  • 继承 子类继承父类
  • 封装 为实例添加方法、为原型增加方法
  • 多态(重写、重载)

继承:两个类 A B ,想让 B 类的实例使用 A 类上的属性和方法,就需要让 B 类继承 A 类,我们称 B 类为子类,A 类为父类(超类)

  1. function Super() {
  2. }
  3. function A() {}
  4. A.prototype.say = function () {
  5. console.log('hello World');
  6. };
  7. let b = new B();
  8. // b 是 B 类的实例 想使用 A 类的属性和方法
  9. console.log(b.say); undefined
  10. // 为啥是 undefined?对象 .属性名 先看自己是否有着私有属性,如果私有属性没有,就去实例所属类的原型查找公有的属性和方法,如果原型上也没有,就根据原型对象的 __proto__ 一直找到 Object 基类的原型上为止,如果还没有就返回 undefined;
  11. 现在需要 b 可以使用 A 类的 say 方法,现在就需要继承;

1.1 原型链继承

  • 原型继承: 将子类 B 的原型对象 重写成父类 A 的一个实例。

B.prototype = new A(); 把父类共有属性和私有属性变成子类的公有属性

  1. function A() {
  2. }
  3. A.prototype.age = 19;
  4. A.prototype.say = function () {
  5. console.log('来自A类原型的say方法')
  6. };
  7. function B() {}
  8. B.prototype = new A(); // 把 B 类的原型改写成一个 A 类的实例;
  9. let b = new B();
  10. console.log(b);
  11. console.log(b.__proto__); {text: 'xxx', __proto__....}
  12. console.log(b.age);
  13. console.log(b.say);

把 B 的原型改写成 A 类的一个实例,此时通过 b.age 访问 b 的 age 属性,首先在私有属性中查找,私有属性中没有 age 属性,接着去 b 所属类的原型(B.prototype)上查找,此时原型对象是 A 的实例对象,在原型对象也没有 age 属性,然后通过原型对象的 proto 就找到了 A.prototype 上,A 的 prototype 上有 age 属性

  • 原型继承是把子类公有的属性和私有的属性都变成了子类私有的属性;
  • 缺点:改写子类的原型对象,会导致子类原型对象上的 constructor 属性被改写,需要重新指定继承后的constructor;

1.2 借用构造函数继承

借用构造函数:把父类当做普通函数,在子类的函数体中call执行父类的函数;

  1. function A() {
  2. this.a = 'aa';
  3. this.say = function () {
  4. console.log('A say');
  5. }
  6. }
  7. A.prototype.public = 'public';
  8. function B() {
  9. A.call(this); // this 是 B 类的一个实例,A.call(this) 的意思是把 A 中的 this 就该成B的实例(而在 B 的构造函数中 this 就是 B 的实例)这样在 A 中通过 this.xxx = xxx 的方式添加的属性都会添加到 B 的实例身上。
  10. }
  11. let b = new B();
  12. console.log(b); {a: 'aa', say: fun.....}
  13. let b2 = new B();
  14. console.log(b2.say === b.say); false;

借用构造函数继承:

  • 把父类当做普通函数,在子类的函数体里面,通过 call 方法执行 A.call(this)
  • call 方法是用来修改 this 指向的,这样一来就把 A 中的 this 修改成了 b 的实例;在函数 A 中通过 this.xxx = xxx 添加的属性都添加到了 B 的实例身上;
  • 特点:只能把父类的私有属性和方法继承为子类的私有属性和方法;

1.3 组合继承

组合继承:原型链继承 + 借用构造函数继承

  • 原型链继承:把父类私有的和共有的继承为子类公有的;
  • 借用构造函数继承:把父类私有的继承为子类私有的
  1. function A() {
  2. this.a = '私有的'
  3. }
  4. A.prototype.text = '公有';
  5. A.prototype.say = function () {
  6. console.log('A公有的say方法')
  7. };
  8. function B() {
  9. A.call(this); // 借用构造函数继承,继承父类私有的
  10. }
  11. B.prototype = new A(); // 原型链继承,继承父类私有和公有的属性;
  12. B.prototype.constructor = B;
  13. let b = new B();
  14. console.log(b.text); // 公有的
  15. console.log(b.a); // 私有的
  • 组合继承也并非没有缺点,组合继承会父类的私有继承两次,一份在借用构造函数继承时成为私有的,而另一份是在原型继承时成为公有的;

1.4 原型式继承

原型式继承:把父类的公有属性继承为子类的公有属性;
创建一个新的对象,并且新对象的 proto 指向 A.prototype,最后把这个新对象作为B类的原型;

  • 创建一个对象,并且对象的 proto 指向 obj
  1. Object.create(obj)
  • 原型式继承示例
  1. function A() {
  2. this.private = 'private私有';
  3. }
  4. A.prototype.public = 'public公有';
  5. function B() {}
  6. B.prototype = Object.create(A.prototype); // 创建一个指定原型的对象 创建一个对象,并且这个对象的 __proto__ 指向 A.prototype
  7. B.prototype.constructor = B; // 原型式继承同样是修改B类原型的指向,所以需要重新指定构造函数
  8. let b = new B();
  9. console.log(b.public);

原型继承.png

1.5 寄生组合式继承

  • 寄生组合式继承:原型式继承 + 借用构造函数继承
  1. function A() {
  2. this.private = '私有属性';
  3. }
  4. A.prototype.public = '公有属性';
  5. function B() {
  6. A.call(this); // 借用构造函数继承
  7. this.name = 'b私有的';
  8. }
  9. // 原型式继承:把父类公有的 继承为子类实例公有的
  10. B.prototype = Object.create(A.prototype);
  11. B.prototype.constructor = B;
  12. let b = new B(); {name: '私有的', private: '私有属性'}
  13. console.log(b.private); // 继承过来的私有属性
  14. console.log(b.public); // 继承过来到的公有属性

寄生组合继承.png

1.6 冒充对象继承

冒充对象继承:在子类的构造函数中生成一个父类的实例,把父类的这个实例进行遍历,把属性都添加子类的实例上;

  1. function A() {
  2. this.private = '私有属性';
  3. }
  4. A.prototype.public = '公有属性';
  5. function B() {
  6. this.name = 'B私有的属性';
  7. let tmp = new A();
  8. for (let key in tmp) {
  9. this[key] = tmp[key];
  10. }
  11. }
  12. let b = new B();
  13. console.log(b);

1.7 ES6中的类

  • ES5 一个函数就是一个类
  • ES6 借鉴后端语言,增加了class 关键字,创建一个类
  • ES5
  1. function Teacher(name, age, subject) {
  2. this.name = name;
  3. this.age = age;
  4. this.subject = subject;
  5. }
  6. Teacher.motor = '传道 授业 解惑';
  7. Teacher.prototype.teach = function () {
  8. console.log(this.name + this.age);
  9. };
  10. let t = new Teacher('mabin', 19, 'js');
  • ES6
  1. class Teacher {
  2. constructor (name, age, subject) {
  3. // 这里面通过this.xxx = xxx 是给实例添加私有属性
  4. this.name = name;
  5. this.age = age;
  6. this.subject = subject;
  7. }
  8. // 添加公有属性
  9. teach () {
  10. console.log(this.name + this.age + this.subject);
  11. }
  12. // 添加静态方法
  13. static motor = '传道授业解惑';
  14. static getMotor () {
  15. console.log('We are U');
  16. }
  17. }
  18. let t = new Teacher('马宾', 18, 'JS');
  19. t.teach();
  20. console.log(Teacher.motor);
  21. Teacher.getMotor();

1.7 ES6继承

ES6继承:extends关键字

  1. class A {
  2. constructor (name, age) {
  3. this.name = name;
  4. this.age = age;
  5. }
  6. // 公有方法(添加到原型上)
  7. say () {
  8. console.log(`${this.name} say`);
  9. }
  10. }
  11. // ES6继承时使用 extends 关键字实现继承
  12. class B extends A { B继承A
  13. constructor (x, y, forName, forAge) {
  14. // 注意:在使用 ES6 的 extends 关键字之前,必须使用 super(); super 表示父类的构造函数
  15. super(forName, forAge); 注意:
  16. this.x = x;
  17. this.y = y;
  18. }
  19. }
  20. let b = new B('x', 'y', 'mabin', 18);
  21. console.log(b);
  22. b.say();
  • ES6的继承原理是:寄生组合式继承

1.8 call继承

CALL 继承的特点:CHILD 方法中把 PARENT 当做普通函数执行,让 PARENT 中的 THIS 指向 CHILD 的实例,相当于给 CHILD 的实例设置了很多私有的属性或者方法

  1. 只能继承父类私有的属性或者方法(因为是把 PARENT 当做普通函数执行,和其原型上的属性和方法没有关系)

  2. 父类私有的变为子类私有的

  1. function A(x) {
  2. this.x = x;
  3. }
  4. A.prototype.getX = function () {
  5. console.log(this.x);
  6. };
  7. function B(y) {
  8. // this:B 的实例 b1
  9. A.call(this, 200); // b1.x = 200;
  10. this.y = y;
  11. }
  12. B.prototype.getY = function () {
  13. console.log(this.y);
  14. };
  15. let b1 = new B(100);
  16. b1.y;
  17. b1.x;