Как защитить массив JS от усечения

Отказ от ответственности: Существует несколько вариантов, как усечь массив в JS, но здесь я хотел бы поговорить только об одном из них.

Если вы хотите усечь массив в JS, есть трюк со свойством length:

const numbers = [1, 2, 3, 4, 5];

if (numbers.length > 3) {
  numbers.length = 3;
}

console.log(numbers); // [1, 2, 3]
console.log(numbers.length); // 3
Войдите в полноэкранный режим Выйти из полноэкранного режима

Иногда мне нравится ставить перед собой небольшие теоретические задачи по JS, чтобы поддерживать себя в форме.
Так что давайте вместе сделаем небольшое упражнение и попробуем найти решение, как защитить массив и запретить его мутацию путем манипуляций со свойством length.

1-е решение:

Свойство длины массива по умолчанию имеет следующие атрибуты:

перечислимый === false
настраиваемое === false
записываемый === true

Когда атрибут свойства configurable равен false, допускается лишь несколько изменений свойства. Одним из них является изменение атрибута writable на false.
Сочетание атрибутов configurable===false и writable === false делает невозможным удаление или изменение свойства любыми возможными способами.

const numbers = [1, 2, 3, 4, 5];
Object.defineProperty(numbers,'length',{writable:false})

if (numbers.length > 3) {
  numbers.length = 3;
}

console.log(numbers); // [1, 2, 3, 4, 5]
console.log(numbers.length); // 5

Object.defineProperty(numbers,'length',{value:3})
// Uncaught TypeError: Cannot redefine property: length
Вход в полноэкранный режим Выход из полноэкранного режима

Цель достигнута! Мутация массива по длине свойства отключена.

2-е решение (несколько экзотическое):

Проверим, что говорит спецификация ES о манипуляции длиной массива:

Для каждого собственного ключа свойства P из A, являющегося индексом массива, числовое значение которого больше или равно newLen, в порядке убывания числового индекса, сделать:

Проще говоря, усечение массива начинается с хвоста массива (в порядке убывания числового индекса). В нашем первом фрагменте кода сначала будет удален элемент 5, а затем элемент 4.

Теперь пришло время вспомнить, что в JS массив — это объект (экзотический, но все же объект), где индекс — это ключ свойства, а элемент — значение свойства.
Поэтому, когда массив усекается, это означает, что свойства массива, чьи ключи больше или равны новой длине, удаляются.

Пусть deleteSucceeded будет ! A.[[Delete]](P).

Здесь A — массив, [[Delete]] — абстрактный метод для удаления свойства из объекта, P — ключ свойства.

Поэтому давайте объединим их вместе.
В нашем примере с массивом чисел свойство с ключом === 4 будет удалено первым. Но есть способ предотвратить удаление свойства из объекта — установите атрибут configurable в false!

Эксперимент:

const numbers = [1, 2, 3, 4, 5];
Object.defineProperty(numbers,'4',{configurable:false})

if (numbers.length > 3) {
  numbers.length = 3;
}

console.log(numbers); // [1, 2, 3, 4, 5]
console.log(numbers.length); // 5
Вход в полноэкранный режим Выход из полноэкранного режима

Массив не мутирует!
Согласно спецификации (см. шаг 17) усечение массива за счет уменьшения длины свойства прекращается при первом неудачном удалении свойства.
Таким образом, если мы защитим от удаления только последний элемент массива, то мы достигнем нашей цели — предотвратим мутацию массива.

P.S. Абстрактный метод [[Delete]], который используется для определения усечения массива, также используется для определения логики оператора delete. Таким образом, когда вы уменьшаете длину массива, вы буквально удаляете из него свойства. Как побочный эффект, метод pop() также будет отключен для такого массива.

P.S.S. Я никогда не использовал это в своей работе и надеюсь, что никогда не буду=) Но мне нравится чувствовать, что я немного понимаю, как устроены внутренние механизмы JS.

Оцените статью
devanswers.ru
Добавить комментарий