Javascript's IN Operator Does Not Work With Strings

Posted May 11, 2010 at 8:39 AM by Ben Nadel

Tags: Javascript / DHTML

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.




Reader Comments

May 11, 2010 at 8:50 AM // reply »
19 Comments

Why not just test if ( typeof arg === 'string' ) { ... } ?


May 11, 2010 at 8:55 AM // reply »
11,246 Comments

@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.


May 11, 2010 at 9:23 AM // reply »
2 Comments

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.


May 11, 2010 at 9:29 AM // reply »
11,246 Comments

Just a quick follow-up to this post - using the hasOwnProperty() method in lieu of the IN operator.

http://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.


May 11, 2010 at 10:18 AM // reply »
29 Comments

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.


May 11, 2010 at 10:20 AM // reply »
11,246 Comments

@Martin,

Really? That's odd. I wonder what that's all about. Thanks for the insight.


May 11, 2010 at 10:30 AM // reply »
29 Comments

Really really :)
Take a look at this (especially the evaluation examples)
https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String


May 11, 2010 at 10:47 AM // reply »
11,246 Comments

@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.


May 11, 2010 at 3:54 PM // reply »
29 Comments

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 :-)


May 11, 2010 at 4:39 PM // reply »
132 Comments

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. :/


May 11, 2010 at 6:25 PM // reply »
272 Comments

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.


May 16, 2010 at 10:04 PM // reply »
11,246 Comments

@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.


May 16, 2010 at 10:26 PM // reply »
19 Comments

@Ben

NS4 = Netscape 4. The birthplace of modern javascript.


May 16, 2010 at 10:53 PM // reply »
11,246 Comments

@Elliott,

Ahhhh, I remember that beast.


May 17, 2010 at 10:49 AM // reply »
70 Comments

@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.


May 17, 2010 at 10:55 AM // reply »
11,246 Comments

@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!


May 17, 2010 at 6:23 PM // reply »
272 Comments

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?


May 18, 2010 at 9:05 PM // reply »
11,246 Comments

@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.


Jul 5, 2010 at 11:14 PM // reply »
70 Comments

@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".


Jul 6, 2010 at 9:00 PM // reply »
272 Comments

@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.


Jul 6, 2010 at 9:13 PM // reply »
272 Comments

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]".


Jul 18, 2010 at 12:32 PM // reply »
11,246 Comments

@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.


Han
Jun 30, 2011 at 4:37 AM // reply »
4 Comments

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

  • var stringValue = "";

you had tried

  • var stringValue = new String( "" );

then

  • in

would've worked fine and returned

  • true

.

Secondly, if instead of

  • var numberValue = new Number( 1 );

you had tried

  • var numberValue = 1;

then you would have gotten the TypeError when you tried

  • console.log( "Number", ("length" in numberValue) );

Thirdly, if instead of trying

  • console.log( "Boolean", ("length" in true) );

or however you had tried testing boolean values, you had done

  • console.log( "Boolean", ("length" in new Boolean( true ) ) );

it would in fact have worked and returned

  • false

.

Fourthly, I wonder why

  • in

doesn't work on

  • null

or

  • undefined

, either?

Fifthly, guess what value gets logged if you try

  • var numberValue = 1;
  • numberValue.someProp = 'prop';
  • console.log( numberValue.someProp ); //=> undefined

versus trying

  • var numberValue = new Number( 1 );
  • numberValue.someProp = 'prop';
  • console.log( numberValue.someProp ); //=> "prop"

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

  • console.log( typeof 1 ); //=> "number"
  • console.log( typeof "" ); //=> "string"
  • console.log( typeof true ); //=> "boolean"
  • console.log( typeof false ); //=> "boolean"
  • console.log( "Everything's as expected, what's up with the next ones though?" );
  • console.log( typeof new Number( 1 ) ); //=> "object"
  • console.log( typeof new String( "" ) ); //=> "object"
  • console.log( typeof new Boolean( true ) ); //=> "object"
  • console.log( typeof new Boolean( false ) ); //=> "object"

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

  • in

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

  • 'primitive-valued string literal'.split(' ')

before, where I'm accessing the

  • split

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

  • split

