类的私有域

中午突然想起一个问题,如何实现私有属性,私有方法?先自己尝试动手实现。

类的私有域

通过闭包实现私有属性

首先想到的是通过创建闭包去维护这些私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const Person = (function () {
const privatePropertys = {};
function Person(n) {
// 创建一个只读的_id属性
Object.defineProperty(this, '_id', {
enumerable: false,
configurable: false,
writable: false,
value: Date.now(),
});
// 初始化name
(privatePropertys[this._id] = {}) && (privatePropertys[this._id].name = n);
}
Person.prototype.sayName = function () {
const { name } = privatePropertys[this._id];
console.log('Hi, My name is', name);
_sayHello.call(this);
};
// 私有方法_sayHello
function _sayHello() {
console.log('hello', privatePropertys[this._id].name);
}
return Person;
})();

let person1 = new Person('person1');
console.log(person1.name, person1._sayHello); // undefined undefined
person1.sayName();
// Hi, My name is person1
// hello person1

上述例子虽然实现了私有属性,私有方法,但是多了一个非必要的_id 属性,实例也可以访问这个属性,且实例对象被GC回收后,privatePropertys 也不会自动去删除这个实例对象对应的私有属性,存在内存泄漏的风险,当实例对象被回收时,需要人工维护privatePropertys 对象,删除该实例对应的属性。

privatePropertys 其实只是一个映射,所以我们可以使用WeakMap来实现它,因为WeakMap 针对对象都是弱引用的,我们把实例对象作为WeakMap的key,value存储实例对象的所有私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Person = (function () {
const privatePropertys = new WeakMap();
function Person(n) {
// 以每个实例对象作为映射的key
privatePropertys.set(this, {
name: n, // 初始化私有属性name
});
}
Person.prototype.sayName = function () {
const { name } = privatePropertys.get(this);
console.log('Hi, My name is ', name);
_sayHello.call(this);
};
// 私有方法_sayHello
function _sayHello() {
console.log('hello', privatePropertys.get(this).name);
}
return Person;
})();

WeakMap & WeakSet

Map和Set数据类型我在日常开发中已经开始使用,但是WeakMap和WeakSet基本没怎么用到过。

WeakMap,WeakSet,有一些共同的特性,所以我们可以放在一起讨论。

WeakSet只能以对象作为集合的成员,WeakMap只接收对象作为映射的key。WeakSet,WeakMap对这些对象都是弱引用,弱引用不会被计入垃圾回收机制里的引用,所以当这些对象没有其他引用时,GC可以直接回收这些对象占用的内存。由于这个特性,WeakSet 的成员和WeakMap 的key都是不可枚举,因为你无法确定GC前后,其枚举出来的列表是否有变化,其是一个不确定的结果。

日常使用中,我很少会将一个对象作为映射的key或者作为集合的成员,所以等到接触typescript之后才了解到类私有域,即#修饰符。窥探tsc 编译后的代码,可以了解到ts是通过WeakMap 实现了类的私有实例属性的。

类私有域

类私有域 - JavaScript | MDN

详细参考MDN上的介绍。

ECMAScript 针对类私有域的提案在2021年四月就已经到了Stage-4 阶段,即将在ECMAScript 2022 正式上线,也就是2022年6月份会正式发布。

http://img.chenpt.cc/FqotN8DQXljfg6hDKwGmzabTTGKK

而各家浏览器在更早之前就已经实现了类的私有域功能,chrome的最早一个实现类私有域的版本是73,时间上可以追溯到2019年。我们可以在can i use上查看所有浏览器的情况。

http://img.chenpt.cc/FnlCYNT9Ld6PKjtnDR-7HM_61I7c

现在大多数高版本的现代浏览器是实现了这个类私有域的提案-明年正式成为标准草案,低版本浏览器的目前全红。所以不太适合直接上生产,编译成ES2015标准还是目前最稳妥的方案了。

总结

使用了Typescript的项目,我们直接使用类私有域的语法就可以实现私有化。

对于只运行在高版本浏览器上的项目,直接使用类私有域的语法即可,因为浏览器支持其大部分功能。

其他需要运行在低版本的项目,请使用polyfill的实现。

当各家浏览器标准愈发统一,且浏览器跟进实现标准的速度越来越快,可以预料到的是,交给前端处理的浏览器兼容性问题会越来越少,我们也不需要写那些polyfill的代码去向下兼容。