Brad Woods Digital Garden

Notes / JavaScript / Objects

The Warhammer 40k Adeptus Mechanicus symbol

Table of contents

    JavaScript Objects

    Planted: 

    Status: decay

    Hits: 278

    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

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

    index.js

    function ObjConstructor(a) {
    this.a = a
    }
    // prototype set to the function ObjConstructor
    const obj = new ObjConstructor(1)

    index.js

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

    index.js

    // 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

    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:

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

    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

    // 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 and 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

    const obj = {
    a: 1
    }

    index.js

    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 and Setters

    Getters and setters are properties that call hidden functions to retrieve and 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 and writable characteristics of the descriptor are ignored. Instead, the engine considers the set and get characteristics of the property (as well as configurable and enumerable).

    index.js

    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

    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

    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

    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

    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 and a reference to this new object will be used as the value.

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

    Code showing the difference between a shallow copy and 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 and 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

    const obj = { a: 1 }
    Object.freeze(obj)
    // This will fail as obj has been frozen
    obj.a = 2
    console.log(obj)

    Feedback

    Have any feedback about this note or just want to comment on the state of the economy?

    Where to next?