Friday, May 17, 2024
 Popular · Latest · Hot · Upcoming
88
rated 0 times [  91] [ 3]  / answers: 1 / hits: 19590  / 14 Years ago, mon, april 26, 2010, 12:00:00

I'm trying to write a function that needs to know the property names of an object being passed in, like so:



var data = { key1:value1, key2:value2, etc}
^ i want the string value key1


How do I retrieve the string key1 from data? I know I can set a property dynamically like data[prop]=value but i want to know what prop is from an object passed in.



If that doesn't make sense I suppose I could try to explain more. Thanks!



I eventually want to do something like:



for (var i = 0; i<data.length; i++)
{
var name = data[i].getPropertyName() <--- not a real function
// do stuff
}

More From » javascript

 Answers
36

Before we look at our options, a quick note of the four key things about properties in JavaScript:




  1. Objects can have properties of their own, and properties they inherit from their prototype object.

  2. Properties can be enumerable or non-enumerable.

  3. Properties can have names that are strings, or (as of ES2015/ES6) names that are Symbols.

  4. Properties cannot have names that are numbers, like 1. Sometimes we act like they do, as when we're dealing with arrays, but they don't. Standard arrays aren't really arrays at all (per the spec; JavaScript implementations are allowed to optimize when they can), and the entries in arrays are object properties whose names are strings. So a = ['x', 'y', 'z'] defines an array with three properties whose names are 0, 1, and 2. When we do a[0] to access the first one, the number 0 is converted to a string. (In theory; again, the JavaScript implementation is allowed to optimize.)



All of these properties can be discovered and enumerated (even the non-enumerable ones).
You have several options for doing so:




  • A for-in loop (spec | MDN), with or without a hasOwnProperty guard inside the loop to differentiate between own and inherited properties. (Does not include properties named with Symbols.) Loops through the names of the properties.

  • Object.keys (spec | MDN) (ES5+), which returns an array of the names of an object's own, enumerable properties. (Does not include properties named with Symbols.)

  • Object.getOwnPropertyNames (spec | MDN) (ES5+), which returns an array of the names of an object's own properties, regardless of whether they're enumerable. (Does not include properties named with Symbols.)

  • Reflect.enumerate (spec | MDN) (ES2015+), which returns an iterator for the names of the enumerable properties of an object, including ones it inherits. (Does not include properties named with Symbols.) It was removed in ES2016.

  • Object.getOwnPropertySymbols (spec | MDN) (ES2015+), which returns an array of the names of an object's own properties named with Symbols, regardless of whether they're enumerable. (Leaves out ones named with strings.)

  • Reflect.ownKeys (spec | MDN) (ES2015+), which returns an array of the names of an object's own properties no matter how they're named (Symbol or string), and whether they're enumerable or not.



As you can see, most of the operations only include properties whose names are strings, with only Object.getOwnPropertySymbols and Reflect.ownKeys giving us the ones named with Symbols.



The order of the keys is not defined (not even in ES2015) for for-in or Object.keys. In ES2015 and above, it is defined for the other four, by the [[OwnPropertyKeys]] and (where applicable) [[Enumerate]] operations. (Since ES2015 is still [as of this writing] relatively knew, it's possible not all JavaScript engines correctly implement the order yet.)



Let's look at examples. First, some setup:



// Create an object with one own property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}

// Create an object that uses the above as its prototype
var obj = Object.create(proto);

// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;

// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, four, {
value: 4,
configurable: true,
writable: true
});


(Object.create was added in ES5, but the version of it taking just one argument [as above] can easily be shimmed/polyfilled for obsolete JavaScript engines, like the one in IE8. Object.defineProperty was also added in ES5, and cannot be correctly shimmed/polyfilled.)



Since most of the operations only involve properties named by strings, we're ignoring Symbols for now.



