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
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.