欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS: 继承

程序员文章站 2022-06-01 08:41:45
继承 记录一下 javascript 的各种继承方式,个人用得比较多的还是原型链继承和 ES6 的 extends。 原型链继承 缺点: 在创建 Child 的实例时,无法向 Parents 传参 父类里面的引用类型被共享,个例修改导致所有实例都被修改 借用构造函数 为了解决上面的问题 ,经典继承方 ......

继承

记录一下 javascript 的各种继承方式,个人用得比较多的还是原型链继承和 es6 的 extends。

原型链继承


// 原型模式
function parents() {
  this.name = 'parents';
}
parents.prototype.getname = function() {
  return this.name;
}

// 原型链继承
function child() {}
child.prototype = new parents();
child.prototype.constructor = child;

let son = new child();
son.getname(); // parents;

缺点:

  • 在创建 child 的实例时,无法向 parents 传参
  • 父类里面的引用类型被共享,个例修改导致所有实例都被修改
function parents() {
  this.name = ['dad', 'mom'];
}

function child() {}
child.prototype = new parents();
child.prototype.constructor = child;

// 无法向 parents 传参
let son = new child();
let daughter = new child();
// 父类里面的引用类型被共享
son.name == daughter.name; // true
// 个例修改导致所有实例都被修改
son.name[0] = 'father';
daughter.name[0]; // "father"

借用构造函数


为了解决上面的问题 ,经典继承方式被设计出来:

// 构造函数模式
function parents(childname) {
  this.childname = childname;
  this.name = ['dad', 'mom'];
  this.getname = function() {
    return this.name;
  }
}

// 借用构造函数继承
function child(name) {
  parents.call(this, name);
}

// 可以向 parents 传参
let son = new child('son');
son.childname; // "son"

// 父类里面的引用类型不会被共享
let daughter = new child('daughter');
son.name[0] = 'father';
daughter.name[0]; // "dad"

缺点:方法都在构造函数里定义,每次创建 child 实例,都要重新创建一次方法,函数无法得到复用。

组合继承


为了弥补上面的缺点,于是乎组合继承诞生了。

// 组合模式
function parents(childname) {
  this.childname = childname;
  this.name = ['dad', 'mom'];
}
parents.prototype.getname = function() {
  return this.name;
}

// 组合继承
function child(name) {
  parents.call(this, name);
}
child.prototype = new parents();
child.prototype.constructor = child;

let son = new child('son');
son.name[0] = 'father';

let daughter = new child('daughter');
daufhter.name[0]; // "dad"

因为原型链能保持不变,所以instanceofisprotypeof()也能识别基于组合继承创建的对象。

原型式继承


function obj(o) {
  function f() {}
  f.prototype = 0;
  return new f();
}

这种方式就是在 obj 函数内部,创建一个临时性的构造函数 f(),然后将传入的对象作为这个构造函数的原型,最后返回这个临时类的实例。

其实就是对传入的对象进行一次浅复制,类似于 es5 的 object.create() 方法。

object.create(proto, [propertiesobject]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

proto: 新创建对象的原型对象。

propertiesobject: 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不 是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应object.defineproperties() 的第二个参数。

寄生式继承


function createobj (o) {
    var clone = object.create(o);
    clone.sayhi = function () {
        console.log('hi');
    }
    return clone;
}

这种方式就是创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再返回增强过的对象。本质上还是对传入的对象进行一次浅复制。

寄生组合式继承


这种方式得先创建一个用于封装继承过程的函数。

function inheritprototype(child, parents) {
  let prototype = object.create(parents.prototype);
  prototype.constructor = child;
  child.prototype = prototype
}

然后用上面的函数实现继承:

function parents(childname) {
  this.childname = childname;
  this.name = ['dad', 'mom'];
}
parents.prototype.getname = function() {
  return this.name;
}

function child(name) {
  parents.call(this, name);
}

inheritprototype(child, parents);

其实和组合继承差不多,只是封装了一个 inheritprototype 函数使得在继承的时候只在 child 里调用一次 parents 构造函数。

es6 的 extends


es6 引入了 class,可以通过extends关键字实现继承,这比 es5 的通过修改原型链实现继承,要清晰和方便很多。但需要注意的是,es6 的 class 只是语法糖,本质上还是 es5 的通过修改原型链实现继承的方法,具体可以去来看看 babel 对于 class 的转换。

class parents {
  // 构造函数
  constructor(name) {
    this.name = name;
  }
  // 静态方法
  static getname() {
    return this.name;
  }
  // 方法
  printname() {
    console.log(this.name);
  }
}

class child extends parents {
  constructor(name, childname) {
    // 调用父类的constructor(name)
    super(name);
    this.childname = childname;
  }
  printname() {
    super.printname();
    console.log(this.childname);
  }
}

let c = new child('mom', 'son');
c.printname();// mom son

es5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(parent.apply(this))。es6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

大多数浏览器的 es5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。class 作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。

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

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

参考


阮一峰_class 的继承

《javascript高级程序设计》(三)