原型链与继承 yinshibanxian

一、原型

  • 在Javascript中,每个对象都有一个__proto__内部属性,这个属性指向对象原型
  • 在Javascript中,每个函数对象不仅有__proto__内部属性,还预置了prototype属性
  • 当函数对象作为构造函数创建实例时,该prototype属性将被作为实例对象的原型__proto__

二、原型链

任何一个实例对象通过原型链可以找到它对应的原型对象,原型对象上的属性和方法都是实例共享的。

一个对象在查找一个属性或者方法时,会先在该对象内部去找,找不到时,他会沿着原型链依次向上查找。

注意: 函数对象同时有__proto__prototype属性,而一般的对象只有__proto__属性,函数有__proto__

因为函数对象是Function的实例对象。

instance of 原理:

判断实例对象的__proto__与构造函数的prototype是否是同一个引用。若不是,会继续沿着实例对象的__proto__向上

查找,直到顶端Object。

判断对象是哪个类的直接实例

使用对象.constructor直接可判断

const F = function(name) {
    this.name = name;
}
const f = new F();
f.constructor // F

使用new创建对象时发生了什么

const Base = function(name) {
    this.name = name;
}

const obj = new Base();

上述代码实际上可以转化为下列代码

const obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

也就是说,使用new创建对象时实际上进行了下述工作:

  1. 创建了一个空对象obj
  2. 空对象obj的_proto_属性指向构造函数的prototype属性
  3. 构造函数(Base)的this指针被替换成了obj,相当于执行了Base.call(obj)
  4. 如果构造函数中显式地返回一个对象,那么实例为返回的对象,否则,则返回这个新创建的对象

类的声明

// ES5
function Animal(name) {
    this.name = name;
}
// ES6
class Animal {
    constructor(name) {
        this.name = name;
    }
}

类的继承

在ES6之前,Javascript是没有类这个概念的,类的继承通过原型链实现.

  1. 原型链继承
     function Parent() {
         this.name = 'parent';
         this.say = () => {
             console.log(`Hello,I'm ${this.name}`);
         }
     }
     function Child() {
     }
     Child.prototype = new Parent();
     const child1 = new Child();
     const child2 = new Child();
     child1.__proto__ = child2.__proto__ // true
    

    优点:

    子类能继承父类原型链上的属性和方法

    缺点

    不能实现多继承

  2. 构造继承

    构造继承也就是使用子类的this把父类的构造函数跑一遍

     function Parent() {
         this.name = 'parent';
         this.say = () => {
             console.log(`Hello,I'm ${this.name}`);
         }
     }
     function Child() {
         Parent.call(this);
     }
    

    优点

    可以实现多继承

    缺点

    不能继承父类原型链上的方法和属性

  3. 组合继承

    组合继承即组合原型链继承与构造继承两种方式来实现继承

      function Parent() {
         this.name = 'parent';
         this.say = () => {
             console.log(`Hello,I'm ${this.name}`);
         }
     }
     function Child() {
         Parent.call(this);
     }
     Child.prototype = new Parent;
    

    组合继承 优化1

    因为这是父类构造函数已经被执行过了,因此子类只需继承父类原型链上的方法和属性即可

     Child.prototype = Parent.prototype
    

    缺点:

    • 因为原型上有一个属性为constructor,此时直接使用父类的 prototype 的话那么会导致 实例的 constructor 为 Parent,即不能区分这个实例对象是 Child 的实例还是父类的实例对象。
    • 子类不可直接在 prototype 上添加属性和方法,因为会影响父类的原型

    组合继承 优化2-添加中间对象

     function Parent() {
         this.name = "parent";
         this.arr = [1, 2, 3];
     }
    
     function Child() {
         Parent.call(this);
         this.type = "child";
     }
    
     Child.prototype = Object.create(Parent.prototype); //提供__proto__
     Child.prototype.constrctor = Child;
    

    封装一个原生的继承方法

     /**
     * 继承
     * @param Parent
     * @param Child
     */
     function extendsClass(Parent, Child) {
         function F() {}
         F.prototype = Parent.prototype;
         Child.prototype = new F();
         Child.prototype.constrctor = Child;
         return Child;
     }