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