Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现继承的几种方式 #9

Open
Godiswill opened this issue Jan 9, 2020 · 0 comments
Open

实现继承的几种方式 #9

Godiswill opened this issue Jan 9, 2020 · 0 comments

Comments

@Godiswill
Copy link
Owner

Godiswill commented Jan 9, 2020

实现继承的几种方式

原文链接

众所周知,JavaScript 并没有传统面向对象语言中类和类继承的概念。
JS一般使用 newinstanceof来确认对象之间的父子相似的关系。
最新es6出现的类也不过是构造函数的语法糖。

以下介绍下常见的继承方式及其优缺点。

原型继承

原型继承的思路就是,把父类的一个实例赋值给子类的 prototype
这样便能继承父类的自有属性和方法和父类原型链上的属性和方法。

function Super() {
  this.arr = ['super'];
}
Super.prototype.getArr = function() {
  return this.arr;
}
Super.prototype.pushArr = function(str) {
  this.arr.push(str);
  return this.arr;
}

function Sub() {}
// 继承父类的自有属性和方法和父类原型链上的属性和方法。
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;

var instance = new Sub();
instance.getArr(); // ["super"]
instance.arr; 		 // ["super"]
// 非自有属性
Object.hasOwnProperty.call(instance, 'arr'); // false

instance instanceof Sub;   // true
instance instanceof Super; // true

var instance2 = new Sub();
instance2.pushArr('instance2'); // 实例instance2的修改会影响到实例instance

instance2.getArr(); // ["super", "instance2"]
instance.getArr();  // ["super", "instance2"]

在构造函数绑定在 this 上的属性就是为了在 new 一个实例的时候单独开辟新的内存空间,相互独立不影响。
而所有实例引用共享 prototype 上的属性或方法,避免内存浪费。

  • 优点
  1. 父类 Super 上的属性和方法都能继承;
  2. 实例与父类、子类的关系清晰。
  • 缺点
  1. Super 实例的自有属性 arr 会被所有 Sub 子类实例共享。
    而想要的结果是父类 Super 在 this 上声明的属性 arr 也应该是子类 Sub 的自有属性 ;
  2. 在构建子类 Sub 时,无法传参给父类 Super 进行初始化。

构造函数继承

function Super(value){
    this.superValue = value;
}
Super.prototype.getSuperValue = function() {
  return this.superValue;
};

function Sub(value){
    Super.call(this, value);
    this.subValue = this.superValue + ' world';
}
var instance = new Sub('hello');
instance.superValue; 		// hello
instance.subValue;	 		// hello world
instance.getSuperValue;	// undefined

instance instanceof Sub;   // true
instance instanceof Super; // false

使用applycall改变this指向。
父类 Super 在构造函数中 this 上声明的属性挂载到子类 Sub 实例 this 上,以实现继承。

  • 优点
  1. 父类 Super 上声明的自有属性可以被子类 Sub 生成的实例继承成自有属性;
  2. 可以向父类构造函数传参。
  • 缺点
  1. 没有继承父类原型链上的共享属性与方法;
  2. 实例与父类、子类的关系不清晰。

组合继承

组合继承就是把上述原型继承和构造函数继承优点组合一下

function Super(arr){
    this.superArr = arr || ['default'];
}
Super.prototype.getSuperArr = function() {
    return this.superArr;
};
function Sub(superArr, subArr) {
    Super.call(this, superArr); // 继承自有属性
    this.subArr = subArr;
}

//继承方法
Sub.prototype = new Super(); // 继承原型链

Sub.prototype.constructor = Sub;
Sub.prototype.getSubArr = function() {
    return this.subArr;
};

var instance1 = new Sub(['super1'], ['sub1']);
instance1.superArr.push('instance1');
instance1.getSuperArr(); // ["super1", "instance1"]
instance1.getSubArr();   // ["sub1"]

var instance2 = new Sub(['super2'], ['sub2']);
instance2.getSuperArr(); // ["super2"]
instance2.getSubArr();	 // ["sub2"]

instance1 instanceof Super; // true
instance1 instanceof Sub;   // true

instance1.__proto__.superArr; // ["default"]
  • 优点
  1. 自有、原型链上的属性、方法都合理继承了;
  2. 可以向父类传参数,初始化一些操作;
  3. 实例与父子类关系清晰。
  • 缺点
  1. 父类 Super 构造函数被调用 2 次;
  2. 父类 this 声明的属性被还被挂载在 Sub.prototype 上,造成内存的浪费。

寄生组合继承

//继承方法
Sub.prototype = new Super(); // 继承原型链

// 原型链继承改写如下

if(!Object.create){
  Object.create = function(proto){
    function F(){}
    F.prototype = proto;
    return new F;
  }
}
Sub.prototype = Object.create(Super.prototype);

这样就能解决组合继承的的缺点了。

为什么不直接把 Super.prototype 赋值给 Sub.prototype 呢?
而是以一个中间对象来承接Super.prototype 链路呢?

主要是为了避免修改 Sub.prototype 修改或覆盖了父类的原型链。

类式继承

es6 中 class 语法实现与上述相同的效果

class Super {
  constructor(arr) {
    this.superArr = arr || ['default'];
  }
  getSuperArr() {
    return this.superArr;
  }
}

class Sub extends Super {
  constructor(superArr, subArr) {
    super(superArr);
    this.subArr = subArr;
  }
  getSubArr() {
      return this.subArr;
  }
}

const instance1 = new Sub(['super1'], ['sub1']);
instance1.superArr.push('instance1');
instance1.getSuperArr(); // ["super1", "instance1"]
instance1.getSubArr();   // ["sub1"]

const instance2 = new Sub(['super2'], ['sub2']);
instance2.getSuperArr(); // ["super2"]
instance2.getSubArr();	 // ["sub2"]

instance1 instanceof Super; // true
instance1 instanceof Sub;   // true

如果你用 babel 转码成一下的话,会发现继承的方式和寄生组合继承实现差不多。
加了一些判断如是否 new,在 constructor 里是否调用 super 等。

例如:

class Super {
  constructor(val) {
    this.superVal = val;
  }
}

class Sub extends Super {
  constructor(val) {
  	super(val);
  }
}

转码后部分代码

  • 原型链继承
function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function");
  }
  // 原型链继承
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true,
      configurable: true
    }
  });
  // 把父类设为子类的原型对象
  if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
    o.__proto__ = p;
    return o;
  };
  return _setPrototypeOf(o, p);
}
  • 构造函数继承
// Sub 的原型是 Super
_getPrototypeOf(Sub).call(this, val);
  • super 是怎么实现的呢

如果你把 super 代码注释掉

class Super {
  constructor(val) {
    this.superVal = val;
  }
}

class Sub extends Super {
  constructor(val) {
  	// super(val);
  }
}

转码后的代码不会有调用父类构造函数的代码

_thisundefined

function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  }
  return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return self;
}

function Sub(val) {// super(val);
      var _this; 
      _classCallCheck(this, Sub);
      return _possibleConstructorReturn(_this);
}

拷贝继承

node util._extend 浅拷贝类似 Object.assign

function _extend(target, source) {
  // Don't do anything if source isn't an object
  if (source === null || typeof source !== 'object') return target;

  var keys = Object.keys(source);
  var i = keys.length;
  while (i--) {
    target[keys[i]] = source[keys[i]];
  }
  return target;
}

Vue Mixin 实现

用到了深、浅拷贝继承。
例如 datamethodscomponentsdirectives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
同名生命周期钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

参考

  1. javascript面向对象系列第三篇——实现继承的3种形式
  2. vue混入
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant