javascriptobjectiterable

Javascript: How to inspect an iterator object (iterator protocol)?


I'm trying to understand what's inside an iterator object.

Q: How can I see every property/method of an object, in this case an iteration object? How can I see exactly of what an object is made of? Why is the .next() method not listed by the Object.getOwnPropertyNames() method? I thought that every method in an object needs to be attached to a key inside that object? The only keys that show up are the ones(color, taste) I added myself.

Below the html code and the rendered page:

<!DOCTYPE html>
<html>
<body>

<h1>JavaScript Strings</h1>
<h2>The matchAll() Method</h2>

<p>ES2020 intoduced the string method matchAll().</p>

<p id="demo"></p>


<script>
let text = "I love cats. Cats are very easy to love. Cats are very popular."
const iterator = text.matchAll(/Cats/gi);

// If I can add a properties, then it must be an object.
iterator.color = "green";
iterator.taste = "salty";

let text2 = "<hr>";
text2 += "Iterator object entries => ";
for (let [key, value] of Object.entries(iterator)) {
  text2 += key + ": " + value + ", ";
}
text2 += "<br><br>";

text2 += "Print out the iterator object directly => " + iterator + "<br><br>";
text2 += "Iterator object keys => " + Object.keys(iterator) + "<br><br>";
text2 += "Iterator object values => " + Object.values(iterator) + "<br><br>";
text2 += "Stringified JSON => " + JSON.stringify(iterator) + "<br><br>";
text2 += "Object.getOwnPropertyNames(iterator) => " + Object.getOwnPropertyNames(iterator) + "<br><br>";

// Intentionally calling .next() Method to "consume" one iteration.
iterator.next();
text2 += "Array.from(iterator) => " + Array.from(iterator) + "<br>";
document.getElementById("demo").innerHTML = text2; 


</script>

</body>
</html>

The rendered page looks something like this:

JavaScript Strings
The matchAll() Method

ES2020 intoduced the string method matchAll().

Iterator object entries => color: green, taste: salty,

Print out the iterator object directly => [object RegExp String Iterator]

Iterator object keys => color,taste

Iterator object values => green,salty

Stringified JSON => {"color":"green","taste":"salty"}

Object.getOwnPropertyNames(iterator) => color,taste

Array.from(iterator) => Cats,Cats

iterator Protocol

W3Schools string.matchAll

choosen example


Solution

  • Methods on the prototype aren't going to show up with your current code. console.dir(object) is a good way to inspect them in DevTools but it doesn't provide string output.

    Below is an example of one way to achieve this. Disclaimer: I used ChatGPT o1 to save myself the trouble of doing this a few months back, some of this code is still the original output.

    let text = "I love cats. Cats are very easy to love. Cats are very popular.";
    const iterator = text.matchAll(/Cats/gi);
    
    iterator.color = "green";
    iterator.taste = "salty";
    
    function consoleDirToString(obj) {
      let output = '';
    
      function traverse(current, depth = 0) {
        // Stop recursion if we reach Object.prototype or null
        if (!current || current === Object.prototype) return;
    
        const indent = '  '.repeat(depth);
        output += `${indent}${current.constructor.name} {\n`;
    
        // List all own properties (including non-enumerable)
        const propNames = Object.getOwnPropertyNames(current);
        for (const name of propNames) {
          const descriptor = Object.getOwnPropertyDescriptor(current, name);
          if (!descriptor) continue;
    
          const { value } = descriptor;
          if (typeof value === 'function') {
            output += `${indent}  ${name}: [Function],\n`;
          } else {
            output += `${indent}  ${name}: ${JSON.stringify(value)},\n`;
          }
        }
    
        // If "current" looks like an iterator, try printing its array form
        if (typeof current.next === 'function') {
          try {
            // If "current" is truly an iterator and not re-iterable, it may be consumed here.
            const arr = Array.from(current);
            output += `${indent}  array from iterator: ${JSON.stringify(arr)},\n`;
          } catch (e) {
            // Not all objects with a `next()` function are valid iterators.  Silently ignore any errors.
          }
        }
    
        output += `${indent}}\n`;
    
        // Move up the prototype chain
        traverse(Object.getPrototypeOf(current), depth + 1);
      }
    
      traverse(obj);
      return output;
    }
    
    console.log(consoleDirToString(iterator));
    

    Output:

    Iterator {
      color: "green",
      taste: "salty",
      array from iterator: [["cats"],["Cats"],["Cats"]],
    }
      Iterator {
        next: [Function],
      }
        Iterator {
          constructor: undefined,
          reduce: [Function],
          toArray: [Function],
          forEach: [Function],
          some: [Function],
          every: [Function],
          find: [Function],
          map: [Function],
          filter: [Function],
          take: [Function],
          drop: [Function],
          flatMap: [Function],
        }
    

    In general, when you're inspecting objects you have to roll custom code for each class. A tip that I give everyone: If you're planning on using objects in this manner and prototype pollution isn't a concern, add your own toJSON() method to the prototype. That way you can simply use JSON.stringify(object) to get the desired output.