Once the code above runs, we have this in memory (the * next to a name indicates it's a non-enumerable property):




+−−−−−−−−−−−−−−−−−+
Object.prototype−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−>| toString* |−−>(...a function...)
| | valueOf* |−−>(...a function...)
| | hasOwnProperty* |−−>(...a function...)
| | ... |
| +−−−−−−−−−−−−−−−−−+
|
+−−−−−−−−−−−−−−−+ |
proto−−−−−−−−−−−−−−−−−−−−+−−>| [[Prototype]] |−−+
| | one: 1 |
| +−−−−−−−−−−−−−−−+
|
+−−−−−−−−−−−−−−−+ |
obj−−>| [[Prototype]] |−−+
| two: 2 |
| three: 3 |
| four*: 4 |
+−−−−−−−−−−−−−−−+


With that setup, let's look at our options:



for-in



for-in loops through the names of all of an object's properties (including ones it inherits from its prototype) whose names are strings (leaving out any whose names are Symbols).



for (var name in obj) {
// name is the name of each property, so:
console.log(name + = + obj[name]);
}




// Create an object with one own property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}

// Create an object that uses the above as its prototype
var obj = Object.create(proto);

// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;

// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, four, {
value: 4,
configurable: true,
writable: true
});

for (var name in obj) {
// name is the name of each property, so:
console.log(name + = + obj[name]);
}





With that, we see




two = 2
three = 3
one = 1


...or similar. The order you see the properties in is not defined, not even in ES2015.



for-in with a hasOwnProperty guard



If we wanted the loop, but ignoring inherited properties, we can add a hasOwnProperty check:



for (var name in obj) {
if (obj.hasOwnProperty(name)) {
console.log(name + = + obj[name]);
}
}




// Create an object with one own property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}

// Create an object that uses the above as its prototype
var obj = Object.create(proto);

// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;

// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, four, {
value: 4,
configurable: true,
writable: true
});

for (var name in obj) {
if (obj.hasOwnProperty(name)) {
console.log(name + = + obj[name]);
}
}





With that, we see




two = 2
three = 3


We don't see one because it's inherited.



Since hasOwnProperty is a method on the object, in theory it could be overridden with a version that didn't return what we expect:



obj.hasOwnProperty = function() {
return true;
};


...which would fool our loop above. That's not usually an issue, but if you want to guard against it, use the one from Object.prototype instead:



var hasOwn = Object.prototype.hasOwnProperty;
for (var name in obj) {
if (hasOwn.call(obj, name)) {
console.log(name + = + obj[name]);
}
}


Of course, someone can assign to the Object.prototype.hasOwnProperty property as well, but if they do, a fair number of things are likely to break.



Object.keys (ES5+, easily shimmed/polyfilled)



Object.keys gives us an array of the names of the object's own, enumerable properties named with strings. So it doesn't include inherited properties, properties marked non-enumerable, or properties named with Symbols.



var propNames = Object.keys(obj);


We can then loop over the entries in that array in any of several ways, such as forEach:



propNames.forEach(function(name) {
console.log(name + = + obj[name]);
});




// Create an object with one own property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}

// Create an object that uses the above as its prototype
var obj = Object.create(proto);

// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;

// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, four, {
value: 4,
configurable: true,
writable: true
});

var propNames = Object.keys(obj);
propNames.forEach(function(name) {
console.log(name + = + obj[name]);
});





With our sample setup, that gives us:




two = 2
three = 3


The order of the names in the array is not defined by the specification, not even in ES2015.



Object.getOwnPropertyNames (ES5+)



Object.getOwnPropertyNames returns an array of the names of the object's own properties named with strings, whether enumerable or not. It leaves out properties named with Symbols.



var propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function(name) {
console.log(name + = + obj[name]);
});




// Create an object with one own property (plus the ones it
// inherits from Object.prototype):
var proto = {
one: 1
}

// Create an object that uses the above as its prototype
var obj = Object.create(proto);

// Add a couple of enumerable properties
obj.two = 2;
obj.three = 3;

// Add a non-enumerable property (by default, properties created
// with Object.defineProperty are non-enumerable)
Object.defineProperty(obj, four, {
value: 4,
configurable: true,
writable: true
});

var propNames = Object.getOwnPropertyNames(obj);
propNames.forEach(function(name) {
console.log(name + = + obj[name]);
});





With our sample setup, that gives us:




two = 2
three = 3
four = 4


The order of the names in the array is defined by the [[OwnPropertyKeys]] operation in the specification, which says the order is:




  • The property names that qualify as integer indexes, in numeric order.

  • The names for other properties, in the order the property was created.



So we get two, three, four because none of those fits the spec's definition of an integer index, and that's the order in which we added the properties. If we added them in a different order, or included ones that qualified as indexes, we'd get different results:



