Explain Codes LogoExplain Codes Logo

Self-references in object literals / initializers

javascript
self-references
object-literals
getters
Nikita BarsukovbyNikita Barsukov·Dec 26, 2024
TLDR

Getters for computed properties, or factory functions for methods with self-references in object literals—these should be your go-to tools.

Getters for immediate usage:

const obj = { a: 1, get doubleA() { return this.a * 2; } // Hello, I'm obj and I just doubeled myself! 👋 }; console.log(obj.doubleA); // Outputs: 2

Factory functions to encapsulate self-references:

const createObj = () => { let self = { a: 1 }; self.multiplyA = (x) => self.a * x; // Mirror mirror on the wall, who's the multiplier of x? return self; }; let obj = createObj(); console.log(obj.multiplyA(2)); // Outputs: 2

With this and self, an object gains self-awareness, accessing its own properties internally.

Diving deeper: enhancing self-references

Dynamic Property Caching - The Magic of Getters

ES6 getters, those crafty little devils, create dynamic object literals while caching computed properties like a squirrel with acorns.

const dynamicObj = { a: 1, b: 2, get sum() { // Note to self: Need. More. Sun. const value = this.a + this.b; Object.defineProperty(this, 'sum', { value }); return value; } }; console.log(dynamicObj.sum); // Calls the getter, caches the sum. First time's the charm! console.log(dynamicObj.sum); // Returns cached value. I got your sum right here!

After initial calling, the sum property has the stored result, preventing additional getter computations.

Self-Reference via Function Scopes

A function’s local scope is not just a privilege—it's an efficient way to create self-referential objects.

function initializeObj() { let obj = { a: 1 }; obj.b = function() { return this.a + 1; }; // B says: I know the value of A, and I can add to it! return obj; } const obj = initializeObj(); console.log(obj.b()); // Outputs: 2, b is now a math genius

Inline Object Initialization

Invoke new function() { ... } pattern for inline object initialization with self-references. This approach allows you to effectively use a constructor function without declaring it separately.

const obj = new function() { this.a = 1; this.double = () => this.a * 2; // When A says double, it means business }; console.log(obj.double()); // Outputs: 2

Advanced techniques for self-reference

Utilising Temporary Properties and Functions

Creating objects with temporary properties or functions enhances initialization. They can be removed after doing their job.

const complexObj = { tempFactor: 2, a: 1, b: 2, init() { this.c = this.a * this.tempFactor; // I'm C, and I'm twice as cool as A! delete this.tempFactor; // TempFactor has left the building delete this.init; // Init? Who's init? return this; } }.init(); console.log(complexObj); // Outputs: { a: 1, b: 2, c: 2 }

Performance Considerations: The YIN and YANG

Always remember the performance trade-offs with these complex self-referential patterns. They might be powerful, but could be the opposite of "light and fast" when overused in performance critical applications.

I mutate, therefore I exist

Once the object is created, often you might need to change some references :

let mutableObj = { a: 1 }; mutableObj.b = mutableObj.a * 2; // B whispers: "I'm secretly A, just twice his size" console.log(mutableObj.b); // Outputs: 2

This allows for external assignment post-initialization, a great way to establish self-references when complex computation isn't needed.