Babel to Class之私有属性(3)
前言🔗
这是《Babel to Class》系列的第三篇
这个系列主要讲 Class 的普通写法、继承、私有属性和原生构造函数继承 babel 是如何实现这一过程的。
注:文章顺序存在关联,请按照顺序阅读。
前置知识点🔗
WeakMap🔗
WeakMap 是 ES6 新加入的数据结构,它与 map 类似,但是键为弱引用。
弱引用就是指不计入垃圾回收次数,当引用被垃圾回收
WeakMap对应键值自动消失。
更多内容请参考文档
WeakSet🔗
与 WeakMap 类似,不同在于 WeakSet 只有键名,没有键值。
更多内容请参考文档
Class 私有属性🔗
之前 Class 要实现私有属性只能通过,Symbol 或者外部变量来实现,不过都各有缺点,现在新的提案 proposal-private-methods 添加了私有属性和私有方法,使用方式很简单,直接#<name>即可。
JSclass Test {
  #age = 12;
  #getage() {}
}
私有属性只能在构造函数内部调用,如果在非内部调用则抛出异常(在 static 上调用也是可以的)。
示例文件🔗
JSclass Test {
  #age = 17;
  static #name = 'zhangsan';
  #getAge() {
    console.log(this.#age);
  }
  static #getName() {
    console.log(this.#name);
  }
  all() {
    this.#getAge();
    Test.#getName();
  }
}
const test = new Test();
test.all();
以下内容根据此文件进行讲解。
babel🔗
JS'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;
}
function _classPrivateMethodInitSpec(obj, privateSet) {
  _checkPrivateRedeclaration(obj, privateSet);
  privateSet.add(obj);
}
function _classPrivateFieldInitSpec(obj, privateMap, value) {
  _checkPrivateRedeclaration(obj, privateMap);
  privateMap.set(obj, value);
}
function _checkPrivateRedeclaration(obj, privateCollection) {
  if (privateCollection.has(obj)) {
    throw new TypeError(
      'Cannot initialize the same private elements twice on an object'
    );
  }
}
function _classStaticPrivateMethodGet(receiver, classConstructor, method) {
  _classCheckPrivateStaticAccess(receiver, classConstructor);
  return method;
}
function _classPrivateMethodGet(receiver, privateSet, fn) {
  if (!privateSet.has(receiver)) {
    throw new TypeError('attempted to get private field on non-instance');
  }
  return fn;
}
function _classStaticPrivateFieldSpecGet(
  receiver,
  classConstructor,
  descriptor
) {
  _classCheckPrivateStaticAccess(receiver, classConstructor);
  _classCheckPrivateStaticFieldDescriptor(descriptor, 'get');
  return _classApplyDescriptorGet(receiver, descriptor);
}
function _classCheckPrivateStaticFieldDescriptor(descriptor, action) {
  if (descriptor === undefined) {
    throw new TypeError(
      'attempted to ' + action + ' private static field before its declaration'
    );
  }
}
function _classCheckPrivateStaticAccess(receiver, classConstructor) {
  if (receiver !== classConstructor) {
    throw new TypeError('Private static access of wrong provenance');
  }
}
function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = _classExtractFieldDescriptor(receiver, privateMap, 'get');
  return _classApplyDescriptorGet(receiver, descriptor);
}
function _classExtractFieldDescriptor(receiver, privateMap, action) {
  if (!privateMap.has(receiver)) {
    throw new TypeError(
      'attempted to ' + action + ' private field on non-instance'
    );
  }
  return privateMap.get(receiver);
}
function _classApplyDescriptorGet(receiver, descriptor) {
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}
var _age = /*#__PURE__*/ new WeakMap();
var _getAge = /*#__PURE__*/ new WeakSet();
var Test = /*#__PURE__*/ (function () {
  debugger;
  function Test() {
    _classCallCheck(this, Test);
    _classPrivateMethodInitSpec(this, _getAge);
    _classPrivateFieldInitSpec(this, _age, {
      writable: true,
      value: 17,
    });
  }
  _createClass(Test, [
    {
      key: 'all',
      value: function all() {
        _classPrivateMethodGet(this, _getAge, _getAge2).call(this);
        _classStaticPrivateMethodGet(Test, Test, _getName).call(Test);
      },
    },
  ]);
  return Test;
})();
function _getAge2() {
  console.log(_classPrivateFieldGet(this, _age));
}
function _getName() {
  console.log(_classStaticPrivateFieldSpecGet(this, Test, _name));
}
var _name = {
  writable: true,
  value: 'zhangsan',
};
var test = new Test();
test.all();
以下方法出现在之前讲解过的篇幅中就不再赘述
_classCallCheck_defineProperties_createClass
_checkPrivateRedeclaration🔗
JSfunction _checkPrivateRedeclaration(obj, privateCollection) {
  if (privateCollection.has(obj)) {
    throw new TypeError(
      'Cannot initialize the same private elements twice on an object'
    );
  }
}
验证 WeakSet 上是否存在构造函数,如果存在直接抛出错误。
_classPrivateMethodInitSpec🔗
JSfunction _classPrivateMethodInitSpec(obj, privateSet) {
  _checkPrivateRedeclaration(obj, privateSet);
  privateSet.add(obj);
}
在 WeakSet 上添加构造函数。
_classPrivateFieldInitSpec🔗
JSfunction _classPrivateFieldInitSpec(obj, privateMap, value) {
  _checkPrivateRedeclaration(obj, privateMap);
  privateMap.set(obj, value);
}
在 WeakMap 上添加私有属性(属性描述符形式)。
_classPrivateMethodGet🔗
JSfunction _classPrivateMethodGet(receiver, privateSet, fn) {
  if (!privateSet.has(receiver)) {
    throw new TypeError('attempted to get private field on non-instance');
  }
  return fn;
}
验证是否在构造函数内部调用,如果外部则抛出错误 试图在非实例上获取私有字段。
_classCheckPrivateStaticAccess🔗
JSfunction _classCheckPrivateStaticAccess(receiver, classConstructor) {
  if (receiver !== classConstructor) {
    throw new TypeError('Private static access of wrong provenance');
  }
}
判断静态属性的来源是否合法,例如
JSclass A {
  static #name() {}
}
class Test {
  static #name() {
    A.#name();
  }
  all() {
    Test.#name();
  }
}
const test = new Test();
test.all();
我们在 Test 获取 A.#name 这种情况下就要抛出异常。
_classStaticPrivateMethodGet🔗
JSfunction _classStaticPrivateMethodGet(receiver, classConstructor, method) {
  _classCheckPrivateStaticAccess(receiver, classConstructor);
  return method;
}
验证通过返回 method。
_classExtractFieldDescriptor🔗
JSfunction _classExtractFieldDescriptor(receiver, privateMap, action) {
  if (!privateMap.has(receiver)) {
    throw new TypeError(
      'attempted to ' + action + ' private field on non-instance'
    );
  }
  return privateMap.get(receiver);
}
验证 WeakMap 是否有构造函数,如果没有则抛出非法获取实例错误,否则返回对应的 key。
_classApplyDescriptorGet🔗
JSfunction _classApplyDescriptorGet(receiver, descriptor) {
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}
返回私有属性,根据属性描述符,如果是 get、set 这种形式储存,直接调用 get 函数 返回,否则返回 value。
_classCheckPrivateStaticFieldDescriptor🔗
JSfunction _classCheckPrivateStaticFieldDescriptor(descriptor, action) {
  if (descriptor === undefined) {
    throw new TypeError(
      'attempted to ' + action + ' private static field before its declaration'
    );
  }
}
判断属性描述符是否存在,不存在抛出异常。
_classStaticPrivateFieldSpecGet🔗
JSfunction _classStaticPrivateFieldSpecGet(
  receiver,
  classConstructor,
  descriptor
) {
  _classCheckPrivateStaticAccess(receiver, classConstructor);
  _classCheckPrivateStaticFieldDescriptor(descriptor, 'get');
  return _classApplyDescriptorGet(receiver, descriptor);
}
返回静态私有方法
- 调用 
_classCheckPrivateStaticAccess确保构造函数和调用函数一致; - 调用 
_classCheckPrivateStaticFieldDescriptor确保静态私有属性一致; - 调用 
_classApplyDescriptorGet返回私有属性; 
_classPrivateFieldGet🔗
JSfunction _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = _classExtractFieldDescriptor(receiver, privateMap, 'get');
  return _classApplyDescriptorGet(receiver, descriptor);
}
返回静态私有属性。
执行过程🔗
上面已经放过 babel 过的代码,这里就不重复了,重点看下执行的过程,从var _age = /*#__PURE__*/ new WeakMap();这段代码开始。
- 创建 
_age和_getAge的Weak对象; - 调用 
_createClass给 Test 完成 prototype 赋值; new Test,进入 Test 构造函数内部;- 调用 
_classCallCheck确保为new调用; - 调用 
_classPrivateMethodInitSpec,让_getAge添加当前this; - 调用 
_classPrivateFieldInitSpec,给_age添加属性,key 为 this,value 为属性描述符; - 调用 
all方法; - 执行 
_classPrivateMethodGet(this, _getAge, _getAge2).call(this);,它的作用验证通过后调用_getAge2; _getAge2内部执行_classPrivateFieldGet,返回_age设置的属性描述符;- 返回 
all内部执行_classStaticPrivateMethodGet(Test, Test, _getName).call(Test);这一行,它的作用是验证后执行_getName; _getName内部执行_classStaticPrivateFieldSpecGet它返回属性描述符;- 输出结果,结束;
 
实现私有属性方法🔗
从上面过程中可以看到,实现私有方法的主要步骤是通过 WeakMap 来完成,如果不考虑验证过程,可以很简单实现。
JSconst privateName = new WeakMap();
class Test {
  constructor() {
    privateName.set(this, 'zhangsan');
  }
  getName() {
    return privateName.get(this);
  }
}
最后🔗
如果有什么不正确或者书写错误欢迎指出。