10

I have the following proxy:

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);
  },
});

It's an array-like object, because it has numeric properties and the length property specifying the amount of elements. I can iterate it using a for...of loop:

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

However, the forEach() method is not working.

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

This code doesn't log anything. The callback function is never called. Why isn't it working and how can I fix it?

Code snippet:

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>

2
  • You could just extend Array to not polyfill each and every feature that can become broken when being proxified. Commented Nov 3, 2016 at 18:56
  • @estus I could, but that doesn't fix it. Commented Nov 3, 2016 at 19:20

3 Answers 3

8

One of the differences between a for...of loop and Array.prototype.forEach() is that the former uses the @@iterator property to loop over the object, while the latter iterates the properties from 0 to length, and executes the callback only if the object has that property. It uses a [[HasProperty]] internal method, which in this case returns false for every array element.

The solution is to add also the has() handler, which will intercept the [[HasProperty]] calls.

Working code:

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>

Sign up to request clarification or add additional context in comments.

Comments

3

There is an additional option that is relatively simple. Use Array.from() to generate an array that you can iterate over.

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

The full code:

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));

Comments

0

if targetObject is of form [{a:1, b:2}, {a: 3, b:4}, {a: 5, b: 6}] to add sudoKey to each object we can below proxy

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);
        }
    });

Example

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}

here !isNaN(parseInt(key)) is to to check whether prop is ArrayIndex (0, 1, 2 ... x.length - 1) or Array.Prototype keys which include forEach as a property

MDN for Array.prototype which attach forEach the proxy Array

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.