面向对象(七):ES6的class转ES5的源码阅读

1,029 阅读4分钟

JavaScript 对象系列

面向对象(一):认识对象

面向对象(二):认识JavaScript中对象的原型

面向对象(三):创建多个对象的方案

面向对象(四):掌握原型链

面向对象(五):ES6 类的基本使用

面向对象(六):JavaScript中的7种继承方式

面向对象(七):ES6的class转ES5的源码阅读

第七篇

最近重新学习了一下 JavaScript 的继承,在最后学到 ES6 的class实现继承的时候,有点好奇,class 语法转成 ES5 的代码应该是怎么样的呢?是属于 ES5 中的哪一种继承方式?

带着这个疑问,我使用 babel 转化了一下代码,认真看了一遍代码的实现过程,,写下此阅读笔记。

准备工作

可以自己进入 babel 官网,自己去试一试,看一下转化后的代码。

babel官网

16_03.png

准备工作完毕,正题就开始了。

简单的class

从简到难,一步一步的脚印走下去。

代码转换

简单的 class 代码

 // 两个属性(name,age),原型上一个方法(play)
 class Person {
   constructor(name, age) {
     this.name = name;
     this.age = age;
   }
   play() {
     console.log("play");
   }
 }

转化后的代码

 "use strict";
 ​
 function _classCallCheck(instance, Constructor) {
   if (!(instance instanceof Constructor)) {
     throw new TypeError("Cannot call a class as a function");
   }
 }
 ​
 function _defineProperties(target, props) {
   for (var i = 0; i < props.length; i++) {
     var descriptor = props[i];
     descriptor.enumerable = descriptor.enumerable || false;
     descriptor.configurable = true;
     if ("value" in descriptor) descriptor.writable = true;
     Object.defineProperty(target, descriptor.key, descriptor);
   }
 }
 ​
 function _createClass(Constructor, protoProps, staticProps) {
   if (protoProps) _defineProperties(Constructor.prototype, protoProps);
   if (staticProps) _defineProperties(Constructor, staticProps);
   Object.defineProperty(Constructor, "prototype", { writable: false });
   return Constructor;
 }
 ​
 var Person = /*#__PURE__*/ (function () {
   function Person(name, age) {
     _classCallCheck(this, Person);
 ​
     this.name = name;
     this.age = age;
   }
   _createClass(Person, [
     {
       key: "play",
       value: function play() {
         console.log("play");
       }
     }
   ]);
   return Person;
 })();

上面就是编译后代码,代码量不是很多,总共42代码

代码分析

第一步: 入口分析
 var Person = /*#__PURE__*/ (function () {})() // 立即执行函数,简单理解 function Person(){}

小知识点:

/*#__PURE__*/ 表示的是一个纯函数,没有副作用的函数。

作用:用在webpack的tree-traking时,如果没有使用该函数,就会直接移除掉该函数,用于缩小打包体积(优化)

第二步:分析 _classCallCheck函数
 /**
  * 描述:检查instance是否继承于Constructor,就是不能直接以 Constructor() 的形式调用
  * @param {Object} instance 
  * @param {Function} Constructor 
  */
 function _classCallCheck(instance, Constructor) {
   if (!(instance instanceof Constructor)) {
     throw new TypeError("Cannot call a class as a function");
   }
 }

就是检查类不能直接向普通函数的形式调用。

 class Person() {}
 ​
 const p = new Person() // 正确的调用方式
 Person() // 错误的调用方法
第三步:分析 _createClass函数
 /**
  * 描述:给target上添加方法
  * @param {Function} target 
  * @param {Array} props 
  */
 function _defineProperties(target, props) {
   for (var i = 0; i < props.length; i++) {
     var descriptor = props[i];
     descriptor.enumerable = descriptor.enumerable || false;
     descriptor.configurable = true;
     if ("value" in descriptor) descriptor.writable = true;
     Object.defineProperty(target, descriptor.key, descriptor);
   }
 }
 ​
 /**
  * 描述:给类添加方法。原型上(Constructor.prototype) 或者 自身(Constructor)
  * @param {Function} Constructor 构造函数的原型
  * @param {Array} protoProps 类方法的集合
  * @param {Array} staticProps 静态方法的集合
  */
 function _createClass(Constructor, protoProps, staticProps) {
   // 添加在原型
   if (protoProps) _defineProperties(Constructor.prototype, protoProps);
   // 添加在本身
   if (staticProps) _defineProperties(Constructor, staticProps);
   Object.defineProperty(Constructor, "prototype", { writable: false });
   return Constructor;
 }

