Course: JavaScript

Progress (0%)

JavaScript Object Methods

Chapter 41 28 mins

Learning outcomes:

  1. The hasOwnProperty() instance method
  2. The isPrototypeOf() instance method
  3. The propertyIsEnumerable() instance method
  4. Merging objects via Object.assign()
  5. The Object.create() method
  6. The Object.defineProperty() and Object.defineProperties() methods
  7. Retrieving all own properties via Object.getOwnPropertyNames()
  8. The Object.getPrototypeOf() and Object.setPrototypeOf() methods

Introduction

So far in this unit, we've explored a ton of information about objects in JavaScript. The object data type is undoubtedly the king of JavaScript, perfecting which is of immense importance for every single JavaScript developer.

In this chapter, we aim to learn about a couple of utilities, specifically methods provided at our dispense to easily inspect various details about given objects and even create new objects.

Let's begin...

The hasOwnProperty() method

The instance method hasOwnProperty() as we saw in the chapter, returns true if the given property is the calling object's own property or else false.

Let's recap its syntax:

obj.hasOwnProperty(propName)

propName is the name of the property for which we ought to determine if it's obj's own property.

Now, let's use this method on a couple of objects and see the result in each case:

var o = { x: 0, y: 0 }
undefined
var proto = { a: 10, b: 20 }
undefined
Object.setPrototypeOf(o, proto)
{ x: 0, y: 0 }
o.hasOwnProperty('x')
true
o.hasOwnProperty('y')
true
o.hasOwnProperty('a')
false
o.hasOwnProperty('b')
false
proto.hasOwnProperty('x')
false
proto.hasOwnProperty('y')
false
proto.hasOwnProperty('a')
true
proto.hasOwnProperty('b')
true
In the snippet above, we've used Object.setPrototypeOf() to change the prototype of o to proto. This is only for the purposes of explanation of hasOwnProperty() — in production code, you should, or at least try your best to, not use Object.setPrototypeOf().

The isPrototypeOf() method

To determine whether a given object is the prototype of another object, we have a couple of ways at our dispense. On of them is to use the object's isPrototypeOf() method.

The isPrototypeOf() method takes in a single obj argument and returns true if it is the prototype of that argument, or else false.

Syntactically, the method could be shown as follows:

protoObj.isPrototypeOf(obj)

Shown below is the isPrototypeOf() method being used with a couple of values:

var o = { x: 0, y: 0 }
undefined
var proto = { a: 10, b: 20 }
undefined
Object.setPrototypeOf(o, proto)
{ x: 0, y: 0 }
o.isPrototypeOf(proto)
false
proto.isPrototypeOf(o)
true

The propertyIsEnumerable() method

As the name suggests, an object's propertyIsEnumerable() method tells us whether a given property is that object's enumerable own property.

The syntax of the method is quite easy to understand:

obj.propertyIsEnumerable(propName)

propName is the name of the property that ought to be checked if it's obj's own enumerable property.

As before, let's test this method:

var o = { x: 0 }
undefined
Object.defineProperty(o, 'y', {
    value: 0,
    enumerable: false
})
undefined
var proto = { a: 10 }
undefined
Object.setPrototypeOf(o, proto)
{ x: 0, y: 0 }
o.propertyIsEnumerable('x')
true
o.propertyIsEnumerable('y')
false
o.propertyIsEnumerable('a')
false

As can be seen here, only o.propertyIsEnumerable('x') returns true, since x is o's own enumerable property. The rest two calls return falsey is o's own property but not enumerable, and similarly a is an enumerable property but not o's own property.

The Object.assign()

If we wish to merge multiple objects into one single object, we could use the static Object.assign() method.

The first argument to the method is the object on which to merge all objects. That is, this object is mutated by adding properties from other objects. It's called the target.

All subsequent arguments provide the method with objects from which we ought to copy properties to the target (i.e. the first argument). Each of these objects is called a source since it provides us with the source from where to obtain properties to be copied to the target.

Here's the syntax of Object.assign():

Object.assign(target[, source1[, source2[, ...[, sourceN]]]])

target is the target object. This object will be mutated with properties from all subsequent sources (if any).

source1 to sourceN specify source objects whose properties' values are assigned to the same properties on target. Later source objects' properties override the values of the properties on earlier source objects.

Object.assign() is much easier seen that read. Let's see a couple of examples.

Consider the code below:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };

We have two simple objects obj1 and obj2.

Now, if we want to add all the properties of obj2 to obj1, we could go like:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };

Object.assign(obj1, obj2);

This will mutate the target object obj1 by adding all the properties from obj2 to it.

Let's inspect obj1 after this mutation:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };

Object.assign(obj1, obj2);

console.log(obj1);
{a: 0, d: 1, b: 10, c: 20}

As you can see, the object obj1 now has all the properties of obj2 as well, and in the same order that they were defined in obj2.

The object obj2 here is unchanged after the call to Object.assign(). Only the target object, which is obj1, is mutated by the method.

Great!

Let's consider another example:

Suppose we have three objects as shown below:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };
var obj3 = { c: 300, e: 400 };

