继承

1.call()方法介绍

  • ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
  • Call()方法的作用:
    • 1.调用指向函数
    • 2.修改函数的this指向
  • 语法格式:
    • fun.call(thisArg, arg1, arg2, …)
    • 1.thisArg :当前调用函数 this 的指向对象
    • 2.arg1,arg2:传递的其他参数
      // call 方法
      function fn(x, y) {
          console.log('我想喝手磨咖啡');//正常输出我想喝手磨咖啡
          console.log(this);//window
          console.log(x + y);//NaN,这时添加参数
      }
             
      var o = {
          name: 'andy'
      };
      // fn();
      // 1. call() 可以调用函数
      fn.call();//与fun();的结果一致,表示call()方法也有函数调用的功能
      // 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
      //原本这个函数的指向应该是window,现在改了滞后它就指向o这个对象了
      fn.call(o, 1, 2);//3 这时又参数传进来了

运行结果:

image

2.借用构造函数继承父类型属性

  • 核心原理: 通过 call() 把父类型的 this 改为指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
     // 借用父构造函数继承属性
     // 1. 父构造函数
    function Father(uname, age) {
        // 这里的 this 指向父构造函数的对象实例
        this.uname = uname;
        this.age = age;
    }
    // 2 .子构造函数
    function Son(uname, age, score) {
    // 这里的 this 指向子构造函数的对象实例
    // 因为我们知道call()方法是可以修改指定函数的this指向的
    Father.call(this, uname, age);//这里将Father的中的this指向改为了子构造函数中的this指向(即这里的this是子构造函数的this指向)
                this.score = score;//给子构造函数创建一个新的属性
    }
    var son = new Son('刘德华', 18, 100);
    console.log(son);//后面是可以正常输出结果的

运行结果:

image

3.借用原型对象继承父类型方法

  • 一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。

    核心原理:

    • 1.将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
    • 2.本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
    • 3.将子类的 constructor 从新指向子类的构造函数
      image
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
    // this 指向父构造函数的对象实例
    this.uname = uname;
    this.age = age;
}
Father.prototype.money = function() {
    console.log(100000);
};

// 2 .子构造函数
function Son(uname, age, score) {
    // this 指向子构造函数的对象实例
    Father.call(this, uname, age);
    this.score = score;
}
// Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
    console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);//里面具有自己设置的exam方法也有沿着原型链向上查找的money方法
console.log(Father.prototype);//里面只有公共方法money,表示son添加的方法并没用影响到Father
console.log(Son.prototype.constructor);//利用constructor指回Son原型对象

运行结果:

image

总结:

  • 我们可以通过原型对象来继承方法,因为我们知道,共享的方法是写在原型对象里面的,这里的Father原型对象里面有一个money的方法,那我们的子构造函数怎样去使用这个money方法呢?这里我们不能直接利用子原型对象=父原型对象这种方法,因为如果这样的话,你一旦修改了子原型对象,那么父原型对象也会跟着一起被修改,所以这里我们需要将构造函数实例化,创建一个实例化对象,让我们的子原型对象指向这个实例化对象,但这个实例化对象里面有一个属性:__proto__指向Father的原型对象的,所以我们的子原型对象是可以拿到这个money方法的并且无论我怎样修改子原型对象都不会影响到父原型对象,因为这里的子原型对象是指向实例化对象的,实例化对象和原型对象的地址是不一样的

4.ES6类的本质

  • 类的本质是一个函数,类实质上就是ES5当中的构造函数,基本上是一模一样的,我们看下面的代码和结果展示:
    // ES6 之前通过 构造函数+ 原型实现面向对象 编程
    // (1) 构造函数有原型对象prototype
    // (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
    // (3) 构造函数可以通过原型对象添加方法
    // (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
    // ES6 通过 类 实现面向对象编程
    class Star {

    }
    console.log(typeof Star);//function 说明类的本质是一个函数
    // 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
    // (1) 类有原型对象prototype
    console.log(Star.prototype);//
    // (2) 类原型对象prototype 里面有constructor 指向类本身
    console.log(Star.prototype.constructor);//结果 class Star 指向类本身
    // (3)类可以通过原型对象添加方法
    Star.prototype.sing = function() {
        console.log('冰雨');
    }
    var ldh = new Star();
    console.dir(ldh);//里面存在__proto__属性 , 和sing方法
    // (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
    console.log(ldh.__proto__ === Star.prototype);//true

运行结果:

image