2693. Call Function with Custom Context
Problem Description
This problem asks you to create a polyfill for JavaScript's built-in call()
method. A polyfill is a piece of code that provides functionality that isn't natively supported.
The task is to add a method called callPolyfill
to all JavaScript functions through the Function.prototype
. This method should:
- Accept an object
obj
as its first parameter, which will become thethis
context for the function - Accept any number of additional arguments that will be passed to the original function
- Execute the function with the specified
this
context and arguments
The key challenge is that you cannot use the built-in Function.call()
method to implement this.
How it works:
When a function is called normally in JavaScript, its this
context might be undefined
or refer to the global object. The callPolyfill
method allows you to explicitly set what this
refers to inside the function.
Example:
Given a function:
function tax(price, taxRate) {
const totalCost = price * (1 + taxRate);
console.log(`The cost of ${this.item} is ${totalCost}`);
}
- Calling
tax(10, 0.1)
normally would log"The cost of undefined is 11"
becausethis.item
is undefined - Calling
tax.callPolyfill({item: "salad"}, 10, 0.1)
would log"The cost of salad is 11"
becausethis
is set to{item: "salad"}
The solution uses the bind()
method to create a new function with the specified this
context, then immediately invokes it with the provided arguments. The bind()
method returns a new function with this
permanently bound to the specified context, which is then called with the spread operator ...args
to pass all arguments.
Intuition
The core challenge is to change the this
context of a function without using the built-in call()
method. We need to find another way to bind a specific object as the this
context when executing a function.
Let's think about what JavaScript methods we have available for manipulating this
:
call()
- explicitly forbidden in this problemapply()
- likely also off-limits since it's similar tocall()
bind()
- creates a new function with a fixedthis
context
The bind()
method stands out as a viable option. When you use bind()
, it doesn't immediately execute the function. Instead, it returns a new function where this
is permanently set to whatever you passed as the first argument.
Here's the key insight: If we can create a new function with the correct this
context using bind()
, we can then immediately invoke that new function with the provided arguments.
The solution becomes straightforward:
- Use
this.bind(context)
to create a new function wherethis
inside the function will refer tocontext
- Immediately invoke this new bound function with the arguments using the spread operator
(...args)
This two-step process effectively mimics what call()
does in a single step - it sets the this
context and executes the function with arguments.
The elegance of this approach is that bind()
handles all the complex internal mechanics of setting up the proper this
binding, and we just need to execute the resulting function. It's like creating a temporary function that "remembers" what its this
should be, then running it right away.
Solution Approach
The implementation extends the Function.prototype
to add the callPolyfill
method to all functions in JavaScript. Here's how the solution works step by step:
1. Extending Function.prototype:
Function.prototype.callPolyfill = function (context, ...args): any {
// implementation
};
By adding a method to Function.prototype
, every function in JavaScript automatically gets access to this method. The this
keyword inside this method refers to the function that callPolyfill
is being called on.
2. Using the rest parameter for arguments:
function (context, ...args)
The first parameter context
is the object that will become the this
context. The ...args
rest parameter collects all remaining arguments into an array, allowing the method to accept any number of additional arguments that need to be passed to the original function.
3. Creating a bound function:
const fn = this.bind(context);
Here, this
refers to the original function (the one calling callPolyfill
). The bind()
method creates a new function where the this
context is permanently set to the context
object passed in. This bound function fn
is like a copy of the original function but with a fixed this
value.
4. Executing with arguments:
return fn(...args);
The bound function is immediately invoked with the spread operator ...args
, which expands the array of arguments and passes them individually to the function. The result of the function execution is returned.
TypeScript Declaration:
declare global {
interface Function {
callPolyfill(context: Record<any, any>, ...args: any[]): any;
}
}
This TypeScript declaration tells the type system that all functions now have a callPolyfill
method, preventing type errors when using this custom method.
The beauty of this solution is its simplicity - it leverages the existing bind()
method to handle the complex task of setting the this
context, then immediately executes the resulting function. This achieves the same effect as call()
without directly using it.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the callPolyfill
solution works:
// Define a simple greeting function that uses 'this'
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
// Create an object to use as context
const person = { name: "Alice" };
// Using our callPolyfill implementation
const result = greet.callPolyfill(person, "Hello", "!");
Step-by-step execution:
-
Method invocation: When
greet.callPolyfill(person, "Hello", "!")
is called:this
insidecallPolyfill
refers to thegreet
functioncontext
=person
object ({ name: "Alice" }
)args
=["Hello", "!"]
(collected by rest parameter)
-
Creating bound function:
const fn = this.bind(context); // fn is now a new function where 'this' is locked to the person object
The
bind()
call creates a new function that's identical togreet
, but whenever it runs, itsthis
will always refer to theperson
object. -
Executing with arguments:
return fn(...args); // Equivalent to: fn("Hello", "!")
The spread operator expands the array
["Hello", "!"]
into individual arguments. The bound function executes with:this.name
→person.name
→"Alice"
greeting
→"Hello"
punctuation
→"!"
-
Final result: The function returns
"Hello, Alice!"
, successfully using theperson
object as thethis
context.
Without callPolyfill
, calling greet("Hello", "!")
directly would fail because this.name
would be undefined
(in strict mode) or refer to the global object. The polyfill allows us to explicitly control what this
refers to, just like the native call()
method does.
Solution Implementation
1from typing import Any, Callable, Dict, List
2
3class FunctionPolyfill:
4 """
5 A class to demonstrate polyfill implementation for Function.prototype.call
6 Since Python doesn't have prototype-based inheritance like JavaScript,
7 we'll create a wrapper class to simulate similar behavior
8 """
9
10 @staticmethod
11 def call_polyfill(func: Callable, context: Dict[str, Any], *args: Any) -> Any:
12 """
13 Polyfill implementation for Function.prototype.call
14 This method allows a function to be called with a given 'this' value and arguments
15
16 Args:
17 func: The function to be executed
18 context: The object that will be bound as 'this' inside the function
19 *args: Variable arguments that will be passed to the function
20
21 Returns:
22 The return value of the function after execution
23 """
24 # In Python, we simulate binding by creating a wrapper function
25 # that has access to the context as 'self'
26 class ContextWrapper:
27 def __init__(self, ctx: Dict[str, Any]):
28 # Copy context attributes to this wrapper instance
29 for key, value in ctx.items():
30 setattr(self, key, value)
31
32 def execute(self, *arguments: Any) -> Any:
33 # Execute the function with this wrapper as 'self'
34 return func(self, *arguments)
35
36 # Create an instance with the provided context
37 wrapper = ContextWrapper(context)
38
39 # Execute the bound function with the provided arguments
40 return wrapper.execute(*args)
41
42
43# Alternative implementation using a decorator approach
44def add_call_polyfill(func: Callable) -> Callable:
45 """
46 Decorator to add call_polyfill method to a function
47
48 Args:
49 func: The function to be decorated
50
51 Returns:
52 The decorated function with call_polyfill method
53 """
54 def call_polyfill(context: Dict[str, Any], *args: Any) -> Any:
55 """
56 Custom implementation of the call method
57
58 Args:
59 context: The object to be used as 'this' when executing the function
60 *args: Arguments to pass to the function
61
62 Returns:
63 The result of the function execution
64 """
65 # Create a namespace object with context attributes
66 namespace = type('Namespace', (), context)()
67
68 # Bind the function to the namespace and execute
69 bound_method = func.__get__(namespace, type(namespace))
70 return bound_method(*args)
71
72 # Attach the call_polyfill method to the function
73 func.call_polyfill = call_polyfill
74 return func
75
76
77# Example usage demonstration
78@add_call_polyfill
79def increment(self) -> int:
80 """
81 Example function that increments a count attribute
82
83 Returns:
84 The incremented count value
85 """
86 self.count += 1
87 return self.count
88
89
90# Example usage:
91# result = increment.call_polyfill({'count': 1}) # Returns 2
92
1/**
2 * Interface representing a JavaScript-like function that can be called with custom context
3 */
4interface CallableFunction {
5 /**
6 * Custom implementation of the call method
7 * @param context - The object to be used as 'this' when executing the function
8 * @param args - Arguments to pass to the function
9 * @return The result of the function execution
10 */
11 Object callPolyfill(Object context, Object... args);
12}
13
14/**
15 * Abstract base class that implements the callPolyfill functionality
16 * This simulates JavaScript's Function.prototype behavior in Java
17 */
18abstract class Function implements CallableFunction {
19
20 /**
21 * Abstract method that derived classes must implement to define the function logic
22 * @param context - The context object that acts as 'this'
23 * @param args - Arguments passed to the function
24 * @return The result of the function execution
25 */
26 protected abstract Object execute(Object context, Object... args);
27
28 /**
29 * Polyfill implementation for Function.prototype.call
30 * This method allows a function to be called with a given 'this' value and arguments
31 *
32 * @param context - The object that will be bound as 'this' inside the function
33 * @param args - Varargs parameters that will be passed as arguments to the function
34 * @return The return value of the function after execution
35 */
36 @Override
37 public Object callPolyfill(Object context, Object... args) {
38 // Execute the function with the provided context and arguments
39 // In Java, we directly call execute since we can't dynamically bind like JavaScript
40 return execute(context, args);
41 }
42}
43
44/**
45 * Example implementation class demonstrating usage
46 */
47class IncrementFunction extends Function {
48 @Override
49 protected Object execute(Object context, Object... args) {
50 // Cast context to a Map to access properties
51 if (context instanceof java.util.Map) {
52 @SuppressWarnings("unchecked")
53 java.util.Map<String, Object> contextMap = (java.util.Map<String, Object>) context;
54
55 // Get current count value
56 Integer count = (Integer) contextMap.get("count");
57 if (count != null) {
58 // Increment the count
59 count++;
60 // Update the context with new count value
61 contextMap.put("count", count);
62 // Return the new count
63 return count;
64 }
65 }
66 return null;
67 }
68}
69
70/**
71 * Example usage:
72 * Map<String, Object> context = new HashMap<>();
73 * context.put("count", 1);
74 * Function increment = new IncrementFunction();
75 * increment.callPolyfill(context); // Returns 2
76 */
77
1#include <iostream>
2#include <functional>
3#include <any>
4#include <vector>
5#include <unordered_map>
6
7/**
8 * Context class to simulate JavaScript object behavior
9 * Acts as a container for key-value pairs that can be used as 'this' context
10 */
11class Context {
12public:
13 std::unordered_map<std::string, std::any> properties;
14
15 /**
16 * Get property value by key
17 * @param key - The property name
18 * @return Reference to the property value
19 */
20 std::any& operator[](const std::string& key) {
21 return properties[key];
22 }
23};
24
25/**
26 * Base class for functions that support callPolyfill method
27 * Template parameter R is return type, Args are function argument types
28 */
29template<typename R, typename... Args>
30class CallableFunction {
31private:
32 std::function<R(Context*, Args...)> m_function;
33
34public:
35 /**
36 * Constructor that takes a function with Context pointer as first parameter
37 * @param func - The function to wrap
38 */
39 CallableFunction(std::function<R(Context*, Args...)> func) : m_function(func) {}
40
41 /**
42 * Polyfill implementation for Function.prototype.call
43 * This method allows a function to be called with a given 'this' value and arguments
44 *
45 * @param context - The object that will be bound as 'this' inside the function
46 * @param args - Arguments that will be passed to the function
47 * @return The return value of the function after execution
48 */
49 R callPolyfill(Context* context, Args... args) {
50 // Execute the function with the provided context and arguments
51 // The context acts as 'this' binding in JavaScript
52 return m_function(context, args...);
53 }
54
55 /**
56 * Regular function call operator for direct invocation
57 * @param context - The context object
58 * @param args - Function arguments
59 * @return The return value of the function
60 */
61 R operator()(Context* context, Args... args) {
62 return m_function(context, args...);
63 }
64};
65
66/**
67 * Example usage demonstration:
68 *
69 * // Define a function that increments count property
70 * auto increment = CallableFunction<int>([](Context* context) {
71 * int& count = std::any_cast<int&>((*context)["count"]);
72 * count++;
73 * return count;
74 * });
75 *
76 * // Create context object with count property
77 * Context ctx;
78 * ctx["count"] = 1;
79 *
80 * // Call function with context binding
81 * int result = increment.callPolyfill(&ctx); // Returns 2
82 */
83
84// Example implementation
85int main() {
86 // Create an increment function that uses 'this' context
87 auto increment = CallableFunction<int>([](Context* context) {
88 // Access and modify the count property from context
89 int& count = std::any_cast<int&>((*context)["count"]);
90 count++;
91 return count;
92 });
93
94 // Create context object with initial count value
95 Context context;
96 context["count"] = 1;
97
98 // Call the function using callPolyfill with the context
99 int result = increment.callPolyfill(&context);
100 std::cout << "Result: " << result << std::endl; // Output: 2
101
102 return 0;
103}
104
1// Extend the global Function interface to include the callPolyfill method
2declare global {
3 interface Function {
4 /**
5 * Custom implementation of the call method
6 * @param context - The object to be used as 'this' when executing the function
7 * @param args - Arguments to pass to the function
8 * @returns The result of the function execution
9 */
10 callPolyfill(context: Record<any, any>, ...args: any[]): any;
11 }
12}
13
14/**
15 * Polyfill implementation for Function.prototype.call
16 * This method allows a function to be called with a given 'this' value and arguments
17 *
18 * @param context - The object that will be bound as 'this' inside the function
19 * @param args - Rest parameters that will be passed as arguments to the function
20 * @returns The return value of the function after execution
21 */
22Function.prototype.callPolyfill = function (context: Record<any, any>, ...args: any[]): any {
23 // Bind the function to the provided context
24 const boundFunction = this.bind(context);
25
26 // Execute the bound function with the provided arguments
27 return boundFunction(...args);
28};
29
30/**
31 * Example usage:
32 * function increment() { this.count++; return this.count; }
33 * increment.callPolyfill({count: 1}); // 2
34 */
35
Time and Space Complexity
Time Complexity: O(1)
The callPolyfill
function performs a constant number of operations:
this.bind(context)
creates a new bound function -O(1)
fn(...args)
invokes the bound function with spread arguments -O(1)
for the invocation itself (excluding the execution time of the actual function being called)
The time complexity is constant because regardless of the number of arguments passed, the binding and invocation operations themselves take constant time. The actual execution time of the function being called is not included in this analysis.
Space Complexity: O(n)
where n
is the number of arguments
The space complexity consists of:
- The bound function created by
bind()
-O(1)
for the function wrapper - The
args
array from rest parameters -O(n)
wheren
is the number of arguments - The spread operation
...args
when callingfn
-O(n)
for unpacking the arguments
The dominant factor is the storage of arguments, making the overall space complexity linear with respect to the number of arguments passed to the function.
Common Pitfalls
1. Loss of Original Function Properties and Methods
When using bind()
to create a new function, the bound function doesn't inherit custom properties that may have been added to the original function. This can break code that relies on function metadata or attached properties.
Problem Example:
function myFunc() {
return this.value;
}
myFunc.customProperty = "important data";
myFunc.helperMethod = function() { return "helper"; };
// After binding, these properties are lost
const boundFunc = myFunc.bind({value: 42});
console.log(boundFunc.customProperty); // undefined
console.log(boundFunc.helperMethod); // undefined
Solution: Copy properties from the original function to maintain compatibility:
Function.prototype.callPolyfill = function(context, ...args) {
const fn = this.bind(context);
// Copy properties from original function
Object.setPrototypeOf(fn, this);
return fn(...args);
};
2. Incorrect Handling of null
or undefined
Context
In JavaScript's native call()
, passing null
or undefined
as context in non-strict mode defaults to the global object, while in strict mode it remains null
or undefined
. The simple bind()
approach doesn't handle this nuance correctly.
Problem Example:
function getValue() {
return this;
}
// Native call behavior
console.log(getValue.call(null)); // null in strict mode, global object in non-strict
console.log(getValue.call(undefined)); // undefined in strict mode, global object in non-strict
// Polyfill might not match this behavior
Solution:
Check for null
or undefined
and handle appropriately:
Function.prototype.callPolyfill = function(context, ...args) {
// Handle null/undefined context based on strict mode
if (context === null || context === undefined) {
context = (function() { return this; })() || context;
}
const fn = this.bind(context);
return fn(...args);
};
3. Performance Overhead from Creating Intermediate Functions
Creating a new bound function via bind()
just to immediately invoke it adds unnecessary overhead. For frequently called functions, this can impact performance.
Problem Example:
// This creates a new function object every time for (let i = 0; i < 1000000; i++) { someFunc.callPolyfill(obj, arg1, arg2); // Creates intermediate bound function }
Solution: Use a more direct approach by temporarily attaching the function to the context object:
Function.prototype.callPolyfill = function(context, ...args) {
if (context === null || context === undefined) {
return this(...args);
}
// Use a unique symbol to avoid property conflicts
const fnSymbol = Symbol('fn');
context[fnSymbol] = this;
const result = context[fnSymbol](...args);
delete context[fnSymbol];
return result;
};
4. Primitive Values as Context
When primitive values (strings, numbers, booleans) are passed as context, they need to be boxed into their object wrappers, which the simple bind()
approach handles but may not be obvious.
Problem Example:
function getType() {
return typeof this;
}
// With native call
console.log(getType.call(5)); // "object" (Number object)
console.log(getType.call("hello")); // "object" (String object)
console.log(getType.call(true)); // "object" (Boolean object)
Solution: Explicitly handle primitive boxing if needed for clarity:
Function.prototype.callPolyfill = function(context, ...args) {
// Box primitives explicitly
if (context !== null && context !== undefined) {
const type = typeof context;
if (type === 'string' || type === 'number' || type === 'boolean') {
context = Object(context);
}
}
const fn = this.bind(context);
return fn(...args);
};
5. Arrow Functions Cannot Be Bound
Arrow functions have lexically bound this
that cannot be changed. The polyfill will fail silently or produce unexpected results with arrow functions.
Problem Example:
const arrowFunc = () => {
return this.value;
};
// This won't work as expected
arrowFunc.callPolyfill({value: 42}); // Returns undefined or outer scope's this.value
Solution: Add a check for arrow functions and handle appropriately:
Function.prototype.callPolyfill = function(context, ...args) {
// Check if it's an arrow function (no prototype property)
if (!this.prototype) {
console.warn('Arrow functions cannot have their context rebound');
return this(...args);
}
const fn = this.bind(context);
return fn(...args);
};
Which of the following uses divide and conquer strategy?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!