diff --git a/pages/Classes.md b/pages/Classes.md index 942829ab3..2b508d010 100644 --- a/pages/Classes.md +++ b/pages/Classes.md @@ -216,27 +216,43 @@ let howard = new Employee("Howard", "Sales"); let john = new Person("John"); // Error: The 'Person' constructor is protected ``` +# Readonly modifier + +You can make properties readonly by using the `readonly` keyword. +Readonly properties must be initialized at their declaration or in the constructor. + +```ts +class Octopus { + readonly name: string; + readonly numberOfLegs: number = 8; + constructor (theName: string) { + this.name = theName; + } +} +let dad = new Octopus("Man with the 8 strong legs"); +dad.name = "Man with the 3-piece suit"; // error! name is readonly. +``` + ## Parameter properties -In our last example, we had to declare a protected member `name` and a constructor parameter `theName` in the `Person` class, and we then immediately set `name` to `theName`. +In our last example, we had to declare a readonly member `name` and a constructor parameter `theName` in the `Octopus` class, and we then immediately set `name` to `theName`. This turns out to be a very common practice. *Parameter properties* let you create and initialize a member in one place. -Here's a further revision of the previous `Animal` class using a parameter property: +Here's a further revision of the previous `Octopus` class using a parameter property: ```ts -class Animal { - constructor(private name: string) { } - move(distanceInMeters: number) { - console.log(`${this.name} moved ${distanceInMeters}m.`); +class Octopus { + readonly numberOfLegs: number = 8; + constructor(readonly name: string) { } } ``` -Notice how we dropped `theName` altogether and just use the shortened `private name: string` parameter on the constructor to create and initialize the `name` member. +Notice how we dropped `theName` altogether and just use the shortened `readonly name: string` parameter on the constructor to create and initialize the `name` member. We've consolidated the declarations and assignment into one location. -Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier. -Using `private` for a parameter property declares and initializes a private member; likewise, the same is done for `public` and `protected`. +Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or `readonly`, or both. +Using `private` for a parameter property declares and initializes a private member; likewise, the same is done for `public`, `protected`, and `readonly`. # Accessors @@ -293,7 +309,12 @@ if (employee.fullName) { To prove to ourselves that our accessor is now checking the passcode, we can modify the passcode and see that when it doesn't match we instead get the message warning us we don't have access to update the employee. -Note: Accessors require you to set the compiler to output ECMAScript 5 or higher. +A couple of things to note about accessors: + +First, accessors require you to set the compiler to output ECMAScript 5 or higher. +Downlevelling to ECMAScript 3 is not supported. +Second, accessors with a `get` and no `set` are automatically inferred to be `readonly`. +This is helpful when generating a `.d.ts` file from your code, because users of your property can see that they can't change it. # Static Properties diff --git a/pages/Interfaces.md b/pages/Interfaces.md index 12610e672..793a46862 100644 --- a/pages/Interfaces.md +++ b/pages/Interfaces.md @@ -98,6 +98,49 @@ function createSquare(config: SquareConfig): { color: string; area: number } { let mySquare = createSquare({color: "black"}); ``` +# Readonly properties + +Some properties should only be modifiable when an object is first created. +You can specify this by putting `readonly` before the name of the property: + +```ts +interface Point { + readonly x: number; + readonly y: number; +} +``` + +You can construct a `Point` by assigning an object literal. +After the assignment, `x` and `y` can't be changed. + +```ts +let p1: Point = { x: 10, y: 20 }; +p1.x = 5; // error! +``` + +TypeScript comes with a `ReadonlyArray` type that is the same as `Array` with all mutating methods removed, so you can make sure you don't change your arrays after creation: + +```ts +let a: number[] = [1, 2, 3, 4]; +let ro: ReadonlyArray = a; +ro[0] = 12; // error! +ro.push(5); // error! +ro.length = 100; // error! +a = ro; // error! +``` + +On the last line of the snippet you can see that even assigning the entire `ReadonlyArray` back to a normal array is illegal. +You can still override it with a type assertion, though: + +```ts +a = ro as number[]; +``` + +## `readonly` vs `const` + +The easiest way to remember whether to use readonly or const is to ask whether you're using it on a variable or a property. +Variables use `const` whereas properties use `readonly`. + # Excess Property Checks In our first example using interfaces, TypeScript let us pass `{ size: number; label: string; }` to something that only expected a `{ label: string; }`. @@ -282,6 +325,18 @@ interface NumberDictionary { } ``` +Finally, you can make index signatures readonly in order to prevent assignment to their indices: + +```ts +interface ReadonlyStringArray { + readonly [index: number]: string; +} +let myArray: ReadonlyStringArray = ["Alice", "Bob"]; +myArray[2] = "Mallory"; // error! +``` + +You can't set `myArray[2]` because the index signature is readonly. + # Class Types ## Implementing an interface diff --git a/pages/Variable Declarations.md b/pages/Variable Declarations.md index aa3f63895..8f3c2bc81 100644 --- a/pages/Variable Declarations.md +++ b/pages/Variable Declarations.md @@ -417,6 +417,8 @@ kitty.numLives--; ``` Unless you take specific measures to avoid it, the internal state of a `const` variable is still modifiable. +Fortunately, TypeScript allows you to specify that members of an object are `readonly`. +The [chapter on Interfaces](./Interfaces.md) has the details. # `let` vs. `const`