property of that string value? Explain this:

  • var str = 'primitive-valued string literal';
  • console.log( str.split(' ') ); //=> obviously ["primitive-valued", "string", "literal"]
  • str.split = function(){ return 'overridden!'; };
  • console.log( str.split(' ') ); //=> still ["primitive-valued", "string", "literal"]

Wait a minute...

  • var str = new String( 'primitive-valued string literal' );
  • console.log( str.split(' ') ); //=> obviously ["primitive-valued", "string", "literal"]
  • str.split = function(){ return 'overridden!'; };
  • console.log( str.split(' ') ); //=> still ["primitive-valued", "string", "literal"]

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,

  • null

and

  • undefined

aren't auto-boxed. That is why, not only does

  • in

through a TypeError on them, but any property access or assignment on a

  • null

or

  • undefined

value will through a TypeError.

But that's merely annoying. Here's something that's downright wrong:

  • console.log( typeof undefined ); //=> "undefined", no surprise there
  • console.log( typeof null ); //=> "object" wait what?

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

  • in

. Even the ECMAScript 3 spec says it has a primitive type--it's own special null type, in fact. But when describing the

  • typeof

operator, it has a special exception for

  • null

, 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
Jun 30, 2011 at 4:43 AM // reply »
4 Comments

@Han,

Damnit, typos, should be:

Wait a minute...

  • var str = new String( 'primitive-valued string literal' );
  • console.log( str.split(' ') ); //=> obviously ["primitive-valued", "string", "literal"]
  • str.split = function(){ return 'overridden!'; };
  • console.log( str.split(' ') ); //=> "overridden!" that's more like it

Also, didn't expect all my &lt;code/&gt; blocks to be made display:block, usually they're just font-family: monospace but still display:inline. Oh well, still mostly readable.


Jul 24, 2011 at 12:39 AM // reply »
1 Comments

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.


Nov 22, 2011 at 12:23 PM // reply »
1 Comments

Please copy and paste the following script in your JavaScript console:

  • (function(undefined) {
  • var i, v = [
  • undefined, void(0),
  • null,
  • false,
  • 0, NaN, -1/0, 1/0,
  • '0',
  • [],
  • {}, new (function(){})(),
  • new Date(),
  • function() {}
  • ];
  •  
  • function whatis(v) {
  • var ops = Object.prototype.toString;
  • if (undefined === v || null === v) {
  • return v + '';
  • }
  • return ops.call(v);
  • }
  •  
  • for (i = 0; i < v.length; i++) {
  • console.log(whatis(v[i]), ' | ', 'length' in Object(v[i]));
  • }
  • })();

Just my two cents.


CWO
Oct 24, 2012 at 9:40 AM // reply »
1 Comments

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;



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 25, 2013 at 10:01 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Avi, Really glad to help! @Jaredwilli, I'm finding a this image hits home with a lot of people :) Hopefully we can all work through the rough patches together! @Prateek, AngularJS has error ... read »
May 25, 2013 at 9:53 PM
Nested Views, Routing, And Deep Linking With AngularJS
@Mrsean2k, I'm glad I could help! I haven't been able to keep up with the ui-router stuff. I keep saying that I'll carve out time, but I just haven't gotten to it :( ... read »
May 25, 2013 at 9:49 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, Thanks for the book recommendations. I am looking them up right now. I can see that Object Thinking is available for the Kindle App - sweet! Also, I just recently heard Martin Fowler on the ... read »
May 25, 2013 at 9:41 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
@Chris, I'm super excited to hear that my posts are helpful. I am also loving AngularJS; but, it definitely has some caveats and some odd behaviors and some things that just don't seem to "wor ... read »
May 25, 2013 at 9:36 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam, @Jason, After reading these comments, I double-checked my latest implementation and I am happy to report that I am using listFirst() and listRest(). ... read »
May 25, 2013 at 9:31 PM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Daxesh, I am not sure I understand the question about the current node. If you already have a reference to the current node, why would you need to query for it? As for parent node, I believe that ... read »
May 25, 2013 at 10:08 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
@Ben, my question is that i want the current node with its tag and its parent node. i just want only that data. So, give me the solution for that. and remember solution is working on " xpath 1.0 ... read »
May 25, 2013 at 10:01 AM
Using "//" And ".//" Expressions In XPath XML Search Directives In ColdFusion
hey ben, i want get my current node tag and also want the root node tag withing. So, how can i fix it.. ! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools