继承行为在 ES5 与 ES6 中的区别详解


Posted in Javascript onDecember 24, 2019

笔者注:一句话引发的基础知识回炉,基础不扎实,还要什么自行车

最近在看 React 方面的一些文章时,看到了这样一个问题,「为什么每个 class 中都要写 super, super 是做什么的?」, 刚看到这个问题时,直接就想到了继承行为在 javascript 中的表现。后面作者的一句话「super 不可以省略,省略的话会报错」。当时脑海中蹦出来一个念头,这个同学是不是写错了,super 不就是用来完成调用父类构造函数,将父类的实例属性挂在到 this 上吗?为什么不写还会报错?

后来自己亲自写了一个 Demo 尝试了一下,还真是会报错,到底是哪里出了问题,找到了阮老师的教程又打开仔细看了一遍,发现里面还真是有这样一句话:

子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。

原来如此,ES6 中 this 对象的构造方式发生了变化。

ES5 中的继承

// Shape - 父类(superclass)
function Shape() {
 this.x = 0;
 this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
 this.x += x;
 this.y += y;
 console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
 Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
 rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
 rect instanceof Shape); // true
rect.move(1, 1); // Outputs, 'Shape moved.'

如上所示: 展示了一个 ES5 中实现单继承的例子,在《Javascript 高级程序设计》一书中,给这种继承方式定义为「寄生组合式继承」。不管什么形式,什么命名,在 ES5 中实现继承始终就是要坚持一个原则:将实例属性放在构造函数中挂在this上,将一些方法属性挂在原型对象上,子类可共享。 上面这种继承方式的关键在于两点:

  1. 子类构造函数通过 apply 或者 call 的方式运行父类的构造函数,此举将父类的实例属性挂在子类的 this 对象上
  2. 以父类的原型对象为基础,与子类的原型对象之间建立原型链关系,使用了 Object.create,本质在于 Child.prototype.__proto === Parent.prototype;

ES6 中的继承

class Point {
 constructor(x, y) {
  this.x = x;
  this.y = y;
 }

 toString() {
  return '(' + this.x + ', ' + this.y + ')';
 }
}

class ColorPoint extends Point {
 constructor(x, y, color) {
  super(x, y); // 调用父类的constructor(x, y)
  this.color = color;
 }

 toString() {
  return this.color + ' ' + super.toString(); 
 }
}

ES6 中的继承使用到了 extends 关键字,function 也变成了 class 关键字。class 的本质还是一个语法糖,这个大家都会脱口而出,但是在继承机制这里到底是如何做到的,我们看一下 babel 在此处是如何帮我们转译的,

var ColorPoint =
/*#__PURE__*/
function (_Point) {
 _inherits(ColorPoint, _Point);

 function ColorPoint(x, y, color) {
  var _this;

  _classCallCheck(this, ColorPoint);

  _this = _possibleConstructorReturn(this, _getPrototypeOf(ColorPoint).call(this, x, y)); // 调用父类的constructor(x, y)

  _this.color = color;
  return _this;
 }

 _createClass(ColorPoint, [{
  key: "toString",
  value: function toString() {
   return this.color + ' ' + _get(_getPrototypeOf(ColorPoint.prototype), "toString", this).call(this);
  }
 }]);

 return ColorPoint;
}(Point);

如上是经过babel转译后的代码,有几个关键点:

一、 _inherits()

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);
}

首先完成extends对象的校验,必须是function 或者null,否则报错。其次完成以下事情:

ColorPoint.__proto__ === Point;
ColorPoint.prototype.__proto__ === Point.prototype;

二、 ColorPoint 构造函数中 _classCallCheck(), _possibleConstructorReturn()

function _classCallCheck(instance, Constructor) {
 if (!_instanceof(instance, Constructor)) {
   throw new TypeError("Cannot call a class as a function");
 }
}

主要是用来检测构造函数不能直接调用,必须是通过new的方式来调用。

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

调用父类的构造函数,初始化一些实例属性,并将this返回。使用该返回的this赋值给子类的this对象,子类通过这一步返回的this对象,再该基础之上在添加一些实例属性。

这就是最大的不同之处。如果不经历这一步,子类没有this对象,一旦操作一个不存在的this对象就会报错。

三、 _createClass()

function _createClass(Constructor, protoProps, staticProps) {
 if (protoProps) _defineProperties(Constructor.prototype, protoProps);
 if (staticProps) _defineProperties(Constructor, staticProps);
 return Constructor;
}

最后一步完成原型属性与静态属性的挂载,如果是原型属性,挂在在Constructor上的prototype上,如果是静态属性或者静态方法,则挂在Constuctor 上。

