Array.prototype.forEach() не работает при вызове на прокси с обработчиком получения

У меня есть следующий прокси:

const p = new Proxy({
  [Symbol.iterator]: Array.prototype.values,
  forEach: Array.prototype.forEach,
}, {
  get(target, property) {
    if (property === '0') return 'one';
    if (property === '1') return 'two';
    if (property === 'length') return 2;
    return Reflect.get(target, property);
  },
});

Это массивоподобный объект, поскольку он имеет числовые свойства и свойство length, указывающее количество элементов. Я могу повторить это с помощью цикла for...of:

for (const element of p) {
  console.log(element); // logs 'one' and 'two'
}

Однако метод forEach() не работает.

p.forEach(element => console.log(element));

Этот код ничего не регистрирует. Функция обратного вызова никогда не вызывается. Почему он не работает и как это исправить?

Фрагмент кода:

const p = new Proxy({
  [Symbol.iterator]: Array.prototype.values,
  forEach: Array.prototype.forEach,
}, {
  get(target, property) {
    if (property === '0') return 'one';
    if (property === '1') return 'two';
    if (property === 'length') return 2;
    return Reflect.get(target, property);
  },
});

console.log('for...of loop:');
for (const element of p) {
  console.log(element);
}

console.log('forEach():');
p.forEach(element => console.log(element));
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.min.js"></script>


person Michał Perłakowski    schedule 03.11.2016    source источник
comment
Почему это может получить отрицательный голос?   -  person Pointy    schedule 03.11.2016
comment
Я не думаю, что название этого вопроса является особенно описательным. Если у кого-то из вас есть идея для лучшего названия, не стесняйтесь редактировать.   -  person Michał Perłakowski    schedule 03.11.2016
comment
@Pointy Я заметил, что и в других вопросах этого типа - кажется, что некоторые пользователи не любят вопросы с самостоятельными ответами. Не знаю, почему, но я видел, как за других проголосовали без комментариев. Что касается названия, я согласен, что его можно было бы улучшить — сначала я не совсем понял, что оно означает. Прочитав вопрос, я думаю, что заголовок идеально подходит ... но я не знаю, как сделать его более описательным при первом прочтении.   -  person VLAZ    schedule 03.11.2016
comment
Вы можете просто расширить Array, чтобы не заполнять каждую функцию, которая может выйти из строя при проксировании.   -  person Estus Flask    schedule 03.11.2016
comment
@estus Я мог бы, но это не исправит.   -  person Michał Perłakowski    schedule 03.11.2016


Ответы (2)


Одно из различий между циклом for...of и циклом Array.prototype.forEach() заключается в том, что первый использует свойство @@iterator для циклического обхода объекта, а второй выполняет итерацию свойств от 0 до length и выполняет обратный вызов, только если объект имеет это свойство. Он использует внутренний метод [[HasProperty]], который в данном случае возвращает false для каждого элемента массива.

Решение состоит в том, чтобы добавить обработчик has(), который будет перехватывать вызовы [[HasProperty]].

Рабочий код:

const p = new Proxy({
  [Symbol.iterator]: Array.prototype.values,
  forEach: Array.prototype.forEach,
}, {
  get(target, property) {
    if (property === '0') return 'one';
    if (property === '1') return 'two';
    if (property === 'length') return 2;
    return Reflect.get(target, property);
  },
  has(target, property) {
    if (['0', '1', 'length'].includes(property)) return true;
    return Reflect.has(target, property);
  },
});

p.forEach(element => console.log(element));
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.min.js"></script>

person Michał Perłakowski    schedule 03.11.2016

Существует дополнительная опция, которая относительно проста. Используйте Array.from() для создания массива, который вы можете перебирать.

const a = Array.from(p);
a.forEach(element => console.log(element));

Полный код:

const p = new Proxy({
  [Symbol.iterator]: Array.prototype.values,
  forEach: Array.prototype.forEach,
}, {
  get(target, property) {
    if (property === '0') return 'one';
    if (property === '1') return 'two';
    if (property === 'length') return 2;
    return Reflect.get(target, property);
  },
});

const a = Array.from(p);
a.forEach(element => console.log(element));
person josh    schedule 17.06.2021