2

I'm trying to determine if an object can be stringified or not. This check works in Chrome and Safari, but not in FF (25.0.1).

var good = true;
var myObj = {"param1":11, "param2": "a string", "param3": $("a")}; 
//some cyclic object, specifically I have a jQuery object I got via `$("a")` 
//which returned several anchor tags.

//try to stringify, which supposedly rejects cyclic objects 
try {
    JSON.stringify(myObj);
} catch(error){
    good = false;
}
console.log(good) //returns true.

No error thrown... or I'm not catching it properly? I've never had call to use try... catch before now, so my experience with its nuances is null.

JSON.stringify(myObj) returns a string version of the object, sans many of the object parameters which obviously can't be stringified. It should, according to MDN, error.

Thanks!

1 Answer 1

4

You're catching the error properly, but (as you've identified) Firefox simply isn't throwing an error.

This is because Fiefox doesn't choke on JSONification of DOM objects, where other browsers do:

JSON.stringify(document.getElementById("header"))

In Chrome and Safari, this line results in an error (because in WebKit/Blink, cyclic DOM objects like siblings exist directly on each DOM object), while in Firefox with harmlessly produces the string "{}".

This is because Firefox's DOM objects do not have any of their own enumerable properties:

Object.keys(document.getElementById("header"))
> []

In WebKit/Blink browsers, this line provides an array of property names as strings, because DOM object have their own properties. JSON.stringify only captures an object's own properties, rather than prototype properties.

Bonus Info: More Than You Wanted to Know About the DOM

In Firefox, DOM objects mostly don't have their own properties; instead, property access is delegated up the prototype chain to the HTMLElement.prototype, Element.prototype, or Node.prototype (or the element's immediate prototype, like HTMLDivElement.prototype or HTMLAnchorElement.prototype).

You might wonder: if accessing a property on a DOM element results in prototype access, how can DOM elements have different property values? Don't all DOM elements have more or less the same prototype chain?

The trick here is that the prototype properties don't have values, they are getter functions. For example, when you ask for firstChild of a HTMLDivElement, the JavaScript engine takes the following steps:

  1. Look for the firstChild property on the object itself. It's not there.
  2. Look for the firstChild property on the object's prototype.
  3. Continue up the prototype chain until we find firstChild on Node.prototype.
  4. Node.prototype.firstChild is defined by an accessor property descriptor, meaning that property access results in the execution of a get function.
  5. The this value during the execution of the getter function is the particular DOM element whose firstChild value you asked for/ Firefox uses that this value to do some under-the-hood lookup of the DOM element's first child.

Thus, when you do:

var val = document.getElementById("header").firstChild;

you're really doing:

var elm = document.getElementById("header");
var nodeProto = elm.__proto__.__proto__.__proto__.__proto__;
var propDescriptor = Object.getOwnPropertyDescriptor(nodeProto, "firstChild");
var getterFunc = propDescriptor.get;
var val = getterFunc.call(elm);  // invoke the getter with `this` set to `elm`

Or (less readably):

var val = Object.getOwnPropertyDescriptor(document.getElementById("header").__proto__.__proto__.__proto__.__proto__, "firstChild").get.call(document.getElementById("header"))
Sign up to request clarification or add additional context in comments.

8 Comments

Great answer and explanation. For extra credit, know a good easy way to make this check work in FF?
@RandyHall If you want to force Firefox to refuse to stringify DOM elements, you can use a passthrough replacer function that throws an error when it tries to stringify a DOM node: JSON.stringify(myObj, function(key, value) { if(value instanceof Node) { throw new TypeError(); } return value; });
@apsillers There is no host object magic here. Per spec all the DOM properties (except unforgeable ones) are on the prototype, not own properties, and JSON.stringify only captures own properties. The fact that the DOM properties are own properties in WebKit and Blink is a known bug in those engines; the Blink engineers are working on fixing that bug. On the other hand, Firefox and IE are following the spec here.
@BorisZbarsky Thanks very much for that clarification; I've edited my answer extensively. Is this really spec-mandated behavior? I would have assumed that the DOM spec was too abstract to require a setup like this.
@RandyHall It's useful because it allows pages to hook the prototype to modify behavior for all consumers of the property. People do this all the time for DOM methods, which are on the prototype in WebKit/Blink.
|

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.