• JavaScript 新旧替换四:继承


    目录

    引子

    在一些书籍中花费了不少的篇幅进行讲述,新的语法中也出现了相关的关键字,实现的方式中也涉及到 JavaScript 中很重要的知识点。

    注意:JavaScript 中并没有类似 Java 中的类和继承,以下用“类”和“继承”是为了方便描述。

    上一篇 JavaScript 新旧替换三:参数转换

    ES5 方式

    实现继承功能的方式有多种,JavaScript 中常用的继承模式是组合继承,这里以此为例。

      function Fruit(name) {
        this.name = name;
      }
    
      Fruit.prototype.showName = function() {
        console.info("Fruit Name:", this.name);
      };
    
      function Apple(name, color) {
        Fruit.call(this, name);
    
        this.color = color;
      }
    
      Apple.prototype = new Fruit();
      // 矫正语义指向,并不是必需
      Apple.prototype.constructor = Apple;
    
      Apple.prototype.showColor = function() {
        console.info("Apple Color:", this.color);
      };
    
      var apple = new Apple("apple", "green");
      console.info("apple:", apple);
      apple.showName();
      apple.showColor();
    

    在组合继承中,主要的思路是:

    • 创建子类的时候,通过 Fruit.call(this, name) 绑定子类的 this,达到继承父类属性效果。
    • 将父类的实例赋给子类的 prototype 属性,子类的实例会沿着原型链查找,达到了继承父类方法的效果。

    ES2015+ 方式

    用新的语法实现上面的继承:

      class Fruit {
        constructor(name) {
          this.name = name;
        }
    
        showName() {
          console.info("Fruit Name:", this.name);
        }
      }
    
      class Apple extends Fruit {
        constructor(name, color) {
          super(name);
          this.color = color;
        }
    
        showColor() {
          console.info("Apple Color:", this.color);
        }
      }
    
      let apple = new Apple("apple", "green");
      console.info("apple:", apple);
      apple.showName();
      apple.showColor();
    

    在书写形式上有很大的变化,但实际上也是通过原型链实现,通过 Babel 转译为 ES5 看下是怎样的实现思路。

    首先说明一下 Babel 中转译有两种模式:normal 和 loose。

    • loose 模式下生成更简单、兼容性更好的代码。
    • normal 模式下生成符合标准语义的代码。

    选择 normal 模式的转译更加合适,先来看下 Fruit 类转译后的实现:

    "use strict";
    /**
     * Symbol.hasInstance 属性,指向一个内部方法。
     * 当其它对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。
     * 比如,foo instanceof Foo 在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。
     */
    function _instanceof(left, right) {
      if (
        right != null &&
        typeof Symbol !== "undefined" &&
        right[Symbol.hasInstance]
      ) {
        return !!right[Symbol.hasInstance](left);
      } else {
        return left instanceof right;
      }
    }
    
    // 防止直接当方法调用
    function _classCallCheck(instance, Constructor) {
      // 判断 instance 是否为 Constructor 的实例
      if (!_instanceof(instance, Constructor)) {
        throw new TypeError("Cannot call a class as a function");
      }
    }
    
    /**
     *
     * Object.defineProperty 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
     *
     */
    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);
      return Constructor;
    }
    
    var Fruit =
      /*#__PURE__*/
      (function() {
        function Fruit(name) {
          _classCallCheck(this, Fruit);
    
          this.name = name;
        }
    
        _createClass(Fruit, [
          {
            key: "showName",
            value: function showName() {
              console.info("Fruit Name:", this.name);
            }
          }
        ]);
    
        return Fruit;
      })();
    

    在上面转译的代码中,处理的主要思路有:

    • _classCallCheck 方法判断调用的方式,防止 Fruit() 这样直接调用。
    • _createClass 方法在 prototype 上添加公用方法,在 Fruit 上添加静态方法。

    这种方式跟组合使用构造函数模式和原型模式创建对象很相似,不过表达的语义不太一样。

    再来看下继承转译后的代码:

    "use strict";
    
    function _typeof(obj) {
      if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
        _typeof = function _typeof(obj) {
          return typeof obj;
        };
      } else {
        _typeof = function _typeof(obj) {
          return obj &&
            typeof Symbol === "function" &&
            obj.constructor === Symbol &&
            obj !== Symbol.prototype
            ? "symbol"
            : typeof obj;
        };
      }
      return _typeof(obj);
    }
    
    function _possibleConstructorReturn(self, call) {
      if (call && (_typeof(call) === "object" || typeof call === "function")) {
        return call;
      }
      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 _getPrototypeOf(o) {
      _getPrototypeOf = Object.setPrototypeOf
        ? Object.getPrototypeOf
        : function _getPrototypeOf(o) {
            return o.__proto__ || Object.getPrototypeOf(o);
          };
      return _getPrototypeOf(o);
    }
    
    /**
     *
     * Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
     * @param {*} 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 = Object.create(superClass && superClass.prototype, {
        constructor: { value: subClass, writable: true, configurable: true }
      });
      // 没有这一步的话,就拿不到父类的属性
      if (superClass) _setPrototypeOf(subClass, superClass);
    }
    
    // 设置对象原型
    function _setPrototypeOf(o, p) {
      _setPrototypeOf =
        Object.setPrototypeOf ||
        function _setPrototypeOf(o, p) {
          o.__proto__ = p;
          return o;
        };
      return _setPrototypeOf(o, p);
    }
    
    function _instanceof(left, right) {
      if (
        right != null &&
        typeof Symbol !== "undefined" &&
        right[Symbol.hasInstance]
      ) {
        return !!right[Symbol.hasInstance](left);
      } else {
        return left instanceof right;
      }
    }
    
    function _classCallCheck(instance, Constructor) {
      if (!_instanceof(instance, 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);
      return Constructor;
    }
    
    var Fruit =
      /*#__PURE__*/
      (function() {
        function Fruit(name) {
          _classCallCheck(this, Fruit);
    
          this.name = name;
        }
    
        _createClass(Fruit, [
          {
            key: "showName",
            value: function showName() {
              console.info("Fruit Name:", this.name);
            }
          }
        ]);
    
        return Fruit;
      })();
    
    var Apple =
      /*#__PURE__*/
      (function(_Fruit) {
        _inherits(Apple, _Fruit);
    
        function Apple(name, color) {
          var _this;
    
          _classCallCheck(this, Apple);
    
          // _getPrototypeOf(Apple).call(this, name) 调用的实际是父类的函数,注意没有使用 new ,返回的是默认的 undefined
          _this = _possibleConstructorReturn(
            this,
            _getPrototypeOf(Apple).call(this, name)
          );
          _this.color = color;
          return _this;
        }
    
        _createClass(Apple, [
          {
            key: "showColor",
            value: function showColor() {
              console.info("Apple Color:", this.color);
            }
          }
        ]);
    
        return Apple;
      })(Fruit);
    
      let apple = new Apple("apple", "green");
      console.info("apple:", apple);
      apple.showName();
      apple.showColor();
    

    在上面转译的代码中,处理的主要思路是:

    1. _inherits 方法基于父类的 prototype 创建了一个新的对象,赋给了子类的 prototype。还将子类的 __proto__ 指向了父类,为的是继承父类的属性。
    2. 直接在子类的 prototype 上定义子类自己的方法。
    3. 在执行子类的构造函数时,修改了 this 的值。

    ES2015+ 方式语法点

    class

    ES2015 引入了类的概念,通过 class 关键字可以定义类。类有下面一些特点:

    1. 必须要使用 new 调用,否则会报错。
    2. 类没有提升,这种规定的原因与继承有关,必须保证子类在父类之后定义。
    3. 类中默认使用的是严格模式。
    4. 不想被继承的方法加上 static 关键字。
    5. 类中 this 指向类的实例。

    constructor

    constructor 是构造方法,通过 new 命令生成对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个空的 constructor 方法会被默认添加。

    class A {}
    let obj = new A();
    console.info(obj.constructor === A.prototype.constructor);
    

    extends

    class 继承通过 extends 实现。继承时,类中构造函数必须要执行 super 方法,否则创建实例的时候会报错。

    class A {}
    class B extends A {
      constructor() {
    
      }
    }
    
    let obj = new B();
    // VM55958:3 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
    

    子类中如果没有显式的写出构造方法,会默认的添加。

    class A {}
    class B extends A {}
    // 等同于
    class B extends A {
      constructor(...args) {
        super(...args)
      }
    }
    

    super

    super 关键字可以当做函数或对象使用。使用 super 的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。

    函数使用

    1. 代表父类的构造函数。
    2. 只能在子类的构造函数中使用,其它地方会报错。

    对象使用

    1. 在普通方法中,指向父类的原型对象。
    2. 在静态方法中,指向父类。

    参考资料

  • 相关阅读:
    ASP标准控件的重要性
    jndi的疑惑 转
    jms中topic和queue的区别
    JNDI解析
    javascript document 对象属性(转)
    SAX解析xml全解
    java路径解析
    深度学习之美(张玉宏)——第三章 机器学习三重门
    centos7 源码编译安装 php
    centos7 源码编译安装 nginx
  • 原文地址:https://www.cnblogs.com/thyshare/p/12935154.html
Copyright © 2020-2023  润新知