大致目的就是,给类的原型上添加类方法,给类的本身上添加属性(静态方法)。

总结

最基础的代码,转化还是比较简单的,主要大致两步:

  1. 对类的调用进行检查。
  2. 给类添加方法和属性(静态方法)。

复杂的class

复杂的class,也就是说的继承

代码转化并分析

class代码

 // Student类 继承 Person类
 class Person {
   constructor(name, age) {
     this.name = name;
     this.age = age;
   }
   play() {
     console.log("play");
   }
 }
 ​
 class Student extends Person {
   constructor(name, age, address) {
     super(name, age);
     this.address = address;
   }
   running() {
     console.log("running");
   }
 }
 ​
 var s = new Student("copyer", 18, "cq");

这里转化后的代码,代码量比较的多,总共172行,简直是成倍的增长。这里呢,我就分为了两个文件来看,是代码的结构更加的清晰。

main.js: 解析后的入口文件

utils.js: 工具函数

main.js
 // 这里就不用分析了吧,在上面已经大致分析过了 (父类)
 var Person = /*#__PURE__*/ (function () {
   function Person(name, age) {
     _classCallCheck(this, Person);
 ​
     this.name = name;
     this.age = age;
   }
 ​
   _createClass(Person, [
     {
       key: "play",
       value: function play() {
         console.log("play");
       }
     }
   ]);
 ​
   return Person;
 })();
 ​
 // 子类
 // _Person: Person父类作为参数
 var Student = /*#__PURE__*/ (function (_Person) {
   // 使子类的原型指向一个中间对象,中间对象的原型指向父类的原型
   _inherits(Student, _Person);
   // 调用父类构造函数,但是不能直接调用,_classCallCheck会拦截报错
   var _super = _createSuper(Student);
 ​
   function Student(name, age, address) {
     var _this;
 ​
     _classCallCheck(this, Student);
 ​
     _this = _super.call(this, name, age);
     _this.address = address;
     return _this;
   }
 ​
   _createClass(Student, [
     {
       key: "running",
       value: function running() {
         console.log("running");
       }
     }
   ]);
 ​
   return Student;
 })(Person);
 ​
 var s = new Student("copyer", 18, "cq");

这里呢,父类 Person 在上面的简单 class 中已经分析了,就不在多述。

子类呢,其实也差不多,就是在function Student函数体中多执行两个函数,这两个函数既是重点,也是难点。

_inherits(): 实现继承

_createSuper(): 调用父类,实现父类函数体的逻辑复用

这两个函数存在utils.js中,一起看看吧。

utils.js

