The call method in JavaScript allows us to invoke a function with a specified this context and arguments. Let's implement a polyfill for it.
1οΈβ£ Understanding Function.prototype.call
Native behavior:
function greet() { console.log(`Hello, my name is ${this.name}`); } const person = { name: "Alice" }; greet.call(person); // Output: Hello, my name is Alice
Here, call(person) makes this inside greet refer to person.
2οΈβ£ Polyfill for call
We need to:
- Attach the function to the
thisArg(context). - Invoke the function with arguments.
- Remove the temporary function reference.
Function.prototype.myCall = function (context, ...args) { if (typeof this !== "function") { throw new TypeError("myCall can only be used on functions"); } context = context || globalThis; // Default to global object (window in browsers, global in Node) const fnKey = Symbol(); // Unique key to avoid property collisions context[fnKey] = this; // Assign function to context const result = context[fnKey](...args); // Invoke function delete context[fnKey]; // Cleanup temporary function return result; };
3οΈβ£ Testing the Polyfill
function greet(age) { console.log(`Hello, my name is ${this.name} and I am ${age} years old.`); } const person = { name: "Bob" }; greet.myCall(person, 30); // Output: Hello, my name is Bob and I am 30 years old.
4οΈβ£ Key Features of Our Polyfill
β Handles any function
β Supports multiple arguments
β
Avoids polluting original object using Symbol
β
Works in any execution environment (globalThis)
Dry Run of myCall Polyfill
Letβs take an example and go step by step to understand how our polyfill works.
π Example
function greet(age) { console.log(`Hello, my name is ${this.name} and I am ${age} years old.`); } const person = { name: "Bob" }; greet.myCall(person, 30);
π Step-by-Step Execution
Step 1: Call myCall
greet.myCall(person, 30);
thisinsidemyCallrefers togreet(the function being invoked).contextisperson({ name: "Bob" }).args = [30].
Step 2: Function Definition Breakdown
Inside myCall:
Function.prototype.myCall = function (context, ...args) { if (typeof this !== "function") { throw new TypeError("myCall can only be used on functions"); } context = context || globalThis; // Default to global object (window in browsers, global in Node) const fnKey = Symbol(); // Unique key to avoid property collisions context[fnKey] = this; // Assign function to context const result = context[fnKey](...args); // Invoke function delete context[fnKey]; // Cleanup temporary function return result; };
π Step 3: Execution Flow
1οΈβ£ Validate this
if (typeof this !== "function") { throw new TypeError("myCall can only be used on functions"); }
thisisgreet, which is a function β .- No error is thrown.
2οΈβ£ Set context
context = context || globalThis;
context = person, since it was provided ({ name: "Bob" }).
3οΈβ£ Attach Function Temporarily
const fnKey = Symbol(); context[fnKey] = this;
-
Symbol()creates a unique property key (e.g.,Symbol(fnKey)) to avoid overwriting existing properties . -
personnow has a new temporary property:person[Symbol(fnKey)] = greet;So
personlooks like this:{ name: "Bob", [Symbol(fnKey)]: function greet(age) { ... } }
4οΈβ£ Invoke Function
const result = context[fnKey](...args);
- Equivalent to:
person ; - Since
thisinsidegreetis nowperson, it prints:Hello, my name is Bob and I am 30 years old.
5οΈβ£ Cleanup
delete context[fnKey];
- Removes the temporary function from
personto keep the object clean. personis now back to:{ name: "Bob" }
π Final Execution Summary
| Step | Action |
|---|---|
| 1οΈβ£ | greet.myCall(person, 30)is called |
| 2οΈβ£ | context = person |
| 3οΈβ£ | Symbol(fnKey)is created and assigned to person[Symbol(fnKey)] = greet |
| 4οΈβ£ | person is invoked, printing "Hello, my name is Bob and I am 30 years old." |
| 5οΈβ£ | Temporary function property is deleted |
β
Works just like the native call method!
Test your understanding with 3 quick questions