前言
私有属性已经出来很长时间了,目前在Stage 3阶段(2020/6/30)日,目前 chrome 浏览器已经支持了,在讲解之前先看看怎么使用
class IncreasingCounter {
#count = 0;
get value() {
console.log("Getting the current value!");
return this.#count;
}
increment() {
this.#count++;
}
}
const counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
上面代码#count是私有属性,只能在类内部使用,在外部使用就会报错,下面就讲讲怎么来模拟实现它。
闭包
最先想到的就是使用闭包的形式,如果我们在内部调用外部的变量那么根据作用域的查找规则,我们可以在类内部引用它,而在外部永远也不能访问。
const IncreasingCounter = (function () {
let count = 42;
return class {
get value() {
console.log("Getting the current value!");
return count;
}
increment() {
count++;
}
};
})();
const counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
优点:
- 实现了私有属性,外部无法访问
- 无命名冲突
缺点:
- 有额外的构建开销
Symbol
es6 新增了Symbol类型,它表示独一无二的值,我们可以利用这个特性模拟私有属性。
const IncreasingCounter = (function () {
const _count = Symbol("count");
return class {
get value() {
console.log("Getting the current value!");
return this[_count];
}
constructor() {
this[_count] = 42;
}
increment() {
this[_count]++;
}
};
})();
const counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
优点:
- 无命名冲突
- 外部无法访问和修改
- 无性能损失
缺点:
- 有兼容性问题
WeakMap
const IncreasingCounter = (function () {
const _private = new WeakMap();
return class {
get value() {
console.log("Getting the current value!");
const value = _private.get("count");
return value;
}
constructor() {
_private.set("count", 42);
}
increment() {
const value = _private.get("count");
_private.set("count", ++value);
}
};
})();
const counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
优点:
- 无命名冲突
- 外部无法访问和修改
缺点:
- 有兼容性问题
- 有性能上的损失
babel 做法
先直接看 babel 转换后的代码是怎么样的
"use strict";
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;
}
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 _count = new WeakMap();
var IncreasingCounter = /*#__PURE__*/ (function () {
function IncreasingCounter() {
_classCallCheck(this, IncreasingCounter);
_count.set(this, {
writable: true,
value: 0,
});
}
_createClass(IncreasingCounter, [
{
key: "increment",
value: function increment() {
var _this$count;
_classPrivateFieldSet(
this,
_count,
(_this$count = +_classPrivateFieldGet(this, _count)) + 1
),
_this$count;
},
},
{
key: "value",
get: function get() {
console.log("Getting the current value!");
return _classPrivateFieldGet(this, _count);
},
},
]);
return IncreasingCounter;
})();
var counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
上面的代码,我之前在从 babel 看 class(上)有讲解过,我们只看私有变量相关的
"use strict";
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 _count = new WeakMap();
var IncreasingCounter = /*#__PURE__*/ (function () {
function IncreasingCounter() {}
_createClass(IncreasingCounter, [
{
key: "increment",
value: function increment() {
var _this$count;
_classPrivateFieldSet(
this,
_count,
(_this$count = +_classPrivateFieldGet(this, _count)) + 1
),
_this$count;
},
},
{
key: "value",
get: function get() {
console.log("Getting the current value!");
return _classPrivateFieldGet(this, _count);
},
},
]);
return IncreasingCounter;
})();
var counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
去除了不必要的代码,babel的做法跟我们上述WeakMap的做法差不多,不过它多了几步
- 每个私有属性都用一个
WeakMap值存储 - 赋值和读取值的时候进行检查,
get和set属性存不存在,如果存在就直接调用函数,否则直接赋值或者读取
上面babel的代码多了很多的边界检查,下面模拟写一版伪代码。
const IncreasingCounter = (function () {
const _count = new WeakMap();
return class {
// 初始化赋值
constructor() {
_count.set(this, {
writable: true,
value: 0,
});
}
get value() {
const descriptor = _count.get(this);
if (!descriptor) {
throw new TypeError("attempted to get private field on non-instance");
}
console.log("Getting the current value!");
return this.descriptor;
}
increment() {
if (!descriptor.writable) {
throw new TypeError("attempted to set read only private field");
}
if (!descriptor.writable) {
throw new TypeError("attempted to set read only private field");
}
descriptor.value = descriptor.value++;
}
};
})();
var counter = new IncreasingCounter();
counter.#count; // 报错
counter.#count = 42; // 报错
到这一步基本上就把私有变量说的差不多了,剩下的说几个比较容易混淆的点
其他
静态私有方法怎么实现的
静态私有属性实现与私有属性实现没有太多区别,先看一段babel的代码
var Test = /*#__PURE__*/ (function () {
function Test() {
_classCallCheck(this, Test);
}
_createClass(Test, null, [
{
key: "obtain",
value: function obtain() {
_classStaticPrivateMethodGet(Test, Test, _computeRandomNumber).call(
Test
);
},
},
]);
return Test;
})();
var _computeRandomNumber = function _computeRandomNumber() {
return _classStaticPrivateFieldSpecGet(Test, Test, _totallyRandomNumber);
};
var _totallyRandomNumber = {
writable: true,
value: 4,
};
Test.obtain();
_totallyRandomNumber是定义静态属性变量信息,_classStaticPrivateMethodGet是静态方法相关的调用函数,它的源码也很简单
function _classStaticPrivateMethodGet(receiver, classConstructor, method) {
if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance");
}
return method;
}
检查构造函数是否相同,相同返回method。
而_classStaticPrivateFieldSpecGet函数做的就是返回_totallyRandomNumber变量的信息,源码如下
function _classStaticPrivateFieldSpecGet(
receiver,
classConstructor,
descriptor
) {
if (receiver !== classConstructor) {
throw new TypeError("Private static access of wrong provenance");
}
if (descriptor.get) {
return descriptor.get.call(receiver);
}
return descriptor.value;
}
结论:
对比私有属性没有太多变化,只是检查从WeakMap变成了对比构造函数
继承能使用么?
嗯。。。答案是不能,因为我试过
class Root {
#foo = 456;
}
class Child extends Root {
getName() {
return this.#foo;
}
}
const value = new Child();
console.log(value.getName());
可以在 chrome 尝试下,如果真的想用可以使用typescript,而且从babel转义的过程来看,子类实际上并没有与父类建立联系出现这种结果也在意料之中了。
最后
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。