Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Henry Hollenstain
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Henry Hollenstain

Javascript's IN Operator Does Not Work With Strings

By
Published in Comments (27)

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

15,811 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.

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.

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.

15,811 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.

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

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

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

15,811 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.

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

15,811 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!

290 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?

15,811 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.

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

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

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

15,811 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.

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

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.

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.

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.

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;

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel