美文网首页
【重学前端】es6中class做了什么

【重学前端】es6中class做了什么

作者: 刘小胖LJX | 来源:发表于2020-05-19 21:04 被阅读0次

    前言

    在JavaScript中不论是es5之前利用function定义一个对象的构造方法还是es6利用class定义类,都可以实现对象的实例化

    • es5中定义一个对象
    // es5
    function Parent () {
      this.name = 'parent'
      this.age = 22
      this.work = function () {
        console.log('the ' + this.name + ' is working')
      }
    }
    
    Parent.prototype.speak = function () {
      console.log('hello!')
    }
    
    Parent.prototype.color = 'yello'
    
    • es6中定义一个对象
    class Parent {
    
      constructor () {
        this.name = 'parent'
        this.age = 22
      }
    
      work () {
        console.log('the ' + this.name + ' is working')
      }
    }
    
    Parent.prototype.speak = function () {
      console.log('hello!')
    }
    
    Parent.prototype.color = 'yello'
    
    class Son extends Parent {
      constructor () {
        super()
        this.sonName = 'son'
      }
    }
    
    const son = new Son()
    

    上述两种方法是等效的,都可以通过关键字new进行实例化

    es6的class做了什么

    我们利用babel转码工具对es6的class进行转码得到以下es5代码

    'use strict';
    
    // _createClass
    var _createClass = 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); 
        } 
      } 
      
      return function (Constructor, protoProps, staticProps) { 
        if (protoProps) defineProperties(Constructor.prototype, protoProps); 
        if (staticProps) defineProperties(Constructor, staticProps); 
        return Constructor; 
      }; 
    }();
    
    // _possibleConstructorReturn
    function _possibleConstructorReturn(self, call) { 
      if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
      } 
      
      return call && (typeof call === "object" || typeof call === "function") ? call : self; 
    }
    
    // _inherits
    function _inherits(subClass, superClass) { 
      if (typeof superClass !== "function" && superClass !== null) { 
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
      } 
      
      subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); 
      if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
    }
    
    // _classCallCheck
    function _classCallCheck(instance, Constructor) { 
      if (!(instance instanceof Constructor)) { 
        throw new TypeError("Cannot call a class as a function"); 
      }
     }
    
    // Parent 类声明
    var Parent = function () {
      function Parent() {
        _classCallCheck(this, Parent);
    
        this.name = 'parent';
        this.age = 22;
      }
    
      _createClass(Parent, [{
        key: 'work',
        value: function work() {
          console.log('the ' + this.name + ' is working');
        }
      }]);
    
      return Parent;
    }();
    
    Parent.prototype.speak = function () {
      console.log('hello!');
    };
    
    Parent.prototype.color = 'yello';
    
    // Son类声明(继承Parent)
    var Son = function (_Parent) {
      _inherits(Son, _Parent);
    
      function Son() {
        _classCallCheck(this, Son);
    
        var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));
    
        _this.sonName = 'son';
        return _this;
      }
    
      return Son;
    }(Parent);
    
    // Son类实例化
    var son = new Son();
    

    从以上代码可以看出,es6的class转码后分为以下几个部分

    • _createClass
    • _classCallCheck
    • _inherits继承
    • _possibleConstructorReturn(super 方法)
    • Parent定义
    • Parent原型方法及属性定义
    • Son类定义(继承Parent)
    • Son实例化

    _createClass 方法

    核心:通过一个闭包+立即执行函数,内部维护一个私有的defineProperties方法,遍历传入的自定义对象属性列表,调用Object.defineProperty给类添加方法,将静态方法添加到构造函数上,将非静态的方法添加到构造函数的原型对象上

    • 代码解析
    var _createClass = function () {
      /**
       * 创建class方法
       * @param {*} target 目标对象
       * @param {*} props 属性 Array<{ key: string, value: Function | String | Number | ...}>
       */
      function defineProperties(target, props) {
        // 遍历传入的属性列表,调用Object.defineProperty向目标对象上添加定义属性
        for (var i = 0; i < props.length; i++) { 
          var descriptor = props[i]; 
          // 属性描述符 enumerable 是否可枚举
          descriptor.enumerable = descriptor.enumerable || false; 
          // 属性描述符 configurable 该属性的属性描述符是否可改变
          descriptor.configurable = true; 
          // 属性描述符 writable 如果设置值, writable为true : value可以被赋值运算符修改
          if ("value" in descriptor) descriptor.writable = true; 
          Object.defineProperty(target, descriptor.key, descriptor); 
        } 
      } 
      
      // 暴露一个方法,接收参数
      /**
       * 
       * @param {*} Constructor // 目标对象
       * @param {*} protoProps // 非静态方法
       * @param {*} staticProps // 静态方法
       */
      return function (Constructor, protoProps, staticProps) {
        // 非静态方法,添加到构造函数(对象)的原型对象上 
        if (protoProps) defineProperties(Constructor.prototype, protoProps); 
        // 静态方法,添加到构造函数上
        if (staticProps) defineProperties(Constructor, staticProps); 
        return Constructor; 
      }; 
    }();
    
    • js对象的属性描述符(JSPropertyDescriptor)
    Field Name Description
    getter get 语法为属性绑定一个函数,每当查询该属性时便调用对应的函数,查询的结构为该函数的返回值
    setter 如果试着改变一个属性的值,那么对应的 setter 函数将被执行
    value 描述指定属性的值 , 可以是任何有效的 Javascript 值(函数 , 对象 , 字符串 ...).
    configurable 当且仅当该属性的 configurable 为 true 时,该属性 描述符 才能够被改变, 同时该属性也能从对应的对象上被删除.
    enumerable 描述指定的属性是否是 可枚举 的.
    writable 当且仅当该属性的 writabletrue 时, value 才能被赋值运算符改变。

    _classCallCheck方法解析

    /**
     * 防止类的构造函数以普通函数的方式调用
     * 判断构造函数是否存在于传入实例的原型链上(实例是构造函数的实例对象)
     * @param {*} instance 实例
     * @param {*} Constructor 构造函数
     */
    function _classCallCheck(instance, Constructor) {
      // instance 即传入的this对象,判断Constructor是否存在于this的原型链上
      if (!(instance instanceof Constructor)) { 
        throw new TypeError("Cannot call a class as a function"); 
      } 
    }
    

    new的原理

    /**
     * 创建一个new操作符
     * @param {*} Con 构造函数
     * @param  {...any} args 忘构造函数中传的参数
     */
      function createNew(Con, ...args) {
        let obj = {} // 创建一个对象,因为new操作符会返回一个对象
        Object.setPrototypeOf(obj, Con.prototype) // 将对象与构造函数原型链接起来
        // obj.__proto__ = Con.prototype // 等价于上面的写法
        let result = Con.apply(obj, args) // 将构造函数中的this指向这个对象,并传递参数
        return result instanceof Object ? result : obj
    }
    

    如果使用普通函数的调用方式,则调用时不会修改设置实例的原型,则以上判断就会出行false,就可以限制class 类以普通函数的方式进行调用

    _inherits实现继承

    /**
     * _inherits实现继承
     * @param {*} subClass 子类
     * @param {*} superClass 父类
     */
    function _inherits(subClass, superClass) { 
      // 判断父类,既不是函数方法也不是null的情况报错
      // null是所有对象的原型链的顶端
      if (typeof superClass !== "function" && superClass !== null) { 
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
      } 
      
      // 基于父类创建子类的原型对象
      subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); 
      // 判断父类及环境是否存在Object.setPrototypeOf api
      // 将父类设置为子类的原型 继承父类
      if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
    }
    

    _possibleConstructorReturn(super 方法)

    结合Son中的调用分析

    function Son() {
      _classCallCheck(this, Son);
    
      var _this = _possibleConstructorReturn(this, (Son.__proto__ || Object.getPrototypeOf(Son)).call(this));
    
      _this.sonName = 'son';
      return _this;
    }
    
    /**
     * _possibleConstructorReturn 修改this的指向
     * @param {*} self // 子类的this
     * @param {*} call // 子类的原型(父类)
     */
    function _possibleConstructorReturn(self, call) { 
      if (!self) { 
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 
      } 
      
      // 修改this指向,将子类的原型指向父类
      return call && (typeof call === "object" || typeof call === "function") ? call : self; 
    }
    

    Parent定义方法解析

    var Parent = function () {
      /**
       * 声明一个构造函数方法,定义Parent类
       */
      function Parent() {
        // 判断原型(防止以函数的方式调用)
        _classCallCheck(this, Parent);
        // 属性定义
        this.name = 'parent';
        this.age = 22;
      }
    
      // 调用_createClass方法定义Parent类上的方法
      _createClass(Parent, [{
        key: 'work',
        value: function work() {
          console.log('the ' + this.name + ' is working');
        }
      }]);
    
      // 返回声明的Parent类
      return Parent;
    }();
    
    // 定义原型链上的属性和方法
    Parent.prototype.speak = function () {
      console.log('hello!');
    };
    
    Parent.prototype.color = 'yello';
    

    补充:es5中的构造函数于es6中的class有什么区别

    • ES6语法定义的class类,不能被提前调用,无法执行预解析;ES5的function函数可以提前调用,但是只有属性没有方法
    • class内部会启用严格模式
      • class内部不可以使用未声明的对象
      • es5构造函数中可以
    • class的所有方法都是不可枚举的
    • class必须使用new关键词调用
    • class 的继承有两条继承链
      • 一条是: 子类的proto 指向父类
      • 另一条: 子类prototype属性的proto属性指向父类的prototype属性.
      • es6的子类可以通过proto属性找到父类,而es5的子类通过proto找到Function.prototype
    // es5
    function Super() {}
    function Sub() {}
    Sub.prototype = new  Super()
    Sub.prototype.constructor = Sub
    var sub = new Sub()
    console.log( Sub.__proto__ === Function.prototype) // true
    
    // es6
    class Super{}
    class Sub extends Super {}
    let sub = new Sub()
    console.log( Sub.__proto__ === Super)// true
    

    相关文章

      网友评论

          本文标题:【重学前端】es6中class做了什么

          本文链接:https://www.haomeiwen.com/subject/punbohtx.html