这里呢,代码量还是比较的多,封装的函数也比较的多,但是呢?大部分函数都是边界条件的判断,只看看主要的函数即可。(可以先不看下面这很长的代码,直接看下面的主要的函数分析,最后来看看边界函数)

 // typeof功能的加深
 function _typeof(obj) {
   "@babel/helpers - typeof";
   return (
     (_typeof =
       "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
         ? function (obj) {
             return typeof obj;
           }
         : function (obj) {
             return obj &&
               "function" == typeof Symbol &&
               obj.constructor === Symbol &&
               obj !== Symbol.prototype
               ? "symbol"
               : typeof obj;
           }),
     _typeof(obj)
   );
 }
 ​
 /**
  * 描述:使子类的原型指向父类的原型
  * @param {Function} subClass 子类构造函数
  * @param {Function} superClass 父类构造函数
  */
 function _inherits(subClass, superClass) {
   if (typeof superClass !== "function" && superClass !== null) {
     throw new TypeError("Super expression must either be null or a function");
   }
   // 原型式继承
   // subClass.prototype = {}; 
   // {}.__proto__ = superClass.prototype 
   subClass.prototype = Object.create(superClass && superClass.prototype, {
     constructor: { value: subClass, writable: true, configurable: true }
   });
   // 重新定义 subClass.prototype 不能被编写
   Object.defineProperty(subClass, "prototype", { writable: false });
   // 继承静态方法
   if (superClass) _setPrototypeOf(subClass, superClass);
 }
 ​
 /**
  * 描述:使子类的原型指向父类的原型(目的:o.__proto__ = p)
  * @param {Function} o 子类构造函数
  * @param {Function} p 父类构造函数
  */
 function _setPrototypeOf(o, p) {
   // 如果支持 Object.setPrototypeOf()
   _setPrototypeOf = Object.setPrototypeOf
     ? Object.setPrototypeOf.bind()
     : function _setPrototypeOf(o, p) {
         o.__proto__ = p;
         return o;
       };
   return _setPrototypeOf(o, p);
 }
 ​
 /**
  * 描述:创建一个类似super的函数,调用父类的构造函数
  * @param {Function} Derived 子类构造函数
  */
 function _createSuper(Derived) { // Student子类
   //  _isNativeReflectConstruct会检查Reflect.construct方法是否可用
   var hasNativeReflectConstruct = _isNativeReflectConstruct();
   return function _createSuperInternal() {
     // _getPrototypeOf方法用来获取Derived的原型,也就是Derived.__proto__
     // 本例来说:Student.__proto__ = Person
     var Super = _getPrototypeOf(Derived),
       result;
     if (hasNativeReflectConstruct) {
       // NewTarget = Student
       var NewTarget = _getPrototypeOf(this).constructor;
       
      // Reflect.construct的操作可以简单理解为:result = new Super(...arguments),
      // 第三个参数如果传了则作为新创建对象的构造函数,也就是result.__proto__ === NewTarget.prototype,否则默认为Super.prototype
       result = Reflect.construct(Super, arguments, NewTarget);
     } else {
       result = Super.apply(this, arguments);
     }
     return _possibleConstructorReturn(this, result);
   };
 }
 ​
 function _possibleConstructorReturn(self, call) {
   if (call && (_typeof(call) === "object" || typeof call === "function")) {
     return call;
   } else if (call !== void 0) {
     throw new TypeError(
       "Derived constructors may only return object or undefined"
     );
   }
   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 _isNativeReflectConstruct() {
   if (typeof Reflect === "undefined" || !Reflect.construct) return false;
   if (Reflect.construct.sham) return false;
   if (typeof Proxy === "function") return true;
   try {
     Boolean.prototype.valueOf.call(
       Reflect.construct(Boolean, [], function () {})
     );
     return true;
   } catch (e) {
     return false;
   }
 }
 ​
 function _getPrototypeOf(o) {
   _getPrototypeOf = Object.setPrototypeOf
     ? Object.getPrototypeOf.bind()
     : function _getPrototypeOf(o) {
         return o.__proto__ || Object.getPrototypeOf(o);
       };
   return _getPrototypeOf(o);
 }
 ​
 function _classCallCheck(instance, Constructor) {
   if (!(instance instanceof Constructor)) {
     throw new TypeError("Cannot call a class as a function");
   }
 }
 ​
 function _defineProperties(target, props) {
   for (var i = 0; i < props.length; i++) {
     var descriptor = props[i];
     descriptor.enumerable = descriptor.enumerable || false;
     descriptor.configurable = true;
     if ("value" in descriptor) descriptor.writable = true;
     Object.defineProperty(target, descriptor.key, descriptor);
   }
 }
 ​
 function _createClass(Constructor, protoProps, staticProps) {
   if (protoProps) _defineProperties(Constructor.prototype, protoProps);
   if (staticProps) _defineProperties(Constructor, staticProps);
   Object.defineProperty(Constructor, "prototype", { writable: false });
   return Constructor;
 }

这里就分析一下两个主要函数。

_inherits()函数
 /**
  * 描述:使子类的原型指向父类的原型
  * @param {Function} subClass 子类构造函数
  * @param {Function} superClass 父类构造函数
  */
 function _inherits(subClass, superClass) {
   if (typeof superClass !== "function" && superClass !== null) {
     throw new TypeError("Super expression must either be null or a function");
   }
   // 原型式继承
   // subClass.prototype = {}; 
   // {}.__proto__ = superClass.prototype 
   subClass.prototype = Object.create(superClass && superClass.prototype, {
     constructor: { value: subClass, writable: true, configurable: true }
   });
   // 重新定义 subClass.prototype 不能被编写
   Object.defineProperty(subClass, "prototype", { writable: false });
   // 继承静态方法
   if (superClass) _setPrototypeOf(subClass, superClass);
 }
 ​
 /**
  * 描述:使子类的原型指向父类的原型(目的:o.__proto__ = p)
  * @param {Function} o 子类构造函数
  * @param {Function} p 父类构造函数
  */
 function _setPrototypeOf(o, p) {
   // 如果支持 Object.setPrototypeOf()
   _setPrototypeOf = Object.setPrototypeOf
     ? Object.setPrototypeOf.bind()
     : function _setPrototypeOf(o, p) {
         o.__proto__ = p;
         return o;
       };
   return _setPrototypeOf(o, p);
 }

上面的注释其实已经写的差不多了。

上面实现继承的方式,就是原型式继承。使用Object.create()创建一个中间对象,是子类与父类之间建立连接

 // 第一步:创建中间对象
 // 中间对象obj,使其原型指向父类的函数原型(Object.create内部实现原理) 
 // obj.__proto__ = Person.prototype
 const obj = Object.create(superClass && superClass.prototype, {
     constructor: { value: subClass, writable: true, configurable: true }
 });
 ​
 // 第二步:子类的函数原型指向中间对象
 Student.prototype = obj
 ​
 var s = new Student()
 // 所以子类的实例对象创建出来,就形成了一条原型链了
 // s.__proto__ => obj.__proto__ => Person.prototype
 // 继承也就实现了

上面还有一个类的静态方法实现继承

 // 如果存在父类,就调用 _setPrototypeOf函数
 if (superClass) _setPrototypeOf(subClass, superClass);
 ​
 // setPrototypeOf函数目的:Student.__proto__ = Person
 // 构造函数也是对象,所以存在__proto__
 // 所以子类也可以通过原型链查找到父类的静态方法,也就是所谓的静态方法的继承
_createSuper函数
 /**
  * 描述:创建一个类似super的函数,调用父类的构造函数
  * @param {Function} Derived 子类构造函数
  */
 function _createSuper(Derived) { // Student子类
   //  _isNativeReflectConstruct会检查Reflect.construct方法是否可用
   var hasNativeReflectConstruct = _isNativeReflectConstruct();
   return function _createSuperInternal() {
     // call方式调用 this指向student实例对象
     // 本例来说:Student.__proto__ = Person
     var Super = _getPrototypeOf(Derived), // Person
       result;
     if (hasNativeReflectConstruct) {
       // NewTarget = Student
       var NewTarget = _getPrototypeOf(this).constructor;
       
      // Reflect.construct的操作可以简单理解为:result = new Super(...arguments),
      // 第三个参数如果传了则作为新创建对象的构造函数,也就是result.__proto__ === NewTarget.prototype,否则默认为Super.prototype
       result = Reflect.construct(Super, arguments, NewTarget);
     } else {
       result = Super.apply(this, arguments);
     }
     return _possibleConstructorReturn(this, result);
   };
 }

该函数就是实现了去调用父类的构造函数。

  1. 先判断是否支持 Reflect.construct方法
  2. 返回一个函数(_createSuperInternal)供外部调用;也就是main.js中的_super函数。
 return function _createSuperInternal() {
     var result;
     ... // 对result进行赋值,然后返回
     return result
 }
  1. 来看看对result的赋值
 // 第一步: 拿到父类 Student.__proto__ = Person 在静态方法继承的时候就已经实现了
 var Super = _getPrototypeOf(Derived), // Person
     
 // 第二步:调用Reflect.construct
 // this 是指向Student的,因为 _super函数是通过显示绑定call调用的
 var NewTarget = _getPrototypeOf(this).constructor;   // Student
 // Reflect.construct 在具体作用在上面已经说到
 result = Reflect.construct(Super, arguments, NewTarget);
 ​
 // result结果:
 // result 是 Super的实例对象,但是result.__proto__ = NewTarget.prototype

好了对class继承的转化大致分析完了,那么认为属于 ES5 中哪类继承呢?寄生组合式继承

寄生组合式继承

 // 父类
 function Person(name, age) {
   this.name = name;
   this.age = age;
 }
 Person.prototype.play() {
   console.log("play");
 }
 ​
 // 子类
 function Student(name, age, address) {
   Person.call(this, name, age); // 借用构造函数继承(关键步骤一)
   this.level = address;
 }
 Student.prototype = Object.create(Person.prototype); // 省去了第二次调用构造函数
 // 为了使子类的实例对象的类型正确
 Object.defineProperty(Student.prototype, "constructor", {
   enumerable: false,
   configurable: true,
   writable: true,
   value: Student,
 });
 Student.prototype.running() {
   console.log("running");
 }

也存在关键的两步:

  1. Person.call(this,name,age) 调用父类的构造函数,对应上面的 _createSuper 函数
  2. 也是使用Object.create() 来创建中间对象,使子类跟父类有所挂钩

如果上面有误,请多多指教。