Reputation: 121
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
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
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