Why are Object.keys() and for ... in different? - javascript

I'm trying to do a bit of browser object discovery, figuring out browser built-ins etc...
I noticed different results when trying to get at the window object's properties (just FYI I'm using Chrome Version 41.0.2272.89 (64-bit)).
Object.keys(window).length;
returns 7 keys. From the docs Object.keys() returns the enumerable properties of an object.
But the docs also say that for ... in iterates over the enumerable properties of an object. However:
var i = 0;
for (var propertyName in window) {
i++;
}
returns a count of 177.
Why is this different? Shouldn't they both only be returning the count of enumerable properties?

for-in loops over the object's own enumerable properties and the enumerable properties of its prototype (and its prototype, etc.). Object.keys only lists the object's own enumerable properties.
So Object.keys builds an array something like this:
var keys = [];
var key;
for (key in object) {
if (object.hasOwnProperty(key)) { // But using an internal, non-overrideable
// operation, not literally the method
keys.push(key);
}
}
Note the hasOwnProperty check (it's not really a call to the method, it's an internal check that can't be tricked by replacing the method or similar).

Related

MongoDB get specific key from document based on given value

I am getting a Document as:
async findOne(id: string) {
return await this.gameModel.findById(id);
}
async update(id: string, updateGameDto: UpdateGameDto) {
const game = await this.findOne(id)
// This gives all keys as expected
for( const key in game){
console.log(key)
}
// ...
const keys = Object.keys(game) // [ '$__', '$isNew', '_doc' ]
return;
}
Why does Object.keys(game) only return those 3 keys? If it only returns those keys, obviously I can't get the key as so:
const specificKeyByValue = Object.keys(game).find(key => game[key] === "SomeValue")
I could create a function that simply returns the key with a for loop like;
const getKeyByValue = (obj, value) =>
{
for( const key in obj)
{
if(obj[key] === value) return key;
}
}
But I prefer to stay away from creating extra functions if I don't have to. Any idea why this Object.Keys() variant doesn't work?
There are few differences in behaviour of Object.keys and for-in.
for-in iterates over all enumerable properties of an object that are keyed by strings (ignoring ones keyed by Symbols), including inherited enumerable properties.
As per the MDN docs this is what for-in does:
The loop will iterate over all enumerable properties of the object
itself and those the object inherits from its prototype chain
(properties of nearer prototypes take precedence over those of
prototypes further away from the object in its prototype chain).
A for...in loop only iterates over enumerable, non-symbol properties.
Objects created from built–in constructors like Array and Object have
inherited non–enumerable properties from Array.prototype and
Object.prototype, such as Array's indexOf() method or Object's
toString() method, which will not be visited in the for...in loop.
And the difference (docs)
Object.keys() returns an array whose elements are strings
corresponding to the enumerable string-keyed property names found
directly upon object. This is the same as iterating with a for...in
loop, except that a for...in loop enumerates properties in the
prototype chain as well. The order of the array returned by
Object.keys() is the same as that provided by a for...in loop.
Check few examples here

Can't list window.document properties - why?

Running this JavaScript lists in Firefox 60.2 only one property ("location"), but there are many others, like "document.title" etc.
window.console.log("List props: " + Object.keys(window.document).sort().join(' / '));
Why is it this way? Safety? How is this done technically?
How can I list all properties?
Object.keys(o) returns the own, enumerable properties of o.
Own: properties defined directly on o, not on its prototype chain:
Enumerable: a flag that controls if a given property is included when listing an object's properties.
In this case most of the keys you expect are defined on another object in in document's prototype chain:
document.hasOwnProperty("title"); // false
document.__proto__.__proto__.hasOwnProperty("title"); // true
document.__proto__.__proto__.propertyIsEnumerable("title"); // true
You can use a for... in loop to find the enumerable properties that are defined on an object or its prototype chain:
for (let key in window.document) {
console.log(key);
}
The reason is that Object.keys() returns returns an array of strings that represent all the enumerable properties of the given object.
Try this to see which properties of document are enumerable
for(let key in document){
let isEnumerable = document.propertyIsEnumerable(key);
console.log(`docment['${key}'] isEnumerable?:${isEnumerable}`);
}
However as the previous answer has stated you can use a for-in loop to get all properties in an array sort them and join them
I couldn't find an official reason for it not working with window.document but it seems you can reproduce this behavior with other variables as well.
The issue seems to be Object.keys() not returning everything from these variables.
If you're still trying to get all properties of document, you can still use
var props = [];
for(var prop in window.document) {
props.push(prop);
}
console.log("List props: " + props.sort().join('/'));
Which will do the exact same thing as your approach.

Why can't I get properties count of navigator object in JavaScript?

Run in your browser (ES5+)
var propCount = Object.keys(navigator).length;
console.log(propCount); // 0
If you do it for a plain object like that
let obj = {
foo: 'bar',
breaking: 'bad'
}
let propCount = Object.keys(obj).length;
console.log(propCount); // 2
Why does it happen?
Sorry if it might relate to another problem like when Object.keys(obj) is only counting it for simple objects which do not contain functions/arrays, but this the 1st time I encountered with it.
And would like to know the reason of it.
Object.keys() function returns properties of the object that are directly assigned to it. If you do the following:
console.log(navigator.hasOwnProperty('permissions')); // false
If you want to see properties of navigator do the following:
for(let i in navigator){
console.log(i);
}
It will list all the properties of navigator object because for ... in ... loop includes object's prototype.
That's because most properties of navigator are set on the Navigator prototype, which the navigator instance inherits, and Object.keys only returns the properties set on the navigator object itself.
You can get those properties from the prototype with this:
Object.keys(Object.getPrototypeOf(navigator));
On a side note, Firefox has the following properties in the navigator object itself:
[ "doNotTrack", "mozPay", "mozContacts", "mozApps" ]
There are enumerable and non enumerable properties in javascript. Object.keys() would return the enumerable own properties of an object. So in navigator object, it seems that there is no enumerable own properties. Hence Object.keys(navigator) is returning an empty array.
From the doc,
The Object.keys() method returns an array of a given object's own
enumerable properties, in the same order as that provided by a
for...in loop.
If you want to list out all the enumerable and non enumerable properties of a certain object, then you have to write your own logic by using Object.getOwnPropertyNames().
Note : getOwnPropertyNames will return both enum/non enum own
properties of an object.
function getAllProps(obj, props = []){
if(Object.getPrototypeOf(obj) == null){ return props; }
return getAllProps(Object.getPrototypeOf(obj),
props.concat(Object.getOwnPropertyNames(obj)));
}
console.log(getAllProps(navigator));
//This will give you all the properties.
If you use for..in loop then that will give you only the enumerable properties across the prototype chain. And you will have to miss the non enumerable properties.

JS "for (var key in arr)" > throws my own Array.prototypes, but not the inherent ones. Why?

I accidently used an iteration like
for (var key in arr)
{
alert(key);
}
and got an output that rose some questions. (I wanted to iterate through a JSON, so I used 'key' - an 'i' for index would be more suitable in this case, no question :-)
The first outputs were the indeces (like '0', '1' etc.) - still no question - but at last it throws the names of my Array.prototypes, I declared somewhere else in the code. Prototypes like
Array.prototype.lastIndex = function(){
return this.length - 1;
}
and it throws just 'lastIndex'.
What I don't understand: Are my own, non-inherent prototypes distinct from the JS-inherent ones like splice(), slice() ... ?
And: Why does it throw them at all? They are part of the Array.prototype, but not of a particular Array object!?
Thx in advance!
When you attach a property to an object, by default, the property will be enumerable.
If a property is enumerable, for..in will pick it up.
So, when you are iterating an array object with for..in, it first gives all the enumerable properties in the current object. Then, it goes up the prototype chain and gives all the enumerable properties of the corresponding objects. So, in your case, it goes up the chain and finds an enumerable property called lastIndex in Array's prototype, includes that in the iteration.
If you define it with enumerable: false, like this
Object.defineProperty(Array.prototype, 'lastIndex', {
value: function() {
return this.length - 1;
},
enumerable: false
});
it wont show up in for..in iterations. Quoting MDN documentation for enumerable attribute,
The enumerable property attribute defines whether the property shows up in a for...in loop and Object.keys() or not.
Note: Always use normal for loops for array iterations.
Any array instances inherits from Array.prototype. So any change to the prototype will be reflected in those array instances as well.
If you wanted it to remain only to arr you would attach the function lastIndex just to it, so other array instances won't get affected.
So it appears in for..in loop, which iterates through enumerable properties, as lastIndex is an enumerable property.

iteration an object and hasownproperty

I was looking at a clone object function here:
http://jsperf.com/cloning-an-object/2
the function is:
function clone(obj) {
var target = {};
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
target[i] = obj[i];
}
}
return target;
}
and i was wondering why is the check
if (obj.hasOwnProperty(i))
needed ?
Because if property i is not in the object obj, it wouldn't be iterated in the first place in the for loop.
Am I missing something ?
The for...in construct also loops over inherited properties.
If you create an object with a constructor, for example like this :
var s = new String();
then you have all enumerable String functions listed among the properties, but not as own properties (i.e. direct properties). For example try this in the console :
for (var k in s) console.log(k)
You'll discover a few interesting functions that were probably indispensable to SO developers like formatUnicorn.
This check lets you clone direct enumerable properties without cloning the prototype properties.
See documentation on the MDN.
Because the original loop will also show properties from the prototype object, which you wouldn't want.
It's worth mentioning that as of JavaScript 1.8.5 you can use Object.keys(obj) to get an Array of properties defined on the object itself
(ones that return true for obj.hasOwnProperty(key)).
This is better (and readable) than using for-in loop.
Its supported on these browsers:
Firefox (Gecko): 4 (2.0)
Chrome: 5
Internet Explorer: 9
More info on
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

Categories