How I troubleshoot issues that don’t make sense
Today we wanted to start tracking some new performance metrics on our landing pages, and more specifically, the memory usage.
As usual, we got the data from our trusty window.performance
object (from now on will be referred to as "the enemy"), stringified it before sending and…
Surprisingly, all we got was "{}"
!
How can it be? We have an object, with properties we can access, but stringifying it returns an empty object!
Down the rabbit hole I go
I started digging deeper into it, with the goal of turning the enemy into a string without hard coding the property names.
I tried spreading it
const result = { ...window.performance.memory };
console.log(result); // {}
I tried getting its components
const keys = Object.keys(window.performance.memory);
const values = Object.values(window.performance.memory);
console.log(keys); // []
console.log(values); // []
I tried destructuring it
const { jsHeapSizeLimit } = window.performance.memory;
console.log(jsHeapSizeLimit); // 2330000000
And surprisingly — it worked!
With great confidence that I’m going to crack this I tried "rest" destructuring:
const { ...rest } = window.performance.memory;
console.log(rest); // {}
What is going on? Why does this specific object refuse to play nice?
A light at the end of the tunnel (that turns out to be a train)
After some more tinkering I found another thing that didn’t make sense.
for (const prop in window.performance.memory)
console.log(`${prop}: ${window.performance.memory[prop]}`)
// totalJSHeapSize: 13400000
// usedJSHeapSize: 12700000
// jsHeapSizeLimit: 2330000000
Iterating over the enemy’s properties does work, but getting its properties names doesn’t? Even Object.getOwnPropertyNames failed!
Although this solution could work for me (remember, my original goal was "turning the enemy into a string without hard coding the property names") I wanted to find a more elegant solution, and wanted to get to the bottom of this.
The first solution, AKA "not good enough"
The next part of my trip was when I started playing around with the prototype of the enemy, called MemoryInfo
. I tried changing the prototype, assigning the enemy to a new object with a different prototype, creating an array from the enemy, and eventually playing around with a combination of all the techniques mentioned above.
Object.getOwnPropertyNames(window.performance.memory); // []
Object.getOwnPropertyNames(window.performance.memory.__proto__) //
["totalJSHeapSize", "usedJSHeapSize", "jsHeapSizeLimit", "constructor"]
Success! Well… Sort of. I got the prototype property names, and I could use this array to get what I want using the following snippet
const success = JSON.stringify(Object.getOwnPropertyNames(window.performance.memory.__proto__).reduce((acc,key) => {
if (key !== 'constructor')
acc[key] = window.performance.memory[key];
return acc;
}, {})
);
console.log(success) // " {"totalJSHeapSize":20500000,"usedJSHeapSize":18200000,"jsHeapSizeLimit":2330000000}"
Not great, not terrible.
And it does work (and it is a one-liner like I love), don’t get me wrong, but it’s just not as elegant as I wanted it to be, and nothing made any sense anymore.
I am one with the code
Fast forward 1 hour and I discovered that objects have properties, and these properties have metadata (called descriptor) and this metadata defines whether we can enumerate over the property, change it, and how we get it from the object.
So it must be that the properties have some sort of a metadata tag that prevents getting them in the conventional ways. From MDN I figured that enumerable
is the property that interests me:
true
if and only if this property shows up during enumeration of the properties on the corresponding object.
Object.getOwnPropertyDescriptors(window.performance.memory.__proto__);
// {..., jsHeapSizeLimit: {get: ƒ, set: undefined, enumerable: true, configurable: true}, ...}
But the properties do have the enumerable
metadata-property turned on, why aren’t they showing up when using Object.keys
or Object.values
?
The not-so-grand finale
Finally, I succeeded in "turning on" the enumerable flags on the enemy, and my data stringified beautifly
const finale = JSON.stringify(Object.defineProperties(window.performance.memory, Object.getOwnPropertyDescriptors(window.performance.memory.__proto__)));
console.log(finale); // "{"totalJSHeapSize":29400000,"usedJSHeapSize":23100000,"jsHeapSizeLimit":2330000000}"
Sweet, sweet one-liner
So what’s going on here?
We already saw that the descriptors of the MemoryInfo
prototype should be fine, so the only explanation is that somehow they are not set (or overridden) on the enemy itself.
Using Object.defineProperties
I am able to get a copy of the enemy, but with the correct descriptors (which we can just get from the prototype using Object.getOwnPropertyDescriptors
)
Then just stringify and ship!
Top comments (0)