Массивы только для чтения
В камере хранения бывает и витрина: ряд шкафчиков со стеклянными дверцами под замком. Посетитель видит, что лежит в каждом, может пересчитать вещи и прочитать номера. Но открыть дверцу, положить вещь или забрать её он не может.
Обычный массив похож на открытые шкафчики. Функция, которая получила массив, может его изменить, и вызывающий код об этом не узнает. Посмотрим на функцию, которая ищет лучший результат:
function best(list: number[]) {
list.sort((a, b) => b - a);
return list[0];
}
const scores = [70, 95, 80];
console.log(best(scores)); // 95
console.log(scores.join(" ")); // 95 80 70Разберём пример по строкам:
bestсортирует массив по убыванию и возвращает первый элемент.- Метод
sortне создаёт копию: он переставляет элементы в том же массиве. best(scores)печатает95, как и ожидалось.- А
scores.join(" ")печатает95 80 70: функция переставила массив, который ей дали только посмотреть. - У
aиbв(a, b) => b - aтипа нет, но ошибкиTS7006тоже нет. TypeScript знает, чтоsortуnumber[]передаёт в эту функцию два числа, и сам даётaиbтипnumber.
Такую ошибку трудно найти: порядок сломался в одном месте, а заметят это совсем в другом.
Тип readonly number[]
Слово readonly перед типом массива делает его массивом только для чтения: элементы можно читать, а менять сам массив нельзя.
В прошлой теме readonly стоял перед свойством. Перед типом его можно ставить только у массивов и кортежей: readonly User даст ошибку TS1354.
const scores: readonly number[] = [70, 95, 80];
console.log(scores[0]);
scores.push(60); // ✗error TS2339: Property 'push' does not exist on type 'readonly number[]'.Сообщение значит: «у типа readonly number[] нет свойства push». Методов, которые меняют массив, в этом типе нет совсем, поэтому tsc и пишет, что push не существует.
Что остаётся у массива только для чтения, а что пропадает:
- нельзя вызвать
push,pop,sort,reverse,splice— всё, что меняет сам массив; - нельзя записать по индексу:
scores[0] = 60даёт ошибкуTS2542; - можно читать по индексу, узнавать
lengthи обходить массив цикломfor…of; - можно вызывать методы, которые возвращают новый массив:
map,filter,slice,toSorted.
Как и у обычного массива, у этого типа есть длинная запись: ReadonlyArray<number> — тот же тип.
Параметр, который обещает не менять
Чаще всего readonly пишут у параметра функции. Такая аннотация — обещание в заголовке функции: «ваш массив я только прочитаю». Перепишем best:
function best(list: readonly number[]) {
return list.toSorted((a, b) => b - a)[0];
}
const scores = [70, 95, 80];
console.log(best(scores)); // 95
console.log(scores.join(" ")); // 70 95 80Давайте посмотрим, что изменилось:
list: readonly number[]— внутри функции массив можно только читать.toSortedвозвращает отсортированную копию и не трогает исходный массив.- В
bestпередали обычный массивscores, и это разрешено: функция обещает делать с ним меньше, чем позволяет типnumber[]. - Вторая строка печатает
70 95 80: порядок вscoresостался прежним.
Если оставить внутри list.sort(...), tsc остановит его ошибкой TS2339: у readonly number[] нет метода sort. Так обещание из заголовка проверяется на каждой строке функции.
Проверяет только tsc
readonly — часть типа, а типы стираются. В JavaScript-файле от readonly number[] ничего не останется, и при запуске массив будет обычным: push сработает. Защищает только проверка tsc до запуска.
Здесь аналогия с витриной перестаёт работать. Витрина закрыта для всех, а readonly закрывает массив только для того, кто смотрит на него через этот тип. После const view: readonly number[] = scores; вызов scores.push(60) пройдёт, ведь у scores тип number[], и view.length станет 4.
На собеседованиях часто спрашивают, чем readonly number[] отличается от const:
const scoresзапрещает записать в имяscoresдругой массив, но разрешаетscores.push(60);readonly number[]запрещает менять сам массив, но переменнойletтакого типа можно присвоить другой массив.
Вместе, в const scores: readonly number[], они закрывают и имя, и содержимое.
Частая ошибка
Массив только для чтения передали в функцию с обычным параметром number[]:
function total(list: number[]) {
let sum = 0;
for (const n of list) {
sum += n;
}
return sum;
}
const scores: readonly number[] = [70, 95, 80];
console.log(total(scores)); // ✗error TS4104: The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.tsc отвечает: тип readonly number[] только для чтения, и его нельзя присвоить изменяемому типу number[]. Параметр number[] разрешает функции менять массив, а scores менять запрещено. Функция total массив не меняет, поэтому исправление — дать то же обещание и ей: function total(list: readonly number[]).
Правило работает в одну сторону: обычный массив подходит к параметру readonly number[], а массив только для чтения к параметру number[] — нет.
Главное
readonly number[]— массив, который можно читать, но нельзя менять:pushдаёт ошибкуTS2339, запись по индексу —TS2542.readonlyу параметра обещает не трогать массив вызывающего кода; обычный массив в такой параметр передать можно.- Массив только для чтения не подходит к параметру
number[]: ошибкаTS4104. constзащищает имя переменной,readonly— содержимое массива, и проверяет его только tsc.