To begin with, I always use the jQuery.each method only for working with a jQuery object. For the rest - for / while loops or its analogue «each». It seems to be logical, but the
documentation states that the above method can be used for any collections.
Let's destroy this myth.
JQuery logic
Let's look at the logic of the jQuery.each method. At the input, the type of the first parameter is checked, more precisely, whether it is an object or not:
length = obj.length, isObj = length === undefined || jQuery.isFunction( obj );
It turns out that if it does not have the length property, then this is an Object. And if length is - then this is Array.
Not very universal approach. Let's check.
Create an object "Obj" with several properties, among which we write and length.
var Obj = { name: "test", size: 2, length: 5 };
Now let's give it to jQuery.each:
$.each( Obj, function() { console.log( this, arguments ); } );
Result of performance:
Window [0, undefined] Window [1, undefined] Window [2, undefined] Window [3, undefined] Window [4, undefined]
Failure. jQuery defined it as an array, and used the dummy property for the for loop,
But how to get around this problem? I had a few ideas. Now we will walk along them gradually and get to the most suitable and universal implementation.
Method One: typeof
This method does not suit us, because both typeof and object and array are “object”. Moreover, if we also need to distinguish between strings and numbers, we will again get an “object” in the case of their creation using the new operator.
')
Method Two: instanceof
Here I was wrong. I thought it was a hat, a suitable way was found.
But to avoid the next pitfalls failed:
var a = {}; a instanceof Object; => true var b = []; b instanceof Array; => true var c = 5; c instanceof Number; => false var d = ""; d instanceof String; => false
There is no problem with the object and array. But with numbers and strings instanceof correctly works only if they are created through the operator new. Go ahead.
Method Three: constructor
Here it is, working way. Comparison of constructors.
var a = {}; a.constructor === Object; => true var b = []; b.constructor === Array; => true var c = 5; c.constructor === Number; => true var d = ""; d.constructor === String; => true
For the full picture, a table comparing the results:
Way | typeof | instanceof | constructor |
---|
new Array () | "Object" | true | true |
[] | "Object" | true | true |
new Object () | "Object" | true | true |
{} | "Object" | true | true |
new string () | "Object" | true | true |
"" | "String" | false | true |
new Number () | "Object" | true | true |
five | "Number" | false | true |
Conclusion:
Perhaps the each method should look something like this (I did not take a piece with args for the demonstration):
each: function( obj, callback, args ) { var name, i = 0, length = obj.length, isObj = obj.constructor === Object, isArray = obj.constructor === Array || obj.constructor === String; if ( isObj ) { for ( name in obj ) { if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) { break; } } } else if ( isArray ) { for ( ; i < length; ) { if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) { break; } } } }
Thank you all for your attention.
upd.Thanks for the helpful comments. The purpose of the article is, first of all, to show how to differentiate data types in JavaScript, and not to reveal a bug in jQuery.
I agree that the method using "toString" is more suitable. Soon I will make changes to the article. Thanks again for the feedback. For me, this topic is interesting, respectively, I also want to figure out how best.