TypeScript 中的只读属性

Marius Schulz, “Read-Only Properties in TypeScript”, October 31, 2016

TypeScript 2.0 增加了 readonly 修饰符,用于标记只读属性。只读属性仅允许两种场景下的赋值,一种是在初始化的时候,另一种是在类的构造函数中,其余场景都不允许赋值。

我们来看个例子。有一个声明了两个只读属性 xy 的简单类型 Point

type Point = {
    readonly x: number;
    readonly y: number;
};
1
2
3
4

现在创建一个表示原点坐标的 origin 对象,xy 的初始值都为 0

const origin: Point = { x: 0, y: 0 };
1

因为 xy 都被声明为只读的,所以它们是不能被修改的:

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
origin.x = 100;
1
2
3

一个更实际的例子

上面的例子可能不太有说服力(确实),考虑下面的函数:

function moveX(p: Point, offset: number): Point {
    p.x += offset;
    return p;
}
1
2
3
4

moveX 函数不能修改给定坐标点的 x 属性,因为这个属性是只读的,否则 TypeScript 编译器会抱怨的:

Forbidden assignment to readonly property in TypeScript

相反,moveX 应该返回一个更新后的新坐标点,类似这样:

function moveX(p: Point, offset: number): Point {
    return {
        x: p.x + offset,
        y: p.y
    };
}
1
2
3
4
5
6

现在编译器就很开心了,因为我们没有修改只读属性,而是返回了一个新的坐标点:

只读类属性

我们还可以为类的属性应用 readonly 修饰符。下面的 Circle 类有一个只读属性 radius 和一个获取属性 area(也是只读的,因为没有 setter):

class Circle {
    readonly radius: number;

    constructor(radius: number) {
        this.radius = radius;
    }

    get area() {
        return Math.PI * this.radius ** 2;
    }
}
1
2
3
4
5
6
7
8
9
10
11

注意,这里用到了 ES2016 新的指数操作符radiusarea 属性都能被外部访问(没有标记为 private),同时又不能被修改(都是只读的):

const unitCircle = new Circle(1);
unitCircle.radius; // 1
unitCircle.area; // 3.141592653589793

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.radius = 42;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = 42;
1
2
3
4
5
6
7
8
9
10
11

只读索引签名

而且,索引签名也能用 readonly 修饰符标记成只读的,从而来避免索引属性被赋值。下面的 ReadonlyArray<T> 就是一个例子:

interface ReadonlyArray<T> {
    readonly length: number;
    // ...
    readonly [n: number]: T;
}
1
2
3
4
5

因为索引是只读的,所以编译器认为下面的赋值是无效的:

const primesBelow10: ReadonlyArray<number> = [2, 3, 5, 7];

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
primesBelow10[4] = 11;
1
2
3
4
5

readonly vs. 不可变

readonly 修饰符是 TypeScript 类型系统的一部分,只用来帮助编译器检查属性赋值是否合法。一旦编译成 JavaScript 代码后,readonly 标记会被移除。可以尝试 这个简单的例子 看下只读属性是如何被转译的。

因为 readonly 只是一个编译器设施,所以是无法为运行时提供保障的。也就是说,它是类型系统的另一个特性,通过让编译器检查 TypeScript 代码中意外的属性赋值,来帮助你写出正确的代码。

本篇文章是 TypeScript Evolution 系列中的一篇。