and want to merge all of them into a new object — not any of the existing objects obj1, obj2 or obj3.

How could this be done using Object.assign()?

Well, the first argument to Object.assign() would be the new object on which we wish to merge the properties of all source objects, and the subsequent arguments would simply be obj1, obj2 and obj3.

This is accomplished below:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };
var obj3 = { c: 300, e: 400 };

var o = {};
Object.assign(o, obj1, obj2, obj3);

As before, let's inspect the new object created here to see what properties it has got:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };
var obj3 = { c: 300, e: 400 };

var o = {};
Object.assign(o, obj1, obj2, obj3);

console.log(o);
{a: 0, d: 1, b: 10, c: 300, e: 400}

As expected, the new object o has properties of all the three objects obj1, obj2 and obj3. But notice the value of the property c — it's 300 and not 20.

Why?

As stated previously, the properties of later sources passed to Object.assign() override the same properties of earlier sources. In the case above, since obj3 is given as the last source (after obj2), the value of its property c overrides the values of the property c of obj2 at the end of the method's execution.

Simple.

Guess what, let's consider yet another example and this time an even more complex one:

In the code below, we define the same four objects as before, but this time the object o has an accessor property c with a getter and setter:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };
var obj3 = { c: 300, e: 400 };

var o = {};

// define accessor property 'c'
Object.defineProperty(o, 'c', {
    set: function(value) {
        if (value < 100) this._c = value;
    },
    get: function() {
        return this._c;
    }
});

Now, let's call Object.assign() just as before and then inspect o:

var obj1 = { a: 0, d: 1 };
var obj2 = { b: 10, c: 20 };
var obj3 = { c: 300, e: 400 };

var o = {};

// define accessor property 'c'
Object.defineProperty(o, 'c', {
    set: function(value) {
        if (value < 100) this._c = value;
    },
    get: function() {
        return this._c;
    }
});

Object.assign(o, obj1, obj2, obj3);

console.log(o);
{a: 0, d: 1, b: 10, _c: 20, e: 400, …}

In merging obj1, obj2 and obj3 into a new object, whenever the property c is encountered on a source object, the setter of obj1's accessor property c is invoked.

This setter only accepts values below 100 and creates a _c property with the given value when it is within the range. This underscore (_) naming convention is used to denote private properties in JavaScript that are only meant to be used by the underlying object itself, and not by any outside code.

There is nothing literally preventing us from accessing the property _c of o here — we could easily access it by o._c. The underscore (_) naming convention is just an indicative feature that tells other people that this is meant to be a private property of the object.

Later on, when this property c is retrieved on o, its getter is invoked.

Simple?

Why is this method called 'assign'?

You might be wondering as to why does Object.assign() use the word 'assign' instead of something like 'merge', which is a bit more self-descriptive.

Well, it turns out that the method is called 'assign' because it literally does something related to assignments.

Object.assign() iterates over all the given source objects, and for each of them, goes over its enumerable properties. The value of each of these enumerable properties is merely assigned to the similar property on the target.

This is just how we'd normally assign the value of one object's property to another object's property i.e. target.prop = source.prop.

The Object.assign() method does NOT necessarily define a new property on the target object based on the same property on any one of the source objects.

Had this been the case, any property on the target would have ultimately been replaced with the same property on a source object. In other words, a configurable accessor property on the target could've become a data property, or vice versa.

Thus, to boil it down, Object.assign() assigns values of properties obtained from the source objects to the same properties on the target object.

The Object.create() method

We saw the Object.create() method in the JavaScript Objects — Prototypes chapter. It's meant to create a new object with a given prototype.

The method accepts two arguments as shown below:

Object.create(proto[, propDescriptorsMap])

proto is the object that has to be made the prototype of the newly-created object.

The second optional propDescriptorsMap argument provides with the initial properties of this newly-created object as a mapping from keys to descriptor objects.

Let's consider two examples of Object.create().

Consider the code below:

var proto = { x: 0, y: 0 };

var o = Object.create(proto);
o.x = 10;

console.log(o.x, o.y);
10 0

Now, let's consider another example.

This time, we'll work with the second propDescriptorsMap argument as well, initializing the object with a couple of properties.

Consider the code below:

var proto = { x: 0, y: 0 };

var o = Object.create(proto, {
    // key -> descriptor
    x: { value: 10 },
    a: { value: 20 },
    b: { value: 30 }
});

console.log('o.x:', o.x);
console.log('o.y:', o.y);
console.log('o.a:', o.a);
console.log('o.b:', o.b);
o.x: 10 o.y: 0 o.a: 20 o.b: 30

Object.defineProperty() and Object.defineProperties()

In the chapter JavaScript Objects — Property Attributes, we came across the highly useful Object.defineProperty() method in JavaScript.

It allows us to define an object's own property with given attributes such as those to make the property non-writable or non-enumerable and so on.

Let's recap it quickly here:

Object.defineProperty(obj, prop, descriptor)