总结

基础知识要打牢,不是为了面试,前期打不劳,后面很多事情就会变的模棱两可,别人问到的时候,就会是「可能」、「也许」。不积跬步何以至千里 ,加油。

参考链接

http://es6.ruanyifeng.com/#docs/class-extends

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-2&targets=&browsers=&builtIns=false&debug=false&code_lz=Q

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQUERY 对象与DOM对象之两者相互间的转换
Apr 27 Javascript
jQuery与其它库冲突的解决方法
Jun 25 Javascript
jQuery学习笔记之jQuery.extend(),jQuery.fn.extend()分析
Jun 09 Javascript
JavaScript中常用的六种互动方法示例
Mar 13 Javascript
Ext JS框架程序中阻止键盘触发回退或者刷新页面的代码分享
Jun 07 Javascript
详解js前端代码异常监控
Jan 11 Javascript
jQuery通过改变input的type属性实现密码显示隐藏切换功能
Feb 08 Javascript
vue做移动端适配最佳解决方案(亲测有效)
Sep 04 Javascript
vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单
Nov 29 Javascript
jQuery使用bind动态绑定事件无效的处理方法
Dec 11 jQuery
vue实现多条件和模糊搜索功能
May 28 Javascript
jquery中attr、prop、data区别与用法分析
Sep 25 jQuery
在JavaScript中实现链式调用的实现
Dec 24 #Javascript
vue实现分页加载效果
Dec 24 #Javascript
微信小程序如何获取地址
Dec 24 #Javascript
浅析vue-router中params和query的区别
Dec 24 #Javascript
JavaScript实现英语单词题库
Dec 24 #Javascript
iSlider手机端图片滑动切换插件使用详解
Dec 24 #Javascript
微信小程序自定义模态弹窗组件详解
Dec 24 #Javascript
You might like
Ajax+PHP快速上手及简单应用说明
2013/07/24 PHP
php文件夹与文件目录操作函数介绍
2013/09/09 PHP
实现php删除链表中重复的结点
2018/09/27 PHP
Javascript 函数对象的多重身份
2009/06/28 Javascript
解读JavaScript代码 var ie = !-[1,] 最短的IE判定代码
2011/05/28 Javascript
在js文件中写el表达式取不到值的原因及解决方法
2013/12/23 Javascript
javascript实现 百度翻译 可折叠的分享按钮列表
2015/03/12 Javascript
jquery实现移动端点击图片查看大图特效
2020/09/11 Javascript
基于BootStrap实现简洁注册界面
2017/07/20 Javascript
完美解决手机网页中输入框被输入法遮挡的问题
2017/12/19 Javascript
node中的cookie的具体使用
2018/09/13 Javascript
react 兄弟组件如何调用对方的方法示例
2018/10/23 Javascript
微信JS-SDK updateAppMessageShareData安卓不能自定义分享详解
2019/03/29 Javascript
vue中的v-model原理,与组件自定义v-model详解
2020/08/04 Javascript
在vue中使用image-webpack-loader实例
2020/11/12 Javascript
python各种语言间时间的转化实现代码
2016/03/23 Python
python 第三方库的安装及pip的使用详解
2017/05/11 Python
Python 私有函数的实例详解
2017/09/11 Python
Django+Ajax+jQuery实现网页动态更新的实例
2018/05/28 Python
Python采集猫眼两万条数据 对《无名之辈》影评进行分析
2018/12/05 Python
对python实现二维函数高次拟合的示例详解
2018/12/29 Python
基于Python实现签到脚本过程解析
2019/10/25 Python
Pandas操作CSV文件的读写实现方法
2019/11/13 Python
python——全排列数的生成方式
2020/02/26 Python
基于python判断字符串括号是否闭合{}[]()
2020/09/21 Python
亚瑟士美国官网:ASICS美国
2017/02/01 全球购物
Sunglass Hut巴西网上商店:男女太阳镜
2020/10/04 全球购物
杭州-飞时达软件有限公司.net笔面试
2012/04/28 面试题
.NET概念性的面试题
2012/02/29 面试题
C#笔试题和英文面试题
2013/02/07 面试题
消防安全承诺书
2014/05/22 职场文书
俞敏洪一分钟演讲稿
2014/08/26 职场文书
银行催款通知书
2015/04/17 职场文书
全陪导游词开场白
2015/05/29 职场文书
求职信:会计求职的写作技巧
2019/04/24 职场文书
elementui的el-popover修改样式不生效的解决
2021/06/30 Javascript