js 继承的实现方法

继承有助于去重和代码复用,继承的应用场景也是不同,例如:当一个项目要针对不同的场景如用户设备类型(是手机还是PC还是平板)写出多个版本,这时候就能用上继承。把一些多版本公用的属性和方法放在一个父类里,然后其他版本继承父类就有了父类的属性和方法,然后在子类中写子类独有的属性和方法。这样一来代码做到了复用,提高了开发效率,结构也更清新。还有很多https://blog.csdn.net/qq_36538012/article/details/104218392

https://segmentfault.com/a/1190000008739672

原型链继承

原型链继承的本质是利用原型让一个引用类型继承另一个引用类型的属性和方法。

//1.定义父类型的构造函数
    function Father () {
        this.property = true;
    }
    //2.给父类型的原型添加方法
    Father.prototype.getFatherValue = function () {
        return this.property
    }

    //3.定义子类型的构造函数
    function Children () {
        this.property = false
    }
    //4.子类型的原型为父类型的一个实例对象
    Children.prototype = new Father()
    //5.让子类型的构造器constructor 指向子类型
    Children.prototype.constructor = Children
    //6.给子类型的原型添加方法
    Children.prototype.getChildrenValue = function () {
        return this.property
    }

    //7.实例化一个子类
    var instance = new Children()
    console.log(instance.getFatherValue()) //true

本质:让子类的原型指向父类的一个实例对象

优点:1、子类实例可继承的属性有:子类实例的构造函数的属性,父类构造函数属性,父类原型的属性。(子类实例不会继承父类实例的属性!)

缺点:1、子类实例无法向父类构造函数传参。
   2、继承单一。
   3、所有子类实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)

借用构造函数(很少单独使用)

//1.定义父类型构造函数
    function Father (name,age) {
        this.name = name
        this.age = age
    }
    //2.定义子类型构造函数
    function Children (name, age, height) {
        //3.用.call()和.apply()将父类构造函数引入子类函数
        Father.call(this, 'tom',20) //相当于:this.Father(name, age)
        this.height = height
}
var child = new Children('tom', 20, 180)
本质:是通过call()方法借用了父类的属性。call方法可以将父类中通过this指定的属性和方法复制到子类的实例中。因为this是在运行时基于函数的执行环境确定的,在全局中,this等于window,
当这个函数被作为某个对象的方法调用时,this指向那个对象。所以,刚开始this指向那个子类的构造函数,当new一个实例的时候,this会指向这个新实例,这样实例中就会有父类中通过this定义的
属性和方法。
总结this:当作为函数调用的时候,this指向window,当作为方法调用的时候this,指向调用它的对象,当new一个实例的时候,this指向实例对象。

优点:解决了原型链继承的缺点,通过call可以继承多个构造函数的属性,同时可以在子类的构造函数中向父类的构造函数传参。各个实例之间互不影响。例如
Father.call(this, 'jerry',18)

缺点:call()方法借用了父类的属性,而方法都定义在构造函数中,并没有从原型上继承到夫类的属性和方法,因此没有可复用性。

组合继承

是一种将原型链和构造函数组合使用的方法。

//1.定义父类型
    function Person (name, age) {
        this.name = name
        this.age = age
    }
    //2.给父类型添加方法
    Person.prototype.setname = function (name) {
        this.name = name
    }
    //3.定义子类型
    function Student (name, age, height) {
        //4.用.call()和.apply()将父类构造函数引入子类函数 本质是借用了父类的属性
        Person.call(this, name, age)
        this.height = height
    }
    //5.子类型的原型为父类型的一个实例对象 本质是继承了父类的方法
    Student.prototype = new Person()
    //6.让子类型的构造器constructor 指向子类型
    Student.prototype.constructor = Student
    //7.给子类型的原型添加方法
    Student.prototype.setheight = function (height) {
        this.height = height
    }
    var s1 = new Student('tom', 21, 180)
    var s2 = new Student('jerry', 18, 190)
    s1.setname('lihh')
    console.log(s1.name,s1.age,s1.height)
    console.log(s2.name,s2.age,s2.height)

本质:是通过call()方法借用了父类中通过this指定的属性,而原型链继承实现了对父类原型上属性和方法的继承

优点:融合两种模式的优点,既可以传参和复用

缺点:会调用两次父类型的构造函数,分别是在创建子类原型的时候和在子类构造函数内部。(对应上面的4,5步)。

原型式继承

 待补充。。。

寄生式继承

  待补充。。。

寄生组合继承

  待补充。。。

Class继承

Class 可以通过 extends 关键字实现继承,然后在子类的constructor里面通过调用super新建父类的this对象,且必须是先调用super,否则新建子类实例会报错,子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。而ES5得继承实质上是先创建子类得this,然后将父类得方法添加到自己的this上面,Parent.call(this)这样。

class ColorPoint extends Point {
  constructor(x, y, color) {
     // this.color = color; 报错
    super(x, y); // 调用父类的 constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString(); // 调用父类的 toString()
  }
}
// 生成子类实例(与 ES5 的行为完全一致)
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true

