ECMAScript proposal: Accessible Object.prototype.hasOwnProperty()

[2021-06-17] dev, javascript, es proposal
(Ad, please don’t block)

In this blog post, we examine the ECMAScript proposal “Accessible Object.prototype.hasOwnProperty() (by Jamie Kyle and Tierney Cyren). It proposes a new, simpler way of checking if an object has an own (non-inherited) property.

Checking if an object has an own property  

In this section, we look at existing ways of checking if an object has an own property. Each of them has downsides, which is why Object.hasOwn() is needed.

The in operator isn’t always what we want  

The in operator checks if an object has a property with a given name:

> 'name' in {name: 'Jane'}
true
> 'name' in {}
false

However, if we treat objects as dictionaries, then we want to ignore inherited properties (and in considers them). For example, interpreted as a dictionary, the following object should be empty, but in says that it has the property 'toString':

> 'toString' in {}
true

That’s because {} inherits method .toString() from Object.prototype:

> Object.getPrototypeOf({}) === Object.prototype
true

.hasOwnProperty() isn’t safe  

All objects created via object literals inherit the method .hasOwnProperty() from Object.prototype. That method seems to do what we want:

> {name: 'Jane'}.hasOwnProperty('name')
true
> {}.hasOwnProperty('name')
false
> {}.hasOwnProperty('toString')
false

However, .hasOwnProperty() fails in two cases.

First, we can create objects that don’t inherit from Object.prototype and don’t have that method:

> Object.create(null).hasOwnProperty('name')
TypeError: Object.create(...).hasOwnProperty is not a function

Second, if an object has an own property with the name 'hasOwnProperty', then that property overrides Object.prototype.hasOwnProperty and we can’t access it:

> {hasOwnProperty: 'yes'}.hasOwnProperty('name')
TypeError: {(intermediate value)}.hasOwnProperty is not a function

Accessing Object.prototype.hasOwnProperty() directly  

To fix the issues we encountered in the previous section, we need to access Object.prototype.hasOwnProperty() directly:

function hasProp(obj, propName) {
  return Object.prototype.hasOwnProperty.call(obj, propName);
}
assert.equal(
  hasProp(Object.create(null), 'name'), false);
assert.equal(
  hasProp({hasOwnProperty: 'yes'}, 'name'), false);

hasProp() works exactly like Object.hasOwn().

The proposal: Object.hasOwn()  

The new proposal works exactly the way we want:

> Object.hasOwn({name: 'Jane'}, 'name')
true
> Object.hasOwn({}, 'name')
false
> Object.hasOwn({}, 'toString')
false
> Object.hasOwn(Object.create(null), 'name')
false
> Object.hasOwn({hasOwnProperty: 'yes'}, 'name')
false
> Object.hasOwn({hasOwn: 'yes'}, 'name')
false

A warning about using objects as dictionaries  

In general, objects are not good dictionaries: There are several pitfalls we have to contend with.

Instead, Maps are great dictionaries and have no pitfalls.

Frequently asked questions about Object.hasOwn()  

Wouldn’t Object.hasOwnProperty() be a better name?  

Alas, that name isn’t available anymore:

> 'hasOwnProperty' in Object
true

Why? Object is a function, which is an object:

> const proto = Object.getPrototypeOf.bind(Object);
> proto(Object) === Function.prototype
true
> proto(proto(Object)) === Object.prototype
true

What is the difference between Object.hasOwn() and Reflect.has()?  

Reflect.has() works like the in operator and considers inherited properties:

> Reflect.has({ownProp: 123}, 'ownProp')
true
> Reflect.has({ownProp: 123}, 'toString')
true

Object.hasOwn() ignores inherited properties:

> Object.hasOwn({ownProp: 123}, 'ownProp')
true
> Object.hasOwn({ownProp: 123}, 'toString')
false

Why wasn’t method .hasOwn() added to Reflect?  

Reflect only contains ECMAScript Proxy traps. Therefore, it is not a suitable location for a utility method such as .hasOwn().

Availability of Object.hasOwn()  

More information on object-oriented programming in JavaScript  

Material in the book “JavaScript for impatient programmers”: