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.
Function.prototype.callNative 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.
callWe need to:
thisArg (context).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; };
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.
β Handles any function
β Supports multiple arguments
β
Avoids polluting original object using Symbol
β
Works in any execution environment (globalThis)
myCall PolyfillLetβs take an example and go step by step to understand how our polyfill works.
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);
myCallgreet.myCall(person, 30);
this inside myCall refers to greet (the function being invoked).context is person ({ name: "Bob" }).args = [30].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; };
thisif (typeof this !== "function") { throw new TypeError("myCall can only be used on functions"); }
this is greet, which is a function β
.contextcontext = context || globalThis;
context = person, since it was provided ({ name: "Bob" }).const fnKey = Symbol(); context[fnKey] = this;
Symbol() creates a unique property key (e.g., Symbol(fnKey)) to avoid overwriting existing properties .
person now has a new temporary property:
person[Symbol(fnKey)] = greet;
So person looks like this:
{ name: "Bob", [Symbol(fnKey)]: function greet(age) { ... } }
const result = context[fnKey](...args);
person ;
this inside greet is now person, it prints:
Hello, my name is Bob and I am 30 years old.
delete context[fnKey];
person to keep the object clean.person is now back to:
{ name: "Bob" }
| 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