继承行为在 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 相关文章推荐
URL编码转换,escape() encodeURI() encodeURIComponent()
Dec 27 Javascript
给网站上的广告“加速”显示的方法
Apr 08 Javascript
用JavaScript调用WebService的示例
Apr 07 Javascript
Javascript跨域请求的4种解决方式
Mar 17 Javascript
Node.js实现的简易网页抓取功能示例
Dec 05 Javascript
使用jquery清空、复位整个输入域
Apr 02 Javascript
js完美解决IE6不支持position:fixed的bug
Apr 24 Javascript
两种方法解决javascript url post 特殊字符转义 + & #
Apr 13 Javascript
javascript断点调试心得分享
Apr 23 Javascript
javascript 删除数组元素和清空数组的简单方法
Feb 24 Javascript
详解如何使用babel进行es6文件的编译
May 29 Javascript
vue-cli3 热更新配置操作
Sep 18 Javascript
在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
老照片 - 几十年前的收音机与人
2021/03/02 无线电
php实现的返回数据格式化类实例
2014/09/22 PHP
PHP实现支持GET,POST,Multipart/form-data的HTTP请求类
2014/09/24 PHP
thinkphp的静态缓存用法分析
2014/11/29 PHP
thinkPHP框架RBAC实现原理分析
2019/02/01 PHP
PHP+mysql防止SQL注入的方法小结
2019/04/27 PHP
锋利的jQuery 要点归纳(三) jQuery中的事件和动画(上:事件篇)
2010/03/24 Javascript
推荐8款jQuery轻量级树形Tree插件
2014/11/12 Javascript
input输入框鼠标焦点提示信息
2015/03/17 Javascript
js实现透明度渐变效果的方法
2015/04/10 Javascript
Bootstrap每天必学之日期控制
2016/03/07 Javascript
require、backbone等重构手机图片查看器
2016/11/17 Javascript
详谈angularjs中路由页面强制更新的问题
2017/04/24 Javascript
详解webpack+es6+angular1.x项目构建
2017/05/02 Javascript
基于JavaScript实现抽奖系统
2018/01/16 Javascript
在vue-cli搭建的项目中增加后台mock接口的方法
2018/04/26 Javascript
ES6函数和数组用法实例分析
2020/05/23 Javascript
vue中的v-model原理,与组件自定义v-model详解
2020/08/04 Javascript
JavaScript 实现下雪特效的示例代码
2020/09/09 Javascript
python清除指定目录内所有文件中script的方法
2015/06/30 Python
python简单线程和协程学习心得(分享)
2017/06/14 Python
利用Python如何实现数据驱动的接口自动化测试
2018/05/11 Python
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
基于python实现百度翻译功能
2019/05/09 Python
Django项目后台不挂断运行的方法
2019/08/31 Python
Python 日期与时间转换的方法
2020/08/01 Python
利用python汇总统计多张Excel
2020/09/22 Python
Python爬虫代理池搭建的方法步骤
2020/09/28 Python
利用python批量爬取百度任意类别的图片的实现方法
2020/10/07 Python
用python爬虫批量下载pdf的实现
2020/12/01 Python
python pyg2plot的原理知识点总结
2021/02/28 Python
HTML5 Notification(桌面提醒)功能使用实例
2014/03/17 HTML / CSS
白兰氏健康Mall:BRAND’S
2017/11/13 全球购物
人力资源专业推荐信
2013/11/29 职场文书
一份教室追逐打闹的检讨书
2014/09/27 职场文书
浅谈Python numpy创建空数组的问题
2021/05/25 Python