TypeScript 中的 Ready-Only 属性

Typescript 2.0 中增加了 readonly 修饰符。被标记为 readonly 的属性只能在初始化的时候赋值或者在同一个类的构造函数中赋值。除此之外,均不被允许。

让我们来看一个例子。这儿有个简单的 Point 类声明了两个 read-only 属性,xy

type Point = {
  readonly x: number
  readonly y: number
}

我们现在创建一个对象来代表原点,point(0/0),x、y 均初始化为 0:

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

因为xy都被标记为readonly,我们不能再修改这两个属性的值:

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

一个更现实的例子

上面这个人为构造的例子过于简单,我们再来看另一个函数:

function moveX(p: Point, offset: number): Point {
  p.x += offset
  return p
}

moveX函数不应该修改属性x的值。因为有 readonly 修饰符,你如果这样做 Typescript 编译器会报错:

作为替代方案,moveX应该返回一个更新了属性值的新的 point,就像下面这样:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y,
  }
}

现在编译器不会报错了,因为我们没有去给一个只读的属性重新赋值,而是创建了一个新的 point 并初始化更新后的值,这完全没问题。

只读的类属性

你还可以在类中用 readonly 修饰属性。有一个Circle类包含了一个只读的属性radius和一个 getter 属性area,因为没有 setter,所以area是隐式只读的:

class Circle {
  readonly radius: number

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

  get area() {
    return Math.PI * this.radius ** 2
  }
}

area 的计算使用了 ES2016 的求幂运算。arearadius都可以在类外面被读取(因为没有标记为private),但是不能被重新赋值(因为都被标记为readonly):

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

只读的索引签名

另外,索引签名(index signatures)可以被标记为readonlyReadonlyArray<T>类型在它的索引签名中使用readonly来避免它的索引被修改:

interface ReadonlyArray<T> {
  readonly length: number
  // ...
  readonly [n: number]: T
}

因为只读的索引签名,所以下面的代码编译器会报错:

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

readonly vs Immutability

readonly 修饰符是 Typescript 类型系统的一部分。它被编译器用来检查是否有非法的属性赋值,一旦 Typescript 代码被编译成 Javascript,所有的readonly标记都会消失。你可以在这个小 Demo中随意修改玩耍,观察只读属性是如何被转译的。

因为readonly只存在于编译阶段,所以运行时对于属性赋值没有任何保护。也就是说,它会在编译阶段检查你代码中意外的属性赋值,这是 Typescript 类型系统中另一个帮你写出正确代码的特性。