The first argument is the object on which we ought to define a property; the second argument is the name of that property; and the last argument specifies its attributes by means of properties of an object as shown below:

  1. value: specifies the value of the property. This only applies to data properties.
  2. writable: true means that the property is writable, and false means that it is non-writable. This only applies to data properties.
  3. get: specifies the getter function that is called when the property is retrieved. This only applies to accessor properties.
  4. set: specifies the setter function that is called when the property is assigned a value. This only applies to accessor properties.
  5. configurable: true means that the property is configurable i.e. can be deleted, changed in its kind from data to accessor or from accessor to data, or its property attributes changed. false means that the property is non-configurable.
  6. enumerable: true means that the property is enumerable i.e. shows up in for...in loops and other enumerable property retrieval utilities.

Shown below are two examples:

In the following code, we define a non-writable and non-configurable property x on an object o and then try to write a value to it.

var o = {};

Object.defineProperty(o, 'x', {
    value: 'old',
    writable: false
    configurable: false
});

console.log(o.x);

o.x = 'new';

console.log(o.x);
old old

Since the property is non-writable (and non-configurable as well), the write expression o.x = 'new' (in line 11) goes silently ignored.

In strict mode, writing to a non-writable property throws an error.

Over to the second example.

In the following code, we have an object o and an accessor property lastItem on it with a getter and setter defined. This property returns the last element of the array held on o's array property when get, while pushes a new element onto the same array when set.

var list = {
    array: [1, 2, 5, 6]
};

Object.defineProperty(list, 'lastItem', {
    get: function() {
        // return the last item of array
        return this.array[this.array.length - 1];
    },
    set: function(value) {
        this.array.push(value);
    }
});

console.log(list.lastItem);

list.lastItem = 50;

console.log(list.lastItem);
6 50

Moving on, when we want to define multiple properties on an object via Object.defineProperty(), we need to obviously call the method multiple times, once for each property.

A much more convenient way to define multiple properties with given descriptors at once on an object is to use the Object.defineProperties() method.

Theoretically, it's just the plural version of Object.defineProperty() allowing you to define multiple properties on an object in just one call.

Here's its syntax:

Object.defineProperties(obj, propDescriptorsMap)

As before, the first argument specifies the object on which to define the properties.

However, the second argument is not the name of the property, but rather a property descriptor map whose keys denote keys of obj and map to descriptor objects.

Let's see a quick example.

In the code below, we create an object o and then define two non-writable properties x and y on it of which the first one is non-enumerable and the second one is enumerable.

var o = {};

Object.defineProperties(o, {
    x: {
        value: 10,
        writable: false,
        enumerable: false
    },
    y: {
        value: 20,
        writable: false,
        enumerable: true
    }
});

console.log(o.x);
console.log(o.y);

console.log(o.propertyIsEnumerable('x'));
console.log(o.propertyIsEnumerable('y'));
10 20 false true

The Object.getOwnPropertyNames() method

As we saw above, the method hasOwnProperty() of an Object instance tells us whether a given object holds the ownership of a given property or not.

Now in order to get back a list of all the own properties of a given object, we couldn't just use hasOwnProperty() alone. We have to use alongside it some other thing such as a for...in loop.

However, with the Object.getOwnPropertyNames() method, doing this is only one call away.

Object.getOwnPropertyNames(obj)

The method takes in an object as argument and returns back an array containing all the own properties of the object.

Let's test it:

var o = { b: 1, d: 2, a: 3 }
undefined
var proto = { c: 10 }
undefined
Object.setPrototypeOf(o, proto)
{ a: 0, b: 1, d: 2 }
Object.getOwnPropertyNames(o)
['b', 'd', 'a']
Object.getOwnPropertyNames(proto)
['c']

First we define an object o and then an object proto. Then, we set proto as the prototype of o to see whether inherited properties show up later on in Object.getOwnPropertyNames() when used to inspect o.

Next up, we retrieve all the own properties of o, which yields the array ['b', 'd', 'a']. Note that the order of properties in this array is the same as the order in which the properties are defined on o. Moreover, the inherited property c of o doesn't show up in this array.

Finally, we retrieve all the own properties of proto, which simply yields the single-element array ['c'].

Easy, wasn't this?

Object.getPrototypeOf() and Object.setPrototypeOf()

Once again, recalling the last two sections of the chapter JavaScript Objects — Prototypes, the methods Object.getPrototypeOf() and Object.setPrototypeOf() allow us to retrieve and set the prototype of a given object, respectively.

The syntax of the Object.getPrototypeOf() method is as follows:

Object.getPrototypeOf(obj)

Just pass in the object whose prototype ought to be determined. The method returns back the prototype object.

Similarly, the syntax of Object.setPrototypeOf() is as follows:

Object.setPrototypeOf(obj, proto)

proto is made the prototype of obj. The method returns back obj.

As stated in the previous chapter, using Object.setPrototypeOf() (or even the __proto__ property) to change the prototype of an existing object is discouraged, at least in production code.

This is due to nature of the operation in all modern JavaScript engines, whereby the prototype reassignment causes numerous optimizations made by the engine to become useless, or in the worst case, the engine falling to slow, naive, bytecode execution.

"I created Codeguage to save you from falling into the same learning conundrums that I fell into."

— Bilal Adnan, Founder of Codeguage