Mohammad
Mohammad

Reputation: 121

for..in loop & Object.keys different behavior

I have this object function constructor:

const Shape = function(name){
this.name = name;
}
Shape.prototype.getName = function(){
    return this.name;
};

and I have this instance

const square = new Shape("square");

When I use for loop to iterate over the square object I can see that the iteration process is being happen over the prototype of the square object

for (const key in square) console.log(key);
/* #output:
name
getName
*/

but when I use the Object.keys() function I can see that the iteration process is not iterating over the prototype object

/* #output:
["name"]
*/

What is the behind the scenes reason for that?

Here is what I've tried:

I've tried to console.log the descriptor of the getName method from the prototype object, I've seen that the enumerable attribute is set to true by default:

console.log(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(square), "getName"))

/* #output:
configurable: true
enumerable: true
value: ƒ ()
writable: true
__proto__: Object
*/

Upvotes: 2

Views: 44

Answers (2)

T.J. Crowder
T.J. Crowder

Reputation: 1073968

What is the behind the scenes reason for that?

for-in loops through all of the string-named enumerable properties in the object, including inherited ones.

Object.keys only gives you an array of the object's own (non-inherited) string-named enumerable properties. Just for illustration, it's similar to:

// Rough, not-exact-in-every-detail implementation of `Object.keys`
// in terms of `for-in`
const result = [];
for (const name in object) {
    if (Object.prototype.hasOwnProperty.call(object, name)) {
        result.push(name);
    }
}

There's also Object.getOwnPropertyNames which gives you an array of all of the objects's own string-named properties (even the non-enumerable ones), and Object.getOwnPropertySymbols which gives you an array of the object's own Symbol-named properties (even the non-enumerable ones). (There's no Symbol equivalent of Object.keys.)


I've tried to console.log the descriptor of the getName method from the prototype object, I've seen that the enumerable attribute is set to true by default:

It is when you create the property via assignment as you do in your question. If you create the property via Object.defineProperty, or by using method syntax in a class construct, enumerable defaults to false.

Examples:

// Your original code:
const Shape = function(name){
    this.name = name;
}
Shape.prototype.getName = function(){
    return this.name;
};
showFlag(Shape.prototype, "getName");       // true

// Using `Object.defineProperty`:
const Shape2 = function(name){
    this.name = name;
}
Object.defineProperty(Shape2.prototype, "getName", {
    value: function(){
        return this.name;
    },
    writable: true,     // These also default to `false`, but you
    configurable: true  // usually want them to be `true` for methods
});
showFlag(Shape2.prototype, "getName");      // false 

// Using `class` syntax
class Shape3 {
    constructor(name) {
        this.name = name;
    }
    getName() {
        return this.name;
    }
}
showFlag(Shape3.prototype, "getName");      // false


function showFlag(proto, name) {
    console.log(Object.getOwnPropertyDescriptor(proto, name).enumerable);
}

Upvotes: 2

CertainPerformance
CertainPerformance

Reputation: 370589

Object.keys only iterates over enumerable own properties. In contrast, for..in iterates over all enumerable properties anywhere on the object's prototype chain.

With this code:

const Shape = function(name){
    this.name = name;
}
Shape.prototype.getName = function(){
    return this.name;
};

A Shape instance is receiving an own property of a name, so it gets iterated over by both iteration methods. In contrast, getName is on an instance's prototype - it's not a property of the instance itself, so it isn't returned in Object.keys:

const Shape = function(name){
    this.name = name;
}
Shape.prototype.getName = function(){
    return this.name;
};
const square = new Shape("square");
console.log(
  square.hasOwnProperty('name'),
  square.hasOwnProperty('getName'),
  Shape.prototype.hasOwnProperty('getName')
);

Upvotes: 4

Related Questions