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

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

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>

3 ответа

Решение

Одно из различий между 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>

Есть еще одна относительно простая опция. Использовать 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));

еслиtargetObjectимеет форму[{a:1, b:2}, {a: 3, b:4}, {a: 5, b: 6}]добавитьsudoKeyдля каждого объекта мы можем использовать прокси-сервер

      let proxy_array = new Proxy(targetObjectArray, {
        get: function (target, key) {
            if (!isNaN(parseInt(key))) {
                return { sudoKey: compute(), ...Reflect.get(target, key)}
            }
            return Reflect.get(target, key);
        }
    });

Пример

      let x = [{a: 1, b:2}, {a:3, b:4}, {a:5, b: 6}]

let y = new Proxy(x, {
        get: function (target, key) {
            if (!isNaN(parseInt(key))) {
                return { c: -1, ...Reflect.get(target, key)}
            }
            return Reflect.get(target, key);
        }
    });

y.forEach(e => console.log(e))
Output:
{c: -1, a: 1, b: 2}
{c: -1, a: 3, b: 4}
{c: -1, a: 5, b: 6}

здесь!isNaN(parseInt(key))это проверить, есть лиpropявляетсяArrayIndex(0, 1, 2... x.длина - 1) илиArray.Prototypeключи, которые включают forEach в качестве свойства

MDN для Array.prototype, который прикрепляет к каждому прокси-массиву

Другие вопросы по тегам