var obj2 = {};
obj2.four = 4;
obj2[0] = zero;
obj2.two = 2;
obj2.three = 3;
Object.getOwnPropertyNames(obj2).forEach(function(name) {
console.log(name + = + obj2[name]);
});




var obj2 = {};
obj2.four = 4;
obj2[0] = zero;
obj2.two = 2;
obj2.three = 3;
Object.getOwnPropertyNames(obj2).forEach(function(name) {
console.log(name + = + obj2[name]);
});





Gives us:




0 = zero
four = 4
two = 2
three = 3


0 was first because although it's a string, it fits the criteria for an integer index. Then we get four because it was created first, then two, then three.



Reflect.enumerate (ES2015) Removed in ES2016



Reflect.enumerate was removed in ES2016.




Reflect.enumerate uses the new iterator feature of ES2015. It returns an iterator that will iterate over the names of the string-named enumerable properties of the object, including inherited ones, skipping ones named with Symbol or that aren't enumerable. First it visits the own properties (in the order defined by [[OwnPropertyKeys]], and then inherited properties (unless they've been hidden by own properties).



We can use the new for-of loop to loop over them:



for (let name of Reflect.enumerate(obj)) {
console.log(name + = + obj[name]);
}


With our setup, that gives us:




two = 2
three = 3
one = 1


one is at the end because it's inherited, and own properties come first.



Note: Again, since ES2015 is relatively new as of this writing, some JavaScript engines may not implement the Reflect object yet.





Object.getOwnPropertySymbols (ES2015+)



Object.getOwnPropertySymbols returns an array of an object's own properties named with Symbols, regardless of whether they're enumerable. (Leaves out ones named with strings.) We'll need a different setup to try this, since we didn't include Symbol-named properties in our main setup. Since inherited properties are ignored, we'll keep it simple:



var obj3 = {};
obj3[Symbol(x)] = ecks;
obj3[1] = one;
obj3[Symbol(y)] = why;
obj3.z = zee;
Object.getOwnPropertySymbols(obj3).forEach(function(symbol) {
console.log(symbol.toString() + = + obj3[symbol]);
});




var obj3 = {};
obj3[Symbol(x)] = ecks;
obj3[1] = one;
obj3[Symbol(y)] = why;
obj3.z = zee;
Object.getOwnPropertySymbols(obj3).forEach(function(symbol) {
console.log(symbol.toString() + = + obj3[symbol]);
});





Output:




Symbol(x) = ecks
Symbol(y) = why


z wasn't listed because it has a string name, not a Symbol name. Symbol(x) was first because it was created first.



There's a lot more to Symbols than shown here, but as you can see, if we give a symbol a name, we can use toString to get back Symbol(the name here) as a string. Interesting, we have to call toString explicitly (thesymbol.toString()) or use String(theSymbol); the + operator will not do it for us when appending a symbol to a string.



Reflect.ownKeys (ES2015+)



Reflect.ownKeys returns an array of an object's own properties no matter how they're named (Symbol or string), and whether they're enumerable or not. It ignores inherited properties:



var obj3 = {};
obj3[Symbol(x)] = ecks;
obj3[1] = one
obj3[Symbol(y)] = why;
obj3.z = zee;
Reflect.ownKeys(obj3).forEach(function(key) {
console.log(key.toString() + = + obj3[key]);
});




var obj3 = {};
obj3[Symbol(x)] = ecks;
obj3[1] = one
obj3[Symbol(y)] = why;
obj3.z = zee;
Reflect.ownKeys(obj3).forEach(function(key) {
console.log(key.toString() + = + obj3[key]);
});





Output:




1 = one
z = zee
Symbol(x) = ecks
Symbol(y) = why


Note the order, which is defined by [[OwnPropertyKeys]]: 1 was first because it's a string that qualifies as an integer index. z was next because it's a string-named property. Then we have the Symbol-named properties, in the order in which they were created.


[#96959] Saturday, April 24, 2010, 14 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
devinjadong

Total Points: 711
Total Questions: 117
Total Answers: 100

Location: Andorra
Member since Sat, May 27, 2023
1 Year ago
devinjadong questions
Thu, Feb 17, 22, 00:00, 2 Years ago
Wed, Dec 8, 21, 00:00, 2 Years ago
Tue, Oct 27, 20, 00:00, 4 Years ago
Fri, Oct 18, 19, 00:00, 5 Years ago
;