JavaScript Objects

Planted

Status: seed

An object is a built-in type in JavaScript. It is made up of properties (string type) and values (any type).

5 women constructing a seaplane wing out of timber.

Creating

An object can be created 4 different ways:

/index.js

Console

// prototype set to Object.prototype
const obj = {
    a: 1
}

/index.js

Console

function ObjConstructor(a) {
    this.a = a
}

// prototype set to the function ObjConstructor
const obj = new ObjConstructor(1)

/index.js

Console

// prototype set to the 1st argument
const obj = Object.create(Object.prototype, {
    a: {
        value: 1,
        enumerable: true,
        writable: true,
        configurable: true
    }
})

/index.js

Console

// A class is a template for creating an object
class Obj {
    constructor(a) {
        this.a = a
    }
}

// prototype set to the class Obj
const obj = new Obj(1)

When a function is invoked with new in front of it (a constructor call):

  1. 1. a new object is created
    • a user-defined object type or
    • 1 of the built-in object types that has a constructor function
  2. 2. it is [[Prototype]] linked
  3. 3. it is set as the this binding for that function call
  4. 4. unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.
Tree of Gnosis

Prototype Chain

A screenshot of the browser console showing the properties of an object.

When an object is created, a property, [[Prototype]], is set on the object. It allows an object to access properties of other objects. You can see this by creating an object in the browser's console:

When you attempt to access an object property's value on an object, for example obj.a, the engine invokes an internal [[Get]] operation. The engine will:

  1. 1. Look for the property on the object.
  2. 2. If it can't find it, it will then look for that property in the object that is referenced in the [[Prototype]] property.
  3. 3. If the linked object doesn't have the property, the engine will check that object's linked object. This continues until the property is found or there are no more links. If no match is found, undefined is returned. This series of links between objects forms the prototype chain.

Creating a Link

You can link 1 object to another using Object.create(..)

/index.js

Console

// Prototype is set to Object.prototype
const obj1 = {
	a: 1
};

const obj2 = Object.create(obj1);
obj2.b = 2

console.log(obj2.a)
A screenshot of the browser console showing the properties of an object
A mechanic working on a seaplane wing in a hanger.

Setting a Property

When you attempt to set a value, myObject.myProperty = 1, the engine invokes an internal [[Put]] operation. If the property is present, the operation will check:

  1. 1. Is the property an accessor descriptor (see "Getters & Setters" section below), call the setter.
  2. 2. Is the property a data descriptor with writable of false, silently fail in non-strict mode, or throw TypeError in strict mode.
  3. 3. Otherwise, set the value to the property.

A property on an object can be set in 3 different ways:

/index.js

Console

const obj = {
    a: 1
}

/index.js

Console

const obj = {}

Object.defineProperty(obj, 'a', {
    value: 1,           
    enumerable: true,   // will it be visible when iterating
    writable: true,     // can the property be edited
    configurable: true  // can the property be deleted
})

The 3rd is through a setter (see below).

Getters & Setters

Getters & setters are properties that call hidden functions to retrieve & set values. When you define a property to have either a getter or a setter, its definition becomes an accessor descriptor (as opposed to a data descriptor).

For accessor-descriptors, the value & writable characteristics of the descriptor are ignored. Instead, the engine considers the set & get characteristics of the property (as well as configurable & enumerable).

/index.js

Console

const obj = {
	get a() {
		return this._a_;
	},
	set a(val) {
		this._a_ = val * 2;
	}
};

obj.a = 2;
console.log(obj.a)

Above, the value is stored into a variable _a_. The underscores in the name is just a convention. It's is a normal object property. A getter can be also be defined using a descriptor:

/index.js

Console

const obj = {};

Object.defineProperty(obj, "a",
    {
        get: function() { 
            return 1
        },
        enumerable: true
    }
);
2 people looking at the blueprints of a seaplane on a drafting board.

Inspecting

To test if an object has a property, use:

  • Object.hasOwn(..) to exclude the [[Prototype]] chain,
  • in to include it.

/index.js

Console

const obj1 = {
	a: 1
};

const obj2 = Object.create(obj1);
obj2.b = 2

console.log(Object.hasOwn(obj2, "a"))
console.log(Object.hasOwn(obj2, "b"))

console.log('---------')

console.log("a" in obj2)
console.log("b" in obj2)
A circle with a dot rotating around it

Iterating

  • for..in iterates over the list of enumerable properties on an object (including its [[Prototype]] chain)
  • for..of with Object.entries doesn't include the [[Prototype]] chain

/index.js

Console

const obj1 = {
	a: 1
};

const obj2 = Object.create(obj1);
obj2.b = 2

for (prop in obj2) {
    console.log(`${prop}: ${obj2[prop]}`)
}

console.log('---------')

for (let [key, value] of Object.entries(obj2)) {
  console.log(`${key}: ${value}`);
}

When iterating over an object, order of iteration isn't guaranteed. If insertion order is required, use a Map instead of an object.

An X-Men trading card of 'Multiple Man'.

Cloning

An object can be cloned in 4 different ways:

/index.js

Console

const obj = { a: 1 }

const copy1 = { ...obj }
const copy2 = Object.assign({}, obj)
const copy3 = JSON.parse(JSON.stringify(obj))
const copy4 = structuredClone(obj)

The 1st 2 create a shallow copy. The last 2 create a deep copy. The difference is only relevant if an object property has a value of another object:

  • shallow: the reference is copied.
  • deep: the object is duplicated & a reference to this new object will be used as the value.

{ ...obj } & structuredClone(..) are the preferred ways to do a shallow & deep clone.

Code showing the difference between a shallow copy & a deep copy
A large ball of ice floating in an artic river.

Immutability

Object.freeze(..) creates an immutable object. An object that can't be changed. It calls Object.seal(..) on the passed in object & marks all data accessor properties as writable: false. Their values can no longer be changed. This approach is the highest level of immutability that you can attain for an object.

/index.js

Console

const obj = { a: 1 }
Object.freeze(obj)

// This will fail as obj has been frozen
obj.a = 2
console.log(obj)

Where to Next?

A sci-fi robot taxi driver with no lower body
└── JavaScript
Arrow pointing down
YOU ARE HERE
└── Objects
├── Classes
└── Referential Equality