LeetCode 2754 - Bind Function to Context

The problem is asking us to implement a polyfill for JavaScript's built-in Function.prototype.bind method. Specifically, we need to create a bindPolyfill method that can be called on any function.

LeetCode Problem 2754

Difficulty: 🟡 Medium
Topics:

Solution

Problem Understanding

The problem is asking us to implement a polyfill for JavaScript's built-in Function.prototype.bind method. Specifically, we need to create a bindPolyfill method that can be called on any function. When invoked, it returns a new function whose this context is fixed to the object passed as an argument. Additionally, this bound function should accept any number of arguments and pass them to the original function.

The inputs in the problem include a function fn, an object obj to bind as this, and optionally an array of inputs to pass to the bound function. The output is the result of executing the bound function with the provided inputs while ensuring that the context (this) inside the function is the specified object.

Constraints indicate that obj is always non-null, so we do not need to handle null or primitive this values. The number of inputs can be up to 100, which is modest and does not require performance optimizations beyond avoiding unnecessary computations. Important edge cases include functions with no arguments, functions that return undefined, or functions using nested this references.

This problem essentially requires understanding JavaScript function contexts and closures, and the solution must work without using the built-in Function.prototype.bind.

Approaches

The brute-force approach would be to manually call the function and set this by using a temporary property on the object. While this would work for simple cases, it may be cumbersome to handle variadic arguments and could produce side effects if obj already has a property with the temporary name. The brute-force approach conceptually works, but it is not clean or idiomatic and risks mutating input objects.

The optimal approach leverages closures in JavaScript to create a new function that internally calls the original function with the specified this context. This avoids modifying the input object and handles any number of arguments dynamically. The key insight is that in JavaScript, Function.prototype.apply can invoke a function with an explicitly set this and an array of arguments. By returning a closure, we encapsulate the context and arguments without touching the original function or object.

Approach Time Complexity Space Complexity Notes
Brute Force O(n) O(1) Uses temporary property on object to set this and invokes function directly
Optimal O(n) O(1) Uses closure and apply to bind context, handles any number of arguments without mutating object

Algorithm Walkthrough

  1. Extend the Function.prototype with a method called bindPolyfill. This ensures that all functions can call this method.
  2. Inside bindPolyfill, capture the original function (this) in a local variable so it can be referenced later in a closure.
  3. Return a new function that takes any number of arguments using the rest parameter syntax (...args).
  4. Inside the returned function, invoke the original function using Function.prototype.apply, passing the object obj as the first argument (the this context) and the arguments array (args) as the second argument.
  5. The returned function now behaves as a bound function, maintaining the context and correctly forwarding all arguments.

Why it works: The closure captures the original function and the binding object. Using apply ensures that the context inside the function is always the provided object and all arguments are passed correctly. Since a new function is returned, the original function remains unchanged, adhering to the behavior of JavaScript's native bind.

Python Solution

class FunctionBindPolyfill:
    def __init__(self, fn):
        self.fn = fn

    def bindPolyfill(self, obj):
        def bound_function(*args):
            return self.fn(obj, *args)
        return bound_function

# Example usage for Python adaptation
def f(self, multiplier):
    return self["x"] * multiplier
bound = FunctionBindPolyfill(f).bindPolyfill({"x": 10})
print(bound(5))  # Output: 50

Explanation: We encapsulate the function inside a class FunctionBindPolyfill since Python functions do not have a bind method by default. The bindPolyfill method returns a closure bound_function that takes any arguments and explicitly passes the bound object obj as the first argument. This simulates JavaScript's this binding.

Go Solution

package main

import "fmt"

type Fn func(obj map[string]int, args ...int) int

func BindPolyfill(fn Fn, obj map[string]int) func(args ...int) int {
    return func(args ...int) int {
        return fn(obj, args...)
    }
}

// Example usage
func f(obj map[string]int, args ...int) int {
    return obj["x"] * args[0]
}

func main() {
    bound := BindPolyfill(f, map[string]int{"x": 10})
    fmt.Println(bound(5)) // Output: 50
}

Explanation: Go does not have dynamic this like JavaScript, so we explicitly pass the context object as the first argument. The BindPolyfill function returns a closure that captures fn and obj, simulating a bound method with variadic arguments.

Worked Examples

Example 1

Original function: fn = function f(multiplier) { return this.x * multiplier; }

Binding object: obj = {"x": 10}

Inputs: [5]

Step Operation this / obj Result
1 Call bindPolyfill(obj) obj = {"x":10} Returns closure
2 Invoke closure with 5 this = obj Calls f.apply(obj, [5])
3 Multiply this.x * 5 10 * 5 50
4 Return 50 50

Example 2

Original function: fn = function speak() { return "My name is " + this.name; }

Binding object: obj = {"name": "Kathy"}

Inputs: []

Step Operation this / obj Result
1 Call bindPolyfill(obj) obj = {"name":"Kathy"} Returns closure
2 Invoke closure with no args this = obj Calls speak.apply(obj, [])
3 Return concatenated string "My name is Kathy" "My name is Kathy"

Complexity Analysis

Measure Complexity Explanation
Time O(n) n is the number of arguments passed; each is forwarded exactly once
Space O(1) Closure captures reference to function and object; no additional memory proportional to inputs

The complexity is minimal since the algorithm only wraps the original function and passes arguments. There is no iteration over object properties or creation of auxiliary data structures.

Test Cases

# Test cases
def test_bind_polyfill():
    # Example 1
    fn = lambda self, multiplier: self["x"] * multiplier
    bound = FunctionBindPolyfill(fn).bindPolyfill({"x": 10})
    assert bound(5) == 50  # normal case with single argument

    # Example 2
    fn2 = lambda self: "My name is " + self["name"]
    bound2 = FunctionBindPolyfill(fn2).bindPolyfill({"name": "Kathy"})
    assert bound2() == "My name is Kathy"  # no arguments

    # Edge case: multiple arguments
    fn3 = lambda self, a, b: self["y"] + a + b
    bound3 = FunctionBindPolyfill(fn3).bindPolyfill({"y": 2})
    assert bound3(3, 4) == 9

    # Edge case: empty object
    fn4 = lambda self: len(self)
    bound4 = FunctionBindPolyfill(fn4).bindPolyfill({})
    assert bound4() == 0

test_bind_polyfill()
Test Why
single argument verifies basic multiply with context
no arguments ensures function works without parameters
multiple arguments tests variadic argument forwarding
empty object checks binding to empty context

Edge Cases

One important edge case is binding a function with no arguments. In this case, the bound function must still return the correct result using only the bound context. Another is binding to an object with no properties or empty values; the algorithm handles this correctly because apply is called with the object, regardless of its contents. A third edge case is functions that accept multiple arguments; using the rest parameter syntax (...args) ensures that all arguments are passed to the original function without loss or misalignment. All of these cases are handled cleanly by the closure-based approach.