Blog Entry - 27th May 2007 - Programming - JavaScript

The In Crowd


Some general notes on the in operator and the for ... in statement.

These notes are based on the ECMAScript Specification Edition 3.

Properties

As you will know, an Object is an unordered collection of properties, each with a name and a value.

The name of a property could be alpha-numeric: myProperty or myProperty1.

The name of a property could be numeric: 1, 2 etc; which is the case for the numbered elements of an Array.

The value of a property could be a Number, String etc.

The value of a property could also be a Function, which is also sometimes called a method.

A property is accessed using either the objectInstance.propertyName notation, or the objectInstance["propertyName"] notation. In other languages one also sees objectInstance->propertyName notation.

So for example:-

var obj = new Object();

obj.property = "Property";

obj.method = function() {
	alert("Method");
};


alert(obj.property);
alert(obj["property"]);

obj.method();
obj["method"]();

editplay

On occasion you may wish to test whether a given Object has a property with a given name, or list all of the properties of an Object.

This is where the in operator and for ... in statement come in.

The in operator

Purpose

The in operator is defined in Section 11.8.7 of the specification.

The in operator's purpose is to inspect the named properties of an object you to supply to it.

For example:-

var obj /*: Object*/ = {"a" : 1};

if ("a" in obj)
{
	alert("'obj' has a property named 'a'.");
}

editplay

Property values do not matter

Note that it does not test the value of a named property, just whether a property of that name exists at all.

So the alert will fire even if the property value is 0, ""(empty string), null or undefined,thus:-

var obj /*: Object*/ = {"a" : null};

if ("a" in obj)
{
	alert("'obj' has a property named 'a'.");
}

editplay

Testing for the non-existence of a named property

A subtle point of interest ocurrs where you wish to test for the non-existence of a property.

Consider the following:-

var obj /*: Object*/ = {"a" : 0, "b" : 1, "c" : 2};

if (!"d" in obj)
{
	alert("Did not find 'd' as a named property of 'obj'.");
}

editplay

You will find that the alert does not fire.

This is because the expression !"d" is evaluated first to false, and the test becomes if (false in obj). The reason it is evaluated first is because in terms of operator precedence, the logical NOT operator ! is higher up the order than the in operator.

A solution is to use brackets:-

var obj /*: Object*/ = {"a" : 0, "b" : 1, "c" : 2};

if (!("d" in obj))
{
	alert("Did not find 'd' as a named property of 'obj'.");
}

editplay

This way the "d" in obj part is evaluated first, and then the ! operator.

Older Browsers

Of course, the in operator may not be supported in older browsers (Internet Explorer 5.0 and below) so the fall back is to use typeof object[propertyName] == "undefined".

But this is not a complete solution because, as noted above, you can have properties which exist, but which are undefined, so you can only use this where you do not expect to have or do not mind missing such properties.

var obj /*: Object*/ = {};
obj.a = undefined;
obj.b = 0;

alert("a" in obj);
alert(typeof obj.a);
alert(typeof obj.b);

editplay

This may be an corner-case you are not concerned with.

Properties in the prototype chain

A point to be wary of, is that the in operator also tests for properties in the prototype chain.

Thus:-

function Base()
{
	this.baseProperty = null;
}

function Child()
{

}
Child.prototype = new Base();

var childInstance = new Child();

alert("baseProperty" in childInstance);

editplay

This is because the in operator uses the internal [[HasProperty]] method (Section 8.6.2.4 of the specification) which recursively looks up the prototype chain.

This can cause an issue, if what you really want to do is test whether an Object has its own property with a given name, and you know that there may be a property with the same name further up the prototype chain.

The solution is to use the hasOwnProperty method which is built-in to all native JavaScript objects:-

function Base()
{
	this.baseProperty = null;
}

function Child()
{

}
Child.prototype = new Base();

var childInstance = new Child();

alert(childInstance.hasOwnProperty("baseProperty"));

editplay

Again, the potential problem with this is that it may not be supported by older browsers. Internet Explorer 5.0 and below.

There is no easy alternative for older browsers, apart from expressly giving access to the prototype and comparing the object and the object's prototype:-

function Base()
{
	this.firstProperty = 1;
	this.thirdProperty = 1;
}

function Child()
{
	this.firstProperty = 1;
	this.secondProperty = 1;

}
Child.prototype = new Base();

// Important, as Internet Explorer and Opera do not
// give access to the internal [[Prototype]] property
Child.prototype.proto = Child.prototype;


var childInstance = new Child();

alert(HasOwnProperty(childInstance, "firstProperty"));
alert(HasOwnProperty(childInstance, "secondProperty"));
alert(HasOwnProperty(childInstance, "thirdProperty"));


/*
 *
 *  (JavaScript Function)
 *
 *  HasOwnProperty
 *
 *  June 2007
 *
 *  A function which seeks to test if a given Object
 *  has its own property with a given name.
 *  
 *  For older browsers, you need to give constructed objects
 *  access to their immediate constructor.
 *
 *  Written by Julian Turner 2007
 *  Copyright Free
 * 
 *  @param (Object) object
 *      An 'Object' reference to the Object which you are testing
 *
 *  @param (String) propertyName
 *      The property name.
 *
 *  @return (Boolean)
 *  	true or false
 */


