MediumMetaUberAmazonPegasystems

call( ) polyfill

Prompt

Implement a polyfill for the JavaScript .call() method. Add a myCall method to Function.prototype that works exactly like the native .call(), without using .call(), .apply(), or .bind().

Playground

Hint 1

You can't use .call(), .apply(), or .bind(). But there's another way to control what this is inside a function: call it as a method on an object. If you write obj.fn(), then this inside fn is obj.

Hint 2

Temporarily attach the function as a property on the context object, call it, then clean up by deleting the property. Use a Symbol for the property name to avoid colliding with existing properties.

Solution

Explanation

To write this polyfill, you first need to understand what .call() actually does. It lets you borrow a function and run it with a different this.

For example, say you have a greet function that uses this.name. Normally, this depends on how you call the function. But with .call(), you can say "run this function, but pretend this is this specific object."

greet.call(person, 'Hello');
// "this" inside greet = person

So .call() does two things: it sets the this context, and it executes the function immediately (unlike .bind() which returns a new function for later).

The core trick

The tricky part is: how do you control what this is without using .call(), .apply(), or .bind()?

There's actually a very simple JavaScript rule that solves this. When you call a function as a method on an object, this automatically points to that object:

const person = { name: 'John' };

person.sayHi = function () {
console.log(this.name); // "John"
};

person.sayHi(); // this = person, because we called it as person.sayHi()

That's the whole trick. If we temporarily attach our function to the context object and call it as a method, JavaScript gives us the right this automatically. After we're done, we clean up by deleting the temporary property.

Why Symbol?

We need to temporarily add the function as a property on the context object. But we need a property name that definitely doesn't already exist on that object. If we used a string like '__temp__' and the context object happened to already have a __temp__ property, we'd overwrite their data.

Symbol() creates a completely new, one-of-a-kind value every time you call it. No two Symbols are ever equal. Think of it like generating a random ID that's guaranteed to never collide with anything.

const a = Symbol();
const b = Symbol();
console.log(a === b); // false, always

So context[Symbol()] = this adds our function under a key that cannot possibly conflict with any existing property. After we're done, we delete it to leave the object exactly as we found it.

Walking through the example

greet.myCall(person, 'Hello');
  1. this inside myCall is greet (because we called greet.myCall(...))
  2. context = person, args = ['Hello']
  3. We create a Symbol and attach greet to person: person[Symbol()] = greet
  4. We call person[Symbol()]('Hello'). Since we're calling it as a method on person, this inside greet is now person
  5. greet runs: `Hello, ${this.name}!` becomes "Hello, John!"
  6. We delete the temporary property and return "Hello, John!"

.call() executes the function immediately and returns its result. .bind() does something different: it saves the context and returns a new function for later. If you're building all three polyfills, start with .call() since it's the foundation. .apply() is nearly identical to .call(), and .bind() uses .apply() internally.