Предполагается ли безопасное удаление элементов из массива при итерации с for..of в JavaScript? - PullRequest
1 голос
/ 21 июня 2019

Я знаю, что он работает с Set, но у меня сложилось впечатление, что он будет работать и с массивом. Поэтому я попробовал это в Chrome и был удивлен, что это не сработало:

const array = [1,2,3,4,5,6]

for (const item of array) {
    if (item === 3 || item === 4) {
        array.splice(array.indexOf(item), 1);
    }
}

console.log(array) // [1,2,4,5,6]

Он не удалил 4.

Так что мой вопрос в том, должна ли безопасность итераций работать только с Set и Map, но не с Array?

(Если это так, то, кроме простого синтаксиса, я не вижу преимущества использования его по сравнению с for(;;). У меня сложилось впечатление, что for..of собирается предотвратить ошибки, даже с Array, как это происходит с Set и Map)

Обратите внимание, что в качестве трюка я могу сделать это путем клонирования массива (или обратной итерации):

const array = [1,2,3,4,5,6]

for (const item of Array.from(array)) {
    if (item === 3 || item === 4) {
        array.splice(array.indexOf(item), 1);
    }
}

console.log(array) // [1,2,5,6]

Ответы [ 3 ]

3 голосов
/ 21 июня 2019

Нет, (как показывает ваш пример) удаление элементов из массива во время его итерации небезопасно.

Итератор массива по умолчанию хранит текущий индекс и не обновляет этот индекс при вызове splice на массиве.Он просто продолжается в той же позиции, независимо от того, что вы сделали с элементами в массиве.Вы можете прочитать спецификацию для ArrayIterator объектов , они в основном работают как цикл for (var index=0; index<array.length; index++) yield array[index];.

1 голос
/ 21 июня 2019

Согласно MDN

В общем случае лучше не добавлять, не изменять и не удалять свойства из объект во время итерации, отличающийся от свойства, находящегося в данный момент посетил. Нет гарантии, будет ли добавленная недвижимость посещено, будет ли измененное свойство (кроме текущего) посещать до или после его изменения, или же удалить свойство будет посещено до того, как оно будет удалено.

В качестве альтернативы , вы можете попробовать это демо или использовать filter()

const array = [1, 2, 3, 4, 5, 6]

for (const item of [3, 4]) {
  array.splice(array.indexOf(item), 1);
}

console.log(array)
1 голос
/ 21 июня 2019

Это потому, что когда цикл перешел на 3 (индекс: 2), массив удаляет значение 3, и 4 теперь становится индексом: 2. Следующая итерация перейдет к индексу: 3, который равен 5.

Вместо этого вы можете сделать это следующим образом:

const array = [1,2,3,4,5,6]
for (var i=0;i<array.length;i++) {
    if (array[i] === 3 || array[i] === 4) {
        array.splice(array.indexOf(array[i]), 1);
        --i;
    }
}

console.log(array) 
...