Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript 新旧替换四:继承 #49

Open
XXHolic opened this issue Aug 3, 2019 · 0 comments
Open

JavaScript 新旧替换四:继承 #49

XXHolic opened this issue Aug 3, 2019 · 0 comments

Comments

@XXHolic
Copy link
Owner

XXHolic commented Aug 3, 2019

目录

引子

在一些书籍中花费了不少的篇幅进行讲述,新的语法中也出现了相关的关键字,实现的方式中也涉及到 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. 在静态方法中,指向父类。

参考资料

🗑️

最近去找了奥斯卡每届的最佳影片资料,又去看了下美版的《无间道风云》。表达的方式很不一样,可能是由于文化之间的差异。虽然有差异,但个人感觉最终突出的主题是一样的:出来混,迟早是要还的。

47-waste-poster

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant