With ES6 taking ahold, I'm eager to drop jQuery and use native JS for my website builds keeping them quick and lightweight. Also to improve my JS skills as I'm one of those whose jumped straight in with jQuery.
I'm building a tiny tiny library to make the more common used javascript in a function to keep the files small.
function $(elm) {
var elm = document.querySelectorAll(elm);
this.forEach = function(f) {
[].forEach.call(elm, f);
}
return elm;
}
function slider() {
$(".slider").forEach(function() {
alert("Hello");
});
}
slider();
This works great in Chrome etc.. but in IE10/11 I'm getting the error
Object doesn't support this property or method "forEach"
referring to the $(".slider").forEach
Any ideas?
You're adding forEach to the window object, not to the object you return; you're calling $ as a function, not a constructor. Since you're using loose mode (apparently), this within the function call is a reference to the global object (also accessible as window on browsers). You're returning the collection from querySelectorAll unchanged.
The reason it works on Chrome is that the collection returned by querySelectorAll has its own forEach (this is a fairly recent addition).
For this to work, four options:
Return an object and add forEach to it, copying the elements from the QSA collection to that object. E.g.:
function $(selector) {
const result = Array.from(document.querySelectorAll(selector));
result.forEach = Array.prototype.forEach;
// Perhaps map, filter, etc.; add in a loop?
return result;
}
Or in ES5:
var $ = (function() {
var methods = Array.prototype;
function $(selector) {
var result = methods.slice.call(document.querySelectorAll(selector));
result.forEach = methods.forEach;
// Perhaps map, filter, etc.; add in a loop?
return result;
}
return $;
})();
Add forEach to the NodeList prototype if it's not already there and use forEach directly on the collection from querySelectorAll. For instance:
if (typeof NodeList !== "undefined" &&
NodeList.prototype &&
!NodeList.prototype.forEach) {
// Yes, direct assignment is fine here, no need for `Object.defineProperty`
NodeList.prototype.forEach = Array.prototype.forEach;
}
(No need for Object.defineProperty above, enumerable [surprisingly], configurable, and writable are all true for it on Chrome and Firefox, so we can just do direct assignment as above.)
...and then of course your $ becomes nothing more than
function $(selector) {
return document.querySelectorAll(selector);
}
(To start with. If you wanted to add more features, you'd probably want to go the way of #1.)
Return an array:
function $(selector) {
return Array.from(document.querySelectorAll(selector));
}
Or in ES5:
function $(selector) {
return Array.prototype.slice.call(document.querySelectorAll(selector));
}
Subclass Array (which cannot be perfectly polyfilled on pre-ES2015 JavaScript engines) so you can add your own features on top of Array's features:
class MyThingy extends Array {
// Perhaps further methods here
}
function $(selector) {
return MyThingy.from(document.querySelectorAll(selector));
}
No ES5 option here, you'd need to at least transpile and polyfill.
If you're going to add features beyond those provided by Array, I quite like #4 other than the polyfilling available only being "so" good. It may well be sufficient for your purposes.
Related
I am making a script for choices about a product (colors etc), which works in every browser except for Internet Explorer (11) & Edge.
I put the choices of each parameter in a array and apply a function to them with the array.forEach() method.
Example for the color parameter:
var color_btns = document.querySelectorAll('#color > p');
color_btns.forEach(function(color) {
color.onclick = function () {
color_btns.forEach(function(element) {
if (element.classList.contains('selected')) {
element.classList.remove('selected');
}
});
color.classList.add('selected');
document.querySelector('#f_color').value = color.dataset.id;
};
});
I get the following output in the console of both IE & Edge:
Object doesn't support property or method 'forEach'
After searching about the issue, I learnt that this function should be supported by IE 9 and newer. I tried to define the function by myself without success. When I log the function it is defined as a function (with "[native code]" inside).
I replaced every .forEach by a for and it's working pretty well,
but how can I make it work ?
Is there a specific usage of the forEach() for Internet Explorer & Edge ?
I thought it was Array.prototype.forEach and that recent versions of IE (and all versions of Edge) had it...?
Most DOM methods and collection properties aren't actually arrays, they're collections:
querySelectorAll returns a static NodeList (a snapshot of matching elements as of when you call it).
getElementsByTagName, getElementsByTagNameNS, getElementsByClassName, and the children property on a ParentNode (Elements are parent nodes) return live HTMLCollection instances (if you change the DOM, that change is reflected live in the collection).
getElementsByName returns a live NodeList (not a snapshot).
NodeList only recently got forEach (and keys and a couple of other array methods). HTMLCollection didn't and won't; it turned out adding them would break too much code on the web.
Both NodeList and HTMLCollection are iterable, though, meaning that you can loop through them with for-of, expand them into an array via spread ([...theCollection]), etc. But if you're running on a browser where NodeList doesn't have forEach, it's probably too old to have any ES2015+ features like for-of or iteration.
Since NodeList is specified to have forEach, you can safely polyfill it, and it's really easy to do:
if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
Direct assignment is fine in this case, because enumerable, configurable, and writable should all be true and it's a value property. (enumerable being true surprised me, but that's how it's defined natively on Chrome/Chromium/Edge/Etc., Firefox, the old Legacy Edge, and Safari).
In your own code, you can do that with HTMLCollection as well if you want, just beware that if you're using some old DOM libs like MooTools or YUI or some such, they may be confused if you add forEach to HTMLCollection.
As I said before, NodeList and HTMLCollection are both specified to be iterable (because of this Web IDL rule¹). If you run into a browser that has ES2015+ features but doesn't make the collections iterable for some reason, you can polyfill that, too:
if (typeof Symbol !== "undefined" && Symbol.iterator && typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype[Symbol.iterator]) {
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}
(And the same for HTMLCollection.)
Here's a live example using both, try this on (for instance) IE11 (although it will only demonstrate forEach), on which NodeList doesn't have these features natively:
// Using only ES5 features so this runs on IE11
function log() {
if (typeof console !== "undefined" && console.log) {
console.log.apply(console, arguments);
}
}
if (typeof NodeList !== "undefined" && NodeList.prototype) {
// forEach
if (!NodeList.prototype.forEach) {
// Yes, there's really no need for `Object.defineProperty` here
console.log("Added forEach");
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Iterability - won't happen on IE11 because it doesn't have Symbol
if (typeof Symbol !== "undefined" && Symbol.iterator && !NodeList.prototype[Symbol.iterator]) {
console.log("Added Symbol.iterator");
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.iterator],
writable: true,
configurable: true
});
}
}
log("Testing forEach");
document.querySelectorAll(".container div").forEach(function(div) {
var html = div.innerHTML;
div.innerHTML = html[0].toUpperCase() + html.substring(1).toLowerCase();
});
// Iterable
if (typeof Symbol !== "undefined" && Symbol.iterator) {
// Using eval here to avoid causing syntax errors on IE11
log("Testing iterability");
eval(
'for (const div of document.querySelectorAll(".container div")) { ' +
' div.style.color = "blue"; ' +
'}'
);
}
<div class="container">
<div>one</div>
<div>two</div>
<div>three</div>
<div>four</div>
</div>
¹ It's confusing, because HTMLCollection is iterable but it isn't marked with the iterable declaration, which bizarrely in the JavaScript DOM bindings doesn't mean that something is iterable, it means that it has forEach, entries, keys, values, and it's iterable. But HTMLCollection, which isn't marked with the iterable declaration, is still iterable. Instead, it's iterable because of this Web IDL rule as mentioned earlier.
OK, let's start from here, in JavaScript, we have some cases which we call it Array-like, means even it looks like an array, it's not a real array...
For example arguments in function or in your case Nodelist...
Even All modern browsers understand which you'de like to change it to Array and work well, in IE and some other browsers it's not supported using array functions on Nodelist for example...
So if you supporting broad range of browsers, it's better to convert them to an array before doing any activity on them...
There are few ways to convert Array-like values to real Array...
One widely used in ES5 is this structure:
Array.prototype.slice.call(YourNodeList);
So you can do:
var allDivs = document.querySelectorAll("div");
var allRealDivsArray = Array.prototype.slice.call(allDivs);
But if you using ES6, there are even neater ways to do it, just make sure you convert them to ES5 using babel for example as those old browsers which not supporting looping over array-like, won't support ES6 as well for sure...
Two very common ways to do them are:
1) Using Array.from
const allDivs = document.querySelectorAll("div");
const allRealDivsArray = Array.from(allDivs);
2) Using [...Array]
const allDivs = document.querySelectorAll("div");
const allRealDivsArray = [...allDivs];
While it may look like an array, it's actually a NodeList which doesn't have the same features as an array.
Use a for loop instead
color_btns = document.querySelectorAll('#color > p');
for (var i = 0; i < color_btns.length; i++) {
color_btns[i].onclick = function () {
for (var j = 0; j < color_btns.length; j++) {
if(color_btns[j].classList.contains('selected')) {
color_btns[j].classList.remove('selected');
}
}
color_btns[i].classList.add('selected');
document.querySelector('#f_color').value = color_btns[i].dataset.id;
};
}
I'm seeing something weird when I try to include the ember.js library (ember-1.0.0-rc.7.js).
The javacode I have just prints out a javascript array:
<script type="application/javascript">
var songs = [ 'a','b','c'];
console.debug(songs.toString());
for(key in songs)
{
console.debug(songs[key]);
}
</script>
When I don't include the library, it'll print out a , b, c in the console. However, when i do include it, it start printing out a, b, c, but as well as all the functions to...
Example:
function (idx) {
return this.objectAt(idx);
}
function superWrapper() {
var ret, sup = this._super;
this._super = superFunc || K;
ret = func.apply(this, arguments);
this._super = sup;
return ret;
}
function (key) {
return this.mapProperty(key);
}
Any reason why this occurs with the ember.js library, and how do I resolve this issue?
Any advice appreciated,
Thanks,
D
By default, Ember extends built-in prototypes such as Array.prototype to provide extra methods or shim ES5 methods for non-supporting browsers. You are seeing these methods because for...in iterates over the enumerable properties of an object. These include all properties, even those inherited through the prototype chain.
Instead, you should use a regular for loop to iterate over an array:
for(var i=0; i<songs.length; i++) {
console.debug(songs[i]);
}
This will only ever go over actual array elements, i.e. properties with a numerical key. There are nicer ways though, for example using ES5 Array.forEach (shimmed by Ember in older browsers):
songs.forEach(function(song, i) {
console.debug(song);
});
Optionally, you can disable Ember's prototype extension by configuring Ember.EXTEND_PROPERTIES if you're not planning to use them or if they might conflict with other libraries/scripts. There's a whole page dedicated to this issue in the Ember documentation.
embjer.js must be adding functions to the native Array.prototype. You can check if each key is actually a property on the array itself, and not an inherited property, using hasOwnProperty:
for(key in songs) {
if(songs.hasOwnProperty(key)){
console.debug(songs[key]);
}
}
But, it is usually recommended to always use ordinary for loops with arrays, since they have numerical keys:
for(var i = 0; i < songs.length; i++) {
console.debug(songs[i]);
}
You can also use the forEach function that, ironically, Ember added to the Array prototype if it didn't already exist (in older browsers).
songs.forEach(function(song){
console.debug(song);
});
Demo
I have defined prototype for Array indexOf ( to support array indexOf in Internet Explorer )
if(!Array.prototype.indexOf){
Array.prototype.indexOf = function(obj){
for(var i=0; i<this.length; i++){
if(this[i]==obj){
return i;
}
}
return -1;
}
}
When I am creating array with values [1,2,3], this indexOf code snippet added into the Array like below
["1","2","3",function(obj){for(var i=0;i<this.length;i++){if(this[i]==obj){return i;}}return -1;}]
This problem happens only in IE.
Can anyone help me to resolve this issue. Thanks in advance.
I didn't use for...in loop anywhere, for this I am using jQuery sortable toArray method .sortable("toArray");.
I am assuming that at some point you are using a for...in loop to iterate over the elements of your array. For example:
for (var elem in myArray) {
//Do stuff...
}
A for...in loop will enumerate all enumerable properties of an object, including those it has inherited from it's ancestors in its prototype chain. You've added a method to the Array prototype:
Array.prototype.indexOf = function(obj){ //...
This property is enumerable (you can't define non-enumerable properties - see Object.defineProperty - in older versions of IE), so a for...in loop will include this property.
The simple solution is to never use a for...in loop to iterate over an array! Use a normal for loop instead.
Your problem is that your new indexOf() method is marked "enumerable." Unfortunately, there is no fix for this in IE7 or non-standards-mode IE8. But you can at least patch the issue for standards mode IE8 by using some ES5 trickery. Modify your code to look like this and it should get rid of the extra element if you're in IE8 standards mode:
(function () {
var indexOfFn = function(obj){
for(var i=0; i<this.length; i++){
if(this[i]==obj){
return i;
}
}
return -1;
};
if(!Array.prototype.indexOf){
if(typeof Object.defineProperty === "function") {
Object.defineProperty(Array.prototype, "indexOf", {
value: indexOfFn,
enumerable: false
});
} else {
Array.prototype.indexOf = indexOfFn;
}
}
}());
I know it's wordy, so it might not be worth the effort. But it will also protect your JavaScript from other people's bad coding where they might end up using an Array with a for-in loop.
It's quite simple, you're array now contains 4 elements, of which the forth is a function object, you're not defining a new method for the array object, let alone all array objects. Just paste the first snippet at the very top of your script, then:
var foo = [1,2,3];
alert(foo.indexOf(2));//alerts 1
Think of Array.prototype as the template of every array. Whenever you try to access some property or method of an array, that isn't defined, rather then throwing errors, JS will first check the Array.prototype if that object doesn't have that method/property. If it does, JS will use that code, and apply it to the array that initially called it. In the above example foo.indexOf(2) could have been written as Array.prototype.indexOf.apply(foo,[2]);. In other words: JS automatically applied the function of the prototype to foo.
Your "full" code should look like this:
if(!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(obj)
{
for(var i=0; i<this.length; i++)
{
if(this[i] === obj)//preferable, use strict comparison
{
return i;
}
}
return -1;
};
}
var yourArray = [1,2,3,4,'4'];
alert(yourArray.indexOf(4));//alerts 3
alert(yourArray.indexOf('4'));//alerts 4 when using strict comparison, if not, alerts 3
Here's a fiddle, checked in IE8, and it's working just fine
Just google prototypal inheritance and prototype chains or augmenting prototypes in JS and the like, read up and be baffled! ;)
When I want to call a function in javascript with arguments supplied from elsewhere I can use the apply method of the function like:
array = ["arg1", 5, "arg3"]
...
someFunc.apply(null, array);
but what if I need to call a constructor in a similar fashion? This does not seem to work:
array = ["arg1", 5, "arg3"]
...
someConstructor.apply({}, array);
at least not as I am attempting:
template = ['string1', string2, 'etc'];
var resultTpl = Ext.XTemplate.apply({}, template);
this does not work wither:
Ext.XTemplate.prototype.constructor.apply({}, template);
Any way to make that one work? (In this particular case I found that new Ext.XTemplate(template) will work, but I am interested in the general case)
similar question but specific to built-in types and without an answer I can use:
Instantiating a JavaScript object by calling prototype.constructor.apply
Thank you.
Edit:
Time has passed and ES6 and transpilers are now a thing.
In ES6 it is trivial to do what I wanted: new someConstructor(...array).
Babel will turn that into ES5 new (Function.prototype.bind.apply(someConstructor, [null].concat(array)))(); which is explained in How to construct JavaScript object (using 'apply')?.
There's no simple, straightforward way to do this with a constructor function. This is because special things happen when you use the new keyword to call a constructor function, and so if you're not going to do that, you have to emulate all of those special things. They are:
Creating a new object instance (you're doing that).
Setting that object's internal prototype to constructor function's prototype property.
Setting that object's constructor property.
Calling the constructor function with that object instance as the this value (you're doing that).
Handling the special return value from the constructor function.
I think that's about it, but worth double-checking in the spec.
So if you can avoid it and just use the constructor function directly, I'd do that. :-) If you can't, though, you can still do it, it's just awkward and involves workarounds. (See also this related answer here on StackOverflow, although I cover all of the ground here [and then some] as well.)
Your biggest issue is #2 above: Setting the internal prototype of the object. For a long time, there was no standard way to do this. Some browsers supported a __proto__ property that did it, so you can use that if it's there. The good news is that ECMAScript 5 introduces a way to do this explicitly: Object.create. So cutting-edge browsers like Chrome will have that. But if you're dealing with a browser that has neither Object.create nor __proto__, it gets a bit ugly:
1) Define a custom constructor function.
2) Set its prototype property to the prototype property of the real constructor function
3) Use it to create a blank object instance.
That handles the prototype for you. Then you continue with:
4) Replace the constructor property on that instance with the real constructor function.
5) Call the real constructor function via apply.
6) If the return value of the real constructor function is an object, use it instead of the one you created; otherwise, use the one you created.
Something like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Use a fake constructor function with the target constructor's
// `prototype` property to create the object with the right prototype
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
obj = new fakeCtor();
// Set the object's `constructor`
obj.constructor = ctor;
// Call the constructor function
newobj = ctor.apply(obj, params);
// Use the returned object if there is one.
// Note that we handle the funky edge case of the `Function` constructor,
// thanks to Mike's comment below. Double-checked the spec, that should be
// the lot.
if (newobj !== null
&& (typeof newobj === "object" || typeof newobj === "function")
) {
obj = newobj;
}
// Done
return obj;
}
You could take it a step further and only use the fake constructor if necessary, looking to see if Object.create or __proto__ are supported first, like this (live example):
function applyConstruct(ctor, params) {
var obj, newobj;
// Create the object with the desired prototype
if (typeof Object.create === "function") {
// ECMAScript 5
obj = Object.create(ctor.prototype);
}
else if ({}.__proto__) {
// Non-standard __proto__, supported by some browsers
obj = {};
obj.__proto__ = ctor.prototype;
if (obj.__proto__ !== ctor.prototype) {
// Setting it didn't work
obj = makeObjectWithFakeCtor();
}
}
else {
// Fallback
obj = makeObjectWithFakeCtor();
}
// Set the object's constructor
obj.constructor = ctor;
// Apply the constructor function
newobj = ctor.apply(obj, params);
// If a constructor function returns an object, that
// becomes the return value of `new`, so we handle
// that here.
if (typeof newobj === "object") {
obj = newobj;
}
// Done!
return obj;
// Subroutine for building objects with specific prototypes
function makeObjectWithFakeCtor() {
function fakeCtor() {
}
fakeCtor.prototype = ctor.prototype;
return new fakeCtor();
}
}
On Chrome 6, the above uses Object.create; on Firefox 3.6 and Opera, it uses __proto__. On IE8, it uses the fake constructor function.
The above is fairly off-the-cuff, but it mostly handles the issues I'm aware of in this area.
From developer.mozilla:
Bound functions are automatically suitable for use with the new operator to construct new instances created by the target function. When a bound function is used to construct a value, the provided this is ignored. However, provided arguments are still prepended to the constructor call.
That said, we still need to use apply to get the arguments out of an array and into the bind call. Further, we need to also reset bind's function as the this argument of the apply function. This gives us a very succinct one-liner that does exactly as needed.
function constructorApply(ctor, args){
return new (ctor.bind.apply(ctor, [null].concat(args)))();
};
Since the original question is quite old, here's a simpler methods in practically any modern browser (as of writing, 2018, I'm counting Chrome, FF, IE11, Edge...).
var template = ['string1', string2, 'etc'];
var resultTpl = Object.create(Ext.XTemplate.prototype);
Ext.XTemplate.apply(resultTpl, template);
Those two lines also explain how the new operator basically works.
I think another way of achieving this could be to extend the Ext Template class so that the constructor of the new object takes your array and does the stuff you want to do. This way all the constructing stuff would be done for you and you could just call the constructor of your superclass with your arguments.
I'm trying to figure out what's gone wrong with my json serializing, have the current version of my app with and old one and am finding some surprising differences in the way JSON.stringify() works (Using the JSON library from json.org).
In the old version of my app:
JSON.stringify({"a":[1,2]})
gives me this;
"{\"a\":[1,2]}"
in the new version,
JSON.stringify({"a":[1,2]})
gives me this;
"{\"a\":\"[1, 2]\"}"
any idea what could have changed to make the same library put quotes around the array brackets in the new version?
Since JSON.stringify has been shipping with some browsers lately, I would suggest using it instead of Prototype’s toJSON. You would then check for window.JSON && window.JSON.stringify and only include the json.org library otherwise (via document.createElement('script')…). To resolve the incompatibilities, use:
if(window.Prototype) {
delete Object.prototype.toJSON;
delete Array.prototype.toJSON;
delete Hash.prototype.toJSON;
delete String.prototype.toJSON;
}
The function JSON.stringify() defined in ECMAScript 5 and above (Page 201 - the JSON Object, pseudo-code Page 205), uses the function toJSON() when available on objects.
Because Prototype.js (or another library that you are using) defines an Array.prototype.toJSON() function, arrays are first converted to strings using Array.prototype.toJSON() then string quoted by JSON.stringify(), hence the incorrect extra quotes around the arrays.
The solution is therefore straight-forward and trivial (this is a simplified version of Raphael Schweikert's answer):
delete Array.prototype.toJSON
This produces of course side effects on libraries that rely on a toJSON() function property for arrays. But I find this a minor inconvenience considering the incompatibility with ECMAScript 5.
It must be noted that the JSON Object defined in ECMAScript 5 is efficiently implemented in modern browsers and therefore the best solution is to conform to the standard and modify existing libraries.
A possible solution which will not affect other Prototype dependencies would be:
var _json_stringify = JSON.stringify;
JSON.stringify = function(value) {
var _array_tojson = Array.prototype.toJSON;
delete Array.prototype.toJSON;
var r=_json_stringify(value);
Array.prototype.toJSON = _array_tojson;
return r;
};
This takes care of the Array toJSON incompatibility with JSON.stringify and also retains toJSON functionality as other Prototype libraries may depend on it.
Edit to make a bit more accurate:
The problem key bit of code is in the JSON library from JSON.org (and other implementations of ECMAScript 5's JSON object):
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
The problem is that the Prototype library extends Array to include a toJSON method, which the JSON object will call in the code above. When the JSON object hits the array value it calls toJSON on the array which is defined in Prototype, and that method returns a string version of the array. Hence, the quotes around the array brackets.
If you delete toJSON from the Array object the JSON library should work properly. Or, just use the JSON library.
I think a better solution would be to include this just after prototype has been loaded
JSON = JSON || {};
JSON.stringify = function(value) { return value.toJSON(); };
JSON.parse = JSON.parse || function(jsonsring) { return jsonsring.evalJSON(true); };
This makes the prototype function available as the standard JSON.stringify() and JSON.parse(), but keeps the native JSON.parse() if it is available, so this makes things more compatible with older browsers.
I'm not that fluent with Prototype, but I saw this in its docs:
Object.toJSON({"a":[1,2]})
I'm not sure if this would have the same problem the current encoding has, though.
There's also a longer tutorial about using JSON with Prototype.
This is the code I used for the same issue:
function stringify(object){
var Prototype = window.Prototype
if (Prototype && Prototype.Version < '1.7' &&
Array.prototype.toJSON && Object.toJSON){
return Object.toJSON(object)
}
return JSON.stringify(object)
}
You check if Prototype exists, then you check the version. If old version use Object.toJSON (if is defined) in all other cases fallback to JSON.stringify()
Here's how I'm dealing with it.
var methodCallString = Object.toJSON? Object.toJSON(options.jsonMethodCall) : JSON.stringify(options.jsonMethodCall);
My tolerant solution checks whether Array.prototype.toJSON is harmful for JSON stringify and keeps it when possible to let the surrounding code work as expected:
var dummy = { data: [{hello: 'world'}] }, test = {};
if(Array.prototype.toJSON) {
try {
test = JSON.parse(JSON.stringify(dummy));
if(!test || dummy.data !== test.data) {
delete Array.prototype.toJSON;
}
} catch(e) {
// there only hope
}
}
As people have pointed out, this is due to Prototype.js - specifically versions prior to 1.7. I had a similar situation but had to have code that operated whether Prototype.js was there or not; this means I can't just delete the Array.prototype.toJSON as I'm not sure what relies on it. For that situation this is the best solution I came up with:
function safeToJSON(item){
if ([1,2,3] === JSON.parse(JSON.stringify([1,2,3]))){
return JSON.stringify(item); //sane behavior
} else {
return item.toJSON(); // Prototype.js nonsense
}
}
Hopefully it will help someone.
If you don't want to kill everything, and have a code that would be okay on most browsers, you could do it this way :
(function (undefined) { // This is just to limit _json_stringify to this scope and to redefine undefined in case it was
if (true ||typeof (Prototype) !== 'undefined') {
// First, ensure we can access the prototype of an object.
// See http://stackoverflow.com/questions/7662147/how-to-access-object-prototype-in-javascript
if(typeof (Object.getPrototypeOf) === 'undefined') {
if(({}).__proto__ === Object.prototype && ([]).__proto__ === Array.prototype) {
Object.getPrototypeOf = function getPrototypeOf (object) {
return object.__proto__;
};
} else {
Object.getPrototypeOf = function getPrototypeOf (object) {
// May break if the constructor has been changed or removed
return object.constructor ? object.constructor.prototype : undefined;
}
}
}
var _json_stringify = JSON.stringify; // We save the actual JSON.stringify
JSON.stringify = function stringify (obj) {
var obj_prototype = Object.getPrototypeOf(obj),
old_json = obj_prototype.toJSON, // We save the toJSON of the object
res = null;
if (old_json) { // If toJSON exists on the object
obj_prototype.toJSON = undefined;
}
res = _json_stringify.apply(this, arguments);
if (old_json)
obj_prototype.toJSON = old_json;
return res;
};
}
}.call(this));
This seems complex, but this is complex only to handle most use cases.
The main idea is overriding JSON.stringify to remove toJSON from the object passed as an argument, then call the old JSON.stringify, and finally restore it.