从babel和typescript看class私有属性实现

831 阅读2分钟

私有属性目前提案已经进行到了Stage 3 阶段,即将落地所以还是很有必要了解下,本文不讲解私有属性如何使用以及需要注意的事项,而是侧重于babeltypescript是怎么实现私有属性的,具体使用方法可以看一下阮一峰的 ES6 入门

在看这篇文章之前可以阅读我之前写的 从 babel 看 class(上)从 babel 看 class(下)

示例文件

下面的代码都是通过这个例子的演化而来

class Foo {
  #name = 'zhangsan';
  age = 17;

  getName() {
    return this.#name;
  }
  get #x() {
    return this.#name;
  }

  set #x(value) {
    this.#name = value;
  }
}

babel

function _classPrivateFieldSet(receiver, privateMap, value) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError('attempted to set private field on non-instance');
  }
  if (descriptor.set) {
    descriptor.set.call(receiver, value);
  } else {
    if (!descriptor.writable) {
      throw new TypeError('attempted to set read only private field');
    }
    descriptor.value = value;
  }
  return value;
}

function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError('attempted to get private field on non-instance');
  }
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}

var _name = new WeakMap();

var _x = new WeakMap();

var Foo = /*#__PURE__*/ (function () {
  function Foo() {
    _classCallCheck(this, Foo);

    _x.set(this, {
      get: _get_x,
      set: _set_x,
    });

    _name.set(this, {
      writable: true,
      value: 'zhangsan',
    });

    _defineProperty(this, 'age', 17);
  }

  _createClass(Foo, [
    {
      key: 'getName',
      value: function getName() {
        return _classPrivateFieldGet(this, _name);
      },
    },
  ]);

  return Foo;
})();

var _get_x = function _get_x() {
  return _classPrivateFieldGet(this, _name);
};

var _set_x = function _set_x(value) {
  _classPrivateFieldSet(this, _name, value);
};

为了保持简洁,我直接去掉了不必要的代码,这里 babel 的执行顺序如下

  1. 首先通过_classCallCheck方法检查是否为new调用,不是直接抛出错误
  2. 通过new WeakMap来存储私有属性
  3. 通过_defineProperty来设置实例属性
  4. 通过_createClass来设置方法和静态方法

所以这里已经可以得出结论了,babel 的私有属性方法实现就是通过WeakMap来实现的

function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = privateMap.get(receiver);
  if (!descriptor) {
    throw new TypeError('attempted to get private field on non-instance');
  }
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}

上面的方法中额外进行了一些判断,一个个看

  1. 执行privateMap.get

进行判断的原因是因为 this 可能会丢失,例如通过解构

const f = new Foo();
const { getName } = f;
getName();
  1. descriptor.get判断

    是因为私有属性可以是getset这种形式出现

_classPrivateFieldSet方法跟get获取流程类似就不讲解了。

typescript

var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};
var _name;
class Foo {
    constructor() {
        _name.set(this, 'zhangsan');
        this.age = 17;
    }
    getName() {
        return this.;
    }
    get () { return __classPrivateFieldGet(this, _name); }
    set (value) {
        __classPrivateFieldSet(this, _name, value);
    }
}
_name = new WeakMap();

这里直接把代码编译到es2015我们主要看私有属性在 ts 中怎么实现,而 class 转化降级的方法就是降级成函数所以略过。

_name.set__classPrivateFieldGet__classPrivateFieldSet从这三处代码我们可以得出结论,ts 也是通过WeakMap来实现的。

实现的过程跟babel大同小异,就是根据this和变量名进行读取和设置

最后

上面的分析到这里就结束了,如果有什么错误欢迎指出,如果对你有帮助欢迎star