前言
最近开始在复杂项目中使用TypeScript,然而在使用过程中遇到了一些状况和报错,决定开一个系列记录遇到的问题和我的解决方法。
private标识符
前几天在使用TypeScript写一个类的时候,看到可以声明private标识符,果断把不想暴露到外部的属性和方法加上,然而…………编译完之后发现在运行时的环境里面还是可以访问,跟我期望有点不太一样(我期望是类似Java那样真的访问不了)
TypeScript
class Foo {
private a: number;
private b: string;
constructor () {
this.a = 1;
this.b = 'Tom';
}
setA (val: number) {
this.a = val;
}
getA () {
return this.a;
}
setB (val: string) {
this.b = val;
}
getB () {
return this.b;
}
}
Webpack构建后的JavaScript
var Foo = (function () {
function Foo() {
this.a = 1;
this.b = 'Tom';
}
Foo.prototype.setA = function (val) {
this.a = val;
};
Foo.prototype.getA = function () {
return this.a;
};
Foo.prototype.setB = function (val) {
this.b = val;
};
Foo.prototype.getB = function () {
return this.b;
};
return Foo;
}());
运行时结果

杀泼赖死~说好的private呢?
查询官方文档后发现有这么一段内容。

大致意思是,在TypeScript 3.8之后,TypeScript支持新的JavaScript的私有字段语法。而且这个私有字段语法是内置在JavaScript的运行时,能更好的确保每一个私有字段的相互隔离。而TypeScript的private只是在编译阶段声明某个字段是私有的。
于是,我们收获了新的私有字段语法#field~
ES6的私有字段语法
查看更多关于私有字段
那么我们把private标识符替换为#试试。
需要注意的是,把tsconfig.json里面的target改称es6。
"target": "es6",
TypeScript
class Foo {
#a: number;
#b: string;
constructor () {
this.#a = 1;
this.#b = 'Tom';
}
setA (val: number) {
this.#a = val;
}
getA () {
return this.#a;
}
setB (val: string) {
this.#b = val;
}
getB () {
return this.#b;
}
}
Webpack构建后的JavaScript
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 __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 _a, _b;
class Foo {
constructor() {
_a.set(this, void 0);
_b.set(this, void 0);
__classPrivateFieldSet(this, _a, 1);
__classPrivateFieldSet(this, _b, 'Tom');
}
setA(val) {
__classPrivateFieldSet(this, _a, val);
}
getA() {
return __classPrivateFieldGet(this, _a);
}
setB(val) {
__classPrivateFieldSet(this, _b, val);
}
getB() {
return __classPrivateFieldGet(this, _b);
}
}
_a = new WeakMap(), _b = new WeakMap();
运行时结果

看到这里,细心的朋友会发现,有个不太常用(对我而言甚至是从没见过)的对象WeakMap出现了。究竟这个是什么呢?
WeakMap类型
查看更多关于WeakMap
按照官方的说法,WeakMap的诞生源于Map会一直持有每个键值(key-value)的引用,导致即使已经没有其他地方引用,内存也没有办法被回收,进而可能引发内存泄露。原生的WeakMap持有的是一个“弱引用”(weak reference),而这个弱引用的机制,会导致WeakMap和Map有以下2个不同:
- 当且仅当键的引用没有被垃圾回收的时候,这个键值才是有效的。
WeakMap的键不可被枚举,因为它的键列表收到垃圾回收的影响,没办法列出;所以如果需要一个可被枚举的键列表,应该使用Map
回到我们的例子中,经过webpack构建后的代码,实际上在ES5的环境下,声明了2个WeakMap类型的局部变量(_a和_b)用于存放对应的私有字段,使用WeakMap的目的是防止内存泄漏。这两个局部变量利用JavaScript的闭包的机制,实现在getA()/setA()/getB()/setB()可被访问,而外部无法对私有字段进行直接访问。这也是在ES5/ES6以前,实现私有字段的常用手段。
私有方法
最后,有人可能会发现,私有字段只说了私有属性,那私有方法呢?
虽然TypeScript和ES6目前都不支持直接声明class的私有方法,但是我们可以用私有属性曲线救国。
// 不支持声明私有方法
class foo {
#myFunc () {
// blablabla
}
}
// 曲线救国
class foo {
#myFunc: Function;
constructor () {
this.#myFunc = () => {
// blablabla
}
}
}