Object.getPrototypeOf() & super 关键字

Object.getPrototypeOf方法可以用来从子类上获取父类

Object.getPrototypeOf(ColorPoint) === Point // true

第一种情况:super 作为函数调用时,代表父类的构造函数,super 虽然代表了父类的构造函数,但是返回的是子类的实例,即 super 内部的 this 指的是子类,这时候相当于A.prototype.constructor.call(this)

class A {}
class B extends A {
  constructor() { // ES6 要求,子类的构造函数必须执行一次 super 函数
    super();  // super() 在这里相当于 A.prototype.constructor.call(this)
  }
}

在super()执行时,它指向的是子类B的构造函数,而不是父类A的构造函数

class A {
  constructor() {
    console.log(new.target.name);
  }
}
class B extends A {
  constructor() {
    super();
  }
}
new A() // A
new B() // B

作为函数时,super() 只能用在子类的构造函数之中,用在其他地方就会报错

class A {}
class B extends A {
  m() {
    super(); // 报错
  }
}

第二种情况:super 作为对象时,在普通方法中指向父类的原型对象

class A {
  p() {
    return 2;
  }
}
class B extends A {
  constructor() {
    super();
    console.log(super.p()); // 2
  }
}
let b = new B();

将 super 当作一个对象使用。这时,super 在普通方法之中,指向 A.prototype,所以 super.p() 就相当于 A.prototype.p()

这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的

class A {
  constructor() {
    this.p = 2;
  }
}
class B extends A {
  get m() {
    return super.p; // p 是父类 A 实例的属性,super.p 就引用不到它
  }
}
let b = new B();
b.m // undefined

ES6 规定,通过 super 调用父类的方法时,方法内部的 this 指向子类

class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}
class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();  // 实际上执行的是 super.print.call(this)
  }
}
let b = new B();
b.m() // 2

由于 this 指向子类,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性

class A {
  constructor() {
    this.x = 1;
  }
}
class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;  // super.x 赋值为 3,这时等同于对 this.x 赋值为3
    console.log(super.x); // undefined,当读取 super.x 的时候,读的是 A.prototype.x,所以返回 undefined
    console.log(this.x); // 3
  }
}
let b = new B();

类的 prototype 属性 & __proto__属性

子类的 __proto__ 属性,表示构造函数的继承,总是指向父类

子类 prototype 属性的 __proto__ 属性,表示方法的继承,总是指向父类的 prototype 属性

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

类的继承是按照下面的模式实现的

class A {
}
class B {
}
Object.setPrototypeOf(B.prototype, A.prototype);  // B 的实例继承 A 的实例
Object.setPrototypeOf(B, A);  // B 继承 A 的静态属性
const b = new B();
Object.setPrototypeOf = function (obj, proto) { // Object.setPrototypeOf 方法的模拟实现
  obj.__proto__ = proto;
  return obj;
}

《对象的扩展》一章给出过Object.setPrototypeOf方法的实现

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

因此,就得到了上面的结果

Object.setPrototypeOf(B.prototype, A.prototype);
B.prototype.__proto__ = A.prototype;  // 等同于
Object.setPrototypeOf(B, A);
B.__proto__ = A;  // 等同于

 这两条继承链,可以这样理解:作为一个对象,子类(B)的原型(__proto__ 属性)是父类(A);作为一个构造函数,子类(B)的原型对象 (prototype 属性)是父类的原型对象(prototype 属性)的实例。

extends 关键字后面可以跟多种类型的值

class B extends A { // 只要 A 是一个有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数
}

// 特殊情况一:子类继承 Object 类(子类其实就是构造函数 Object 的复制,子类的实例就是 Object 的实例)
class A extends Object {
}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

// 特殊情况二:不存在任何继承
class A {
}
A.__proto__ === Function.prototype // true,A 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype
A.prototype.__proto__ === Object.prototype // true,A 调用后返回一个空对象(即 Object 实例),所以 A.prototype.__proto__ 指向构造函数(Object)的 prototype 属性

// 特殊情况三:子类继承 null
class A extends null {
}
A.__proto__ === Function.prototype // true,A 也是一个普通函数,所以直接继承 Function.prototype
A.prototype.__proto__ === undefined // true,A 调用后返回的对象不继承任何方法,所以它的 prototype.__proto__ 指向 undefined
class C extends null {  // 实质上等同于
  constructor() { return Object.create(null); }
}

// 子类实例的 __proto__ 属性的 __proto__ 属性,指向父类实例的 __proto__ 属性。也就是说,子类的原型的原型,是父类的原型
var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');
p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

// 因此,通过子类实例的 __proto__.__proto__ 属性,可以修改父类实例的行为
p2.__proto__.__proto__.printName = function () {
  console.log('Ha');
};
p1.printName() // "Ha"
不积跬步无以至千里
原文地址:https://www.cnblogs.com/lyt0207/p/12361356.html