Javascript's IN Operator Does Not Work With Strings
A while back, I learned that you could use the Javascript IN operator to test for object property existence. This was a great find because it tested for the presence of a key and not just the key's value (which might evaluate to False). Since all core data types in Javascript extend the base Object in one way or another, I figured that the IN operator would work with all data types. This, however, turns out to be a poor assumption.
When I was updating my jQuery Template Markup Language (JTML) project, I wanted to make it so that the template constructor could accept either a jQuery collection (pointing to a Script tag) or a raw JTML markup string. In order to differentiate between these two types of objects, I put in the following code to test for a jQuery collection:
if ("jquery" in source){ ...jquery logic... }
I figured if this evaluated to True, I was dealing with a jQuery collection; and, if this evaluated to False, I was dealing with a JTML string. This worked fine if I passed-in a jQuery collection, but it would error out if I passed-in a JTML string. As it turns out, the IN operator doesn't seem to like working on String "objects." To test this further, I set up the following demo code:
<!DOCTYPE HTML>
<html>
<head>
<title>Javascript IN Operator And String Objects</title>
<script type="text/javascript">
// Create a number of different data types to test.
var stringValue = "";
var objectValue = {};
var arrayValue = [];
var dateValue = new Date();
var numberValue = new Number( 1 );
// -------------------------------------------------- //
// -------------------------------------------------- //
// Run control group known to work.
console.log( "Object", ("length" in objectValue) );
// Run test on array.
console.log( "Array", ("length" in arrayValue) );
// Run test on date object.
console.log( "Date", ("length" in dateValue) );
// Run test on number object.
console.log( "Number", ("length" in numberValue) );
// Try to use the IN operator on a String value.
console.log( "String", ("length" in stringValue) );
</script>
</head>
<body>
<!-- Intentionally left blank. -->
</body>
</html>
As you can see, I am trying to use Javascript's IN operator on an instance of Object, Array, Date, Number, and String. When I run this code, I get the following console output:
Object false
Array true
Date false
Number false
invalid 'in' operand stringValue
[Break on this error] console.log( "String", ("length" in stringValue) );
As you can see, the IN operator worked fine on everything except the String value. I am not sure why this is the case. Considering the fact that even Number works with the IN operator, I am not sure why String values are being treated so differently. Regardless, it might be a best practice to test the type of object before you use the IN operator on it.
NOTE: Although not demonstrated above, Boolean values also seem to be incompatible with the IN operator.
Want to use code from this post? Check out the license.
Reader Comments
Why not just test if ( typeof arg === 'string' ) { ... } ?
@Cowboy,
For some reason, I have just never been a fan of the typeof() function. I think that is what I ended up going with (I don't remember off-hand). Really, I guess what would have made the most sense if something like this could work:
if ("html" in source){
source = source.html();
}
In this way, I could use duck-typing to get the HTML value of the given script tag. That way, I could accept jQuery collections as well as any other object that has an html() method... of course, I suppose that that point I'm just trying to flexible for the sake of flexibility, which is probably the wrong reason.
I tried this.
var n = 1.2;
alert('length of n = ' + ('length' in n));
I also generates:
invalid 'in' operand n
[Break on this error] console.log('length of n = ' + ('length' in n));
I suppose scalar type (other than object inherited type) always has such problem.
Just a quick follow-up to this post - using the hasOwnProperty() method in lieu of the IN operator.
www.bennadel.com/blog/1919-Javascript-s-hasOwnProperty-Method-Is-More-Consistent-Than-The-IN-Operator.htm
The hasOwnProperty() method seems to be much more consistent; however, the big difference between this and the IN operator is that the hasOwnProperty() method does *not* search the prototype chain for inherited properties.
There is a difference between string primitives and String objects in Javascript.
When you change the line
var stringValue = "";
to
var stringValue = new String("");
it will work as expected. Javascript is weird ;)
String primitives and String objects give also different results when using the eval function.
@Martin,
Really? That's odd. I wonder what that's all about. Thanks for the insight.
Really really :)
Take a look at this (especially the evaluation examples)
https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String
@Martin,
In the Mozilla center, they do say that:
Because JavaScript automatically converts between string primitives and String objects, you can call any of the methods of the String object on a string primitive. JavaScript automatically converts the string primitive to a temporary String object, calls the method, then discards the temporary String object.
But, this might be for only the core String objects methods and not the methods that it inherits from Object?
Anyway, thanks for pointing this out - this is all new to me.
From my understanding it temporarily converts a string literal to a String object if you try to call a member function of String (either inherited or core).
But since IN is an operator and technically no member function, there is no convertion.
Anyway, I too find it confusing to have string primitives and objects coexist. Especially because they act the same most of the time due to the implicit conversion.
Glad I could help a bit, because I've already learned so much from you. In fact your jQuery presentation (ca. 1.5 years ago) was my first contact with jQuery and Javascript. Big Thanks :-)
The next iteration of JS is supposed to finally get rid of the string primitive problem. Martin is right that the problem is that "in" is an operator and not a method.
"foo" in new String("bar") does work for that reason.
I'm pretty sure all this nonsense came about for performance reasons back inside the NS4 code base. Shame we still live with it today. :/
In my experience, a "for (... in ...)" loop on a string iterates over the characters of the string.
So I just now ran this experiment:
$(document).ready(function()
{
var sName = "";
var sString = "lit";
for (sName in sString)
alert("sString[" + sName + "] = '" + sString[sName] + "'.");
sString = new String("new");
for (sName in sString)
alert("sString[" + sName + "] = '" + sString[sName] + "'.");
});
In Firefox 3.6.3, Google Chrome 4.1, Netscape 8.1, Opera 10.53 and Safari 4.0.5, I got just an iteration over the characters in both strings. No length property. In MSIE 7, I got nothing at all.
I can try it again on my Mac when I get home, if you like.
@Elliott,
Ah right, it's a operator, not a method. Good point. I am not sure what NS4 is, but I'll just go with it.
@Steve,
Oh cool, though a shame that doesn't seem to be fully cross-browser compliant.
@Ben
NS4 = Netscape 4. The birthplace of modern javascript.
@Elliott,
Ahhhh, I remember that beast.
@Ben:
My point was not to show something cool, but rather to reveal more about the nature of "in (string)". Specifically, length doesn't show up in the loop on any browser so far.
I just now looked through the PDFs of the 3rd and 5th editions of the standard (from ecmascript.org), and it seems that properties can have properties. One of them is whether or not a property is enumerable, which is what affects the in operator.
It was freaky enough, once upon a time, to learn that methods can have methods. Now I've got to wrap my head around properties having properties.
@Steve,
Ha ha - properties having properties. Did you take the red pill or the blue pill? It's quite the dynamic language! I have to say though, having functions have functions and other properties has been something that I have grown to love!
Of course, It's easy to think of implementation-defined properties of properties. For example, in the DOM, window.location has window.location.href.
I meant LANGUAGE-defined IMPLICIT properties, like the language-defined implicit methods .call() and .apply() that all functions have.
Apparently there's this whole underbelly of language-defined properties that properties have, but we don't know they have them, because they're not enumerable, including the enumerable property itself.
Here's an interesting question: Does a function's .call() method have its own .call() and .apply methods? The answer is yes, they do, in every browser I've tried. That means that they can't possibly exist until they're referenced, or else the first time you define a function would cause an infinite loop.
And here's another interesting question: If you do functionname.call(object1).call(object2), which object becomes this? The answer to that one is consistent across browsers too.
Ohh, what's really going to bake your noodle later on is, would you still have called it if I hadn't said anything?
@Steve,
Call() having its only call() method?!? Ouch, my brain hurts - why would do such a thing! I just tried this:
var girl = {
name: "Sarah",
sayHello: function(){ alert( this.name ); }
};
girl.sayHello();
girl.sayHello.call( girl );
girl.sayHello.call.call( girl.sayHello, girl );
This is like some code-obfuscation contest.
@Ben: I just finished reading Douglas Crockford's book Javascript: The Good Parts. Appendix A is The Awful Parts, and one of those is "Phony Arrays".
Turns out, JavaScript arrays are simulations based on object properties that just happen to be non-negative integers. Many of the things we think of as arrays are not even descended from Array, such as arguments[...] within a function.
For my 2¢, I think of something as a JavaScript Array object if it has a length property and all of the methods I've come to associate with Arrays, such as join(), push(), sort(), etc. The way to test that, from both jQuery 1.4.2's isArray() and Crockford's book, is Object.prototype.toString.call(obj) === "[object Array]". You can also do that to detect Strings with "[object String]". You can also use apply() instead of call(), because toString() doesn't take any arguments.
So, it turns out, the call() method of every Function object, but in particular, of toString(), actually is a way to detect whether an object truly is an Array. No need for "in".
@Ben: One more thing... JavaScript: The Good Parts answers your original observation, that "in" doesn't work with strings. Quoting Chapter 3 "Objects", first paragraph:
<ul>
"The simple types of JavaScript are numbers, strings, booleans (true and false), null and undefined. All other values are objects. Numbers, strings and booleans are object-like in that they have methods, but they are immutable. Objects in JavaScript are mutable keyed collections. ..." [bold emphasis added]
</ul>
In your example code at top of page, you wrapped 1 in an object wrapper with numberValue = new Number ( 1 ), but you didn't do that with stringValue. If you had defined string value as new String ( "" ) instead, "length" in stringValue would have returned true. I know this, because I just tested it in Firefox and MSIE and got true in both.
So the problem was that you said "length" in simpletype, not "length" in objecttype. So it WAS an invalid operand.
Don't feel bad. I've been doing JavaScript since it was called LiveScript (and was case-insensitive, believe it or not), and I didn't realize this about strings either.
P.S.: Put that together with my previous observation (that Object.prototype.toString.call(obj) === "[object String]" can be used to detect strings), and you see that Object.toString() lies in all browsers. String is not an object unless you wrap it in an object wrapper with new String().
It could very well be that the initialization code of Object.prototype.toString() casts the operand into an object, so that it can call internal methods that it wouldn't otherwise be able to call. In other words, toString() might not have been lying, per se, but rather, was being not as precise as we would like.
It really ought to have returned "[value String]" or "[simpletype String]".
@Steve,
I've heard nothing but great things about "The Good Parts" book. It's probably time that I get a copy for myself. This stuff is all very interesting - thanks for the super insight.
I'm surprised no one else has explained this more clearly, though most of the pieces are scattered throughout the comments.
I'm going to start by poking some holes in your tests, and also point out some JavaScript quirks you probably haven't noticed, which I hope will motivate my excruciatingly detailed (but hopefully painfully clear) explanation.
Firstly, if instead of
you had tried
then
would've worked fine and returned
.
Secondly, if instead of
you had tried
then you would have gotten the TypeError when you tried
Thirdly, if instead of trying
or however you had tried testing boolean values, you had done
it would in fact have worked and returned
.
Fourthly, I wonder why
doesn't work on
or
, either?
Fifthly, guess what value gets logged if you try
versus trying
I'm sure by now you notice a pattern in what works as expected and what doesn't. Well, don't get ahead of yourself, have you ever tried
Starting to get the picture?
One of the shortcomings JavaScript borrowed from Java was the distinction between primitive types and the object type.
Number values, like 1,
string values, like "",
the boolean values true and false,
and the special values null and undefined
all have primitive types,
while all other kinds of values, including the builtin
Date objects,
regular expression objects,
function objects,
arrays
and literal objects like {},
all have the object type.
What's the difference between primitive types and the object types? Plenty, the one you're noticing here being that the
operator only works on objects, but the main one is that you can access and assign properties of objects, whereas primitive values are just naked values.
But wait, you say, I've totally done
before, where I'm accessing the
property of the string, which I expect to be a function, and then call that function (such properties which hold function values are also called methods) !
Really? You think you're accessing the
property of that string value? Explain this:
Wait a minute...
In Java, this feature is called auto-boxing. Whenever you access or assign to a property of a number, string or boolean, a temporary object value (of the Number, String or Boolean class, respectively) is created with the same naked value as the primitive value, but that temporary object is only available to that property access, and does not replace the primitive value that your variable references.
I hope that makes perfect sense now. Elsewise, this next part will be even more confusing.
Unlike number, string or boolean values, the other two primitive values,
and
aren't auto-boxed. That is why, not only does
through a TypeError on them, but any property access or assignment on a
or
value will through a TypeError.
But that's merely annoying. Here's something that's downright wrong:
The null value absolutely has a primitive type, as you can see for yourself that assigning or accessing a property of a null value throws a TypeError, as does
. Even the ECMAScript 3 spec says it has a primitive type--it's own special null type, in fact. But when describing the
operator, it has a special exception for
, simply because this was already the case in pre-existing widespread implementations of ECMAScript 3 (that would be NS4).
If there's anything in my explanation that could be further clarified, please let me know.
I have one last request: I admire your impulse to experiment and test, which is very important, but reading the documentation is important, too. Your blog has pretty high Google rankings, so I've come across it when looking up JS quirks before, and I believe that like w3schools.com, you have some small responsibility to provide accurate information. But every time I've come across your blog, it's been clear you didn't check the ECMAScript 3 spec and your conclusions about how stuff works are just wild guesses based on limited tests, and since of course you don't blog about obvious, expected behavior, your blog posts are about subtle, confusing quirks, so your understanding has invariably been flawed, just like this. So next time you run some tests and come up with "I am not sure why this is the case", I urge you, maybe try reading the documentation.
tl;dr: RTFM, my friend
@Han,
Damnit, typos, should be:
Wait a minute...
Also, didn't expect all my <code/> blocks to be made display:block, usually they're just font-family: monospace but still display:inline. Oh well, still mostly readable.
I am almost positive the reason this does not work is because of the way JavaScript uses string objects as wrappers. When you call a method of a string, it passes it to new String() and calls the method on that newly generate object. Once that object is done with, it deletes it. Try calling it like this "length" in new String(stringValue). Remember strings are not objects until they are conveyed to be.
Please copy and paste the following script in your JavaScript console:
Just my two cents.
Well, it's pretty simple, actually. Consistent results come out of consistent data.
You are initialising your sting as a primitive value and your number as an object. Native values don't have properties and in particular they don't have the method 'in'.
To have normalised data you can compare you need to either initialise your string as
var stringValue = new String("");
or initialise your number value as
var numberValue = 1;