js中的继承

前言

在js中, 对象就是键值对的集合.形如:{键1:值1,键2:值2}. 看起来很简单的样子, 但是提起笔想要在js中使用面向对象的方式来编程时, 就有点力不从心了.始终感觉对js的面向对象的继承理解不是很清楚.在红宝书中有很多的继承方式. 无奈理解不到位, 记也记不住. 最近看了gulp源码,发现它就是把Gulp写成了构造函数的形式.地址:Gulp

其中在Gulp内部调用了Undertaker.call(this), 又在Gulp外部写了util.inherits(Gulp, Undertaker).初看有点蒙啊(没错, 我就是这么菜).后来看了uitl模块的文的文档, 才知道util.inherits方法主要是用来:从一个构造函数中继承原型方法到另一个. 也就是说Gulp继承了Undertaker的原型.那么具体是如何继承的呢?不知道! 所以我就去github上面看了下node的一点源码.具体操作如下:

function inherits(ctor, superCtor) {

  if (ctor === undefined || ctor === null)
    throw new ERR_INVALID_ARG_TYPE('ctor', 'Function', ctor);

  if (superCtor === undefined || superCtor === null)
    throw new ERR_INVALID_ARG_TYPE('superCtor', 'Function', superCtor);

  if (superCtor.prototype === undefined) {
    throw new ERR_INVALID_ARG_TYPE('superCtor.prototype',
                                   'Object', superCtor.prototype);
  }
  Object.defineProperty(ctor, 'super_', {
    value: superCtor,
    writable: true,
    configurable: true
  });
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
}

再看一次这里的代码, 感觉有点奇怪啊.给ctor添加一个super_属性, 属性值为superCtor. 所以我们可以再ctor上的super_上获取superCtor.

再看最后一句:Object.setPrototypeOf(ctor.prototype, superCtor.prototype);所以执行完这句之后就变成了:Gulp.prototype.__proto__ = Undertaker.prototype, 我们可正常在Gulp.prototype上添加属性

再说说在Gulp内部调用的Undertaker.call(this), 由于Undertaker(用于控制gulp任务的执行顺序)也是一个构造函数,而此时this是gulp的实例对象, 所以这里是继承了Undertaker的构造函数

小结论: js里的继承是基于原型的,但是我们不能单独的使用原型继承, 因为会存在变量共享的问题, 在一处改变了值, 会意外的影响到其他地方.所以往往是和借用构造函数的继承方法一起使用的.

什么是借用构造函数的继承方法!!?? 请思考上文中提到的Undertaker.call(this). 没错这就是Gulp借用了Undertaker的构造函数, 然后在它内部使其this指向自己的实例执行.

接上面的小结论: js的继承需要继承两个内容:构造函数的继承和原型的继承, 最常用的继承方式是组合继承(后半句来自红宝书)

Object.create

上文中的util.inherits使用了Object.setPrototypeOf方法来实现原型的继承, 但是MDN上又说Object.setPrototypeOf的性能不好, 建议使用Object.create方法.所以这里题外话一下Object.create方法

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

所以util.inherits方法可以将Object.setPrototypeOf(ctor.prototype, superCtor.prototype),改写为:ctor.protytype=Object.create(superCtor.prototype).

至于node的源码中为啥不使用MDN说的Object.create方法而是使用性能比较差的Object.setPrototypeOf这就不得而知了. 只要我们搞懂了这里两种方法都可以使用不就好了吗?

ES6中的extends语法糖

虽然util.inherits方法提供了原型继承的方法, 但是它建议使用原生的ES6的extends语法糖来写.

我们就来看看ES6的写法是如何的:

const EventEmitter = require('events');

class MyStream extends EventEmitter {
  write(data) {
    this.emit('data', data);
  }
}

const stream = new MyStream();

stream.on('data', (data) => {
  console.log(`接收的数据:"${data}"`);
});
stream.write('使用 ES6');

写法还是挺简单的, 那么extends具体做了哪些事情呢?

先说两条链:

  1. 子类的__proto__属性表示构造函数的继承, 总指向父类
  2. 子类的prototype属性的__proto__属性表示方法的继承, 总是指向父类的prototype属性

所以上面的例子的链关系:

MyStream.__proto__ === EventEmitter//这是构造函数的__proto__链, 又可能在extends语法糖内部使用到了这条链, 因为extends的继承方式是先构造父类然后由子类对这个对象加工,最后由子类返回
MyStream.prototype._proto__===EventEmitter.prototype//这条链就是原型链的继承

关于extends由几点注意事项:

  1. class中定义的原型方法是不可枚举的
  2. class定义的方法的prototype属性是不可以更改其指向的

你可能感兴趣的:(js)