Some general notes on the in
operator and the for ... in
statement.
These notes are based on the ECMAScript Specification Edition 3.
Contents
Testing for the non-existence of a named property
Properties in the prototype chain
Some properties names are ignored
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"]();
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'.");
}
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'.");
}
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'.");
}
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'.");
}
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);
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);
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"));
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;
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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
}
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.