function HasOwnProperty (
	targetObject /*: Any*/, 
	propertyName /*: String*/
) /*: Boolean*/
{
	if (typeof targetObject == "undefined" || targetObject === null)
	{
		return false;
	}

	if (typeof targetObject[propertyName] == "undefined")
	{
		return false;
	}

	if (typeof targetObject.hasOwnProperty)
	{
		return targetObject.hasOwnProperty(propertyName);
	}

	var proto /*: Object*/ = (targetObject.__proto__) ? targetObject.__proto__ : (targetObject.proto) ? targetObject.proto : (targetObject.prototype)? targetObject.prototype : (targetObject.constructor) ?  targetObject.constructor.prototype : null;

	if (!proto)
	{
		return true;
	}

	if (typeof proto[propertyName] == "undefined")
	{
		return true;
	}

	if (targetObject[propertyName] !== proto[propertyName])
	{
		return true;
	} 

	var propertyValue /*: Any*/ = proto[propertyName];
	proto[propertyName] = Math.random();
	var b /*: Boolean*/ = (targetObject[propertyName] !== proto[propertyName]);
	proto[propertyName] = propertyValue;
	return b;

}

editplay

The for ... in loop

Basic Usage

The for (<property-name> in <object>) loop (Section 12.6.4 of the spec.) will loop through each of the named properties of an object and supply you with the property name of each.

It is available as of Internet Explorer version 5.0.

The variable propertyName in the following example is filled with each successive named property):-

var obj /*: Object*/ = {"a" : 0, "b" : 1, "c" : 2};

for (var propertyName /*: String*/ in obj)
{
	alert(propertyName);
}

editplay

This will alert: a then b then c.

This can be especially useful if you do not know what all the named properties of an object are.

Property values do not matter

Again, as with the in operator, the for...in statement will enumerate through named properties again even if the property value is 0, "" (empty string), null or undefined.

var obj /*: Object*/ = {"a" : null, "b" : "", "c" : 0, "d" : undefined};

for (var propertyName /*: String*/ in obj)
{
	alert(propertyName);
}

editplay

This will again alert: a, b, c then d.

Accordingly if you do wish to exclude properties with those values, you must do this explicitly:-

var obj /*: Object*/ = {"a" : null, "b" : "", "c" : 0, "d" : undefined};
var value /*: Any*/;

for (var propertyName /*: String*/ in obj)
{
	value = obj[propertyName];

	if (value === null || value === "" || value === 0 || typeof value == "undefined")
	{
		continue;
	}

	alert(propertyName);
}

editplay

Some properties names are ignored

You need to be aware that some properties or methods of JavaScript are ignored, because they are labelled with an internal DontEnum (which the spec. says : "The property is not to be enumerated by a for-in enumeration."), thus you will not see any of the built-in properties or methods of Array in the following:-

var a /*: Array*/ = [0, 1, 2, 3];

for (var propertyName /*: String*/ in a)
{
	alert(propertyName);
}

editplay

Properties in the prototype chain

Again you must be wary of the fact that it will also look at properties in the prototype chain.

function Base()
{
	this.firstProperty = 1;
	this.thirdProperty = 1;
}

function Child()
{
	this.firstProperty = 2;
	this.secondProperty = 1;
}
Child.prototype = new Base();

var childInstance = new Child();

for (var propertyName /*: String*/ in childInstance)
{
	alert(propertyName);
}

editplay

If you want to only see the properties of the object itself, then you must use hasOwnProperty as discussed above.

function Base()
{
	this.firstProperty = 1;
	this.thirdProperty = 1;
}

function Child()
{
	this.firstProperty = 2;
	this.secondProperty = 1;
}
Child.prototype = new Base();

var childInstance = new Child();

for (var propertyName /*: String*/ in childInstance)
{
	if (!childInstance.hasOwnProperty(propertyName))
	{
		continue;
	}

	alert(propertyName);
}

editplay

Order of property values

A final point to note, is that you cannot rely at all on the order in which property names appear in a for ... in statement.

For some browsers, the order seems to depend on the order in which the properties were added, whereas for others, they may internally sort the properties by name.

Thus:-

var obj /*: Object*/ = {"d" : 1, "c" : 1, "b" : 1, "a": 1};
obj.e = 3;

for (var propertyName in obj)
{
	alert(propertyName);
}

editplay

Updates Since First Published

Watch out for literals [ADDED 22nd July 2007]

The in operator is liable to fail if you supply it a string literal, even when you think it might not.

There are reasons for this in the specification: literals are converted to temporary objects internally when you call methods on them, so you are liable to be fooled into thinking they are objects.

var stringLiteral = "12345";

alert(stringLiteral.toString());

try
{
	alert("toString" in stringLiteral);
}
catch(e)
{
	alert(e.description || String(e));
}

for (k in stringLiteral)
{
	alert(k);  // Will Not Fire
}

editplay


Comment(s)


Sorry, comments have been suspended. Too much offensive comment spam is causing the site to be blocked by firewalls (which ironically therefore defeats the point of posting spam in the first place!). I don't get that many comments anyway, so I am going to look at a better way of managing the comment spam before reinstating the comments.


Leave a comment ...


{{PREVIEW}} Comments stopped temporarily due to attack from comment spammers.