DEV Community

ValPetal Tech Labs
ValPetal Tech Labs

Posted on

Javascript Question of the Day #9 [Talk::Overflow]

This post explains a quiz originally shared as a LinkedIn poll.

🔹 The Question

const obj = {
  value: 42,
  getValue: function() {
    return this.value;
  }
};

const { getValue } = obj;
console.log(getValue());

const boundGet = obj.getValue.bind(obj);
const { bind } = Function.prototype;
const reboundGet = bind.call(boundGet, { value: 100 });
console.log(reboundGet());
Enter fullscreen mode Exit fullscreen mode

Hint: Once a function is bound, can you rebind it to a different this context? Think about what bind returns and how it handles subsequent bind calls.

🔹 Solution

Correct Answer: C) undefined and 42

The output will be:

  • undefined
  • 42

đź§  How this works

This quiz demonstrates two critical aspects of JavaScript's this binding mechanism that cause production bugs:

  1. Destructuring loses context: When you destructure a method from an object (const { getValue } = obj), you're extracting just the function, not its relationship to the object. When called standalone, this becomes undefined in strict mode (or the global object in non-strict mode).

  2. Bound functions cannot be rebound: Once a function is bound using .bind(), it creates a new function with a permanently locked this context. Calling .bind() again on an already-bound function has no effect—the original binding wins.

The second part is the subtle production footgun: developers often try to "override" a bound function's context for testing, mocking, or context switching, but it silently fails because bound functions are immutable with respect to their this binding.

🔍 Line-by-line explanation

  1. Object definition:
   const obj = { value: 42, getValue: function() { return this.value; } };
Enter fullscreen mode Exit fullscreen mode
  • Creates an object with a method that depends on this
  1. Destructuring the method:
   const { getValue } = obj;
Enter fullscreen mode Exit fullscreen mode
  • Extracts the function reference, losing the connection to obj
  • getValue is now a standalone function
  1. First call:
   console.log(getValue());
Enter fullscreen mode Exit fullscreen mode
  • Calls the function without any context
  • this is undefined (in strict mode, which is implicit in modules)
  • undefined.value would throw an error, but since we're just accessing this.value and this is undefined, it returns undefined
  • Outputs: undefined
  1. Creating a bound function:
   const boundGet = obj.getValue.bind(obj);
Enter fullscreen mode Exit fullscreen mode
  • Creates a new function with this permanently bound to obj
  • boundGet will always use obj as its context, no matter how it's called
  1. Attempting to rebind:
   const { bind } = Function.prototype;
   const reboundGet = bind.call(boundGet, { value: 100 });
Enter fullscreen mode Exit fullscreen mode
  • Extracts the bind method from Function.prototype
  • Calls bind on boundGet, attempting to bind it to a new object { value: 100 }
  • This has no effect! boundGet is already bound, and bound functions ignore rebinding attempts
  • The internal [[BoundThis]] slot of boundGet remains obj
  1. Second call:
   console.log(reboundGet());
Enter fullscreen mode Exit fullscreen mode
  • Calls the "rebound" function
  • Despite the rebinding attempt, it still uses the original binding to obj
  • Returns obj.value → 42
  • Outputs: 42

The non-obvious part: Many developers assume that calling .bind() on an already-bound function will override the previous binding, similar to how reassigning a variable works. But bound functions have an immutable this context—once bound, always bound to that original context.

🔹 The Fix

For destructuring:

// Option 1: Keep the method call
console.log(obj.getValue());

// Option 2: Use arrow function
const getValue = () => obj.getValue();

// Option 3: Bind explicitly
const getValue = obj.getValue.bind(obj);
Enter fullscreen mode Exit fullscreen mode

For rebinding:

// You cannot rebind a bound function. Instead:

// Option 1: Keep reference to original unbound function
const originalGetValue = obj.getValue;
const boundToObj = originalGetValue.bind(obj);
const boundToOther = originalGetValue.bind({ value: 100 });

// Option 2: Use call/apply instead of bind
boundGet.call({ value: 100 }); // Still uses obj, won't work

// Option 3: Wrap in a new function
const reboundGet = function() {
  return obj.getValue.call({ value: 100 });
};

// Option 4: Check if function is bound before attempting rebind
function isBound(fn) {
  return fn.name.startsWith('bound ');
}
Enter fullscreen mode Exit fullscreen mode

🔹 Key Takeaways

  1. Destructuring methods loses this context: Extracting a method from an object creates a standalone function without context binding.

  2. Bound functions are immutable: Once a function is bound with .bind(), its this context cannot be changed by subsequent .bind(), .call(), or .apply() calls.

  3. The original binding always wins: When you bind an already-bound function, the new binding is silently ignored—no error is thrown.

  4. Detection is difficult: There's no reliable built-in way to detect if a function is already bound (checking fn.name.startsWith('bound ') is a heuristic, not a guarantee).

  5. Keep references to originals: If you need flexibility to rebind, keep a reference to the original unbound function rather than only storing bound versions.

  6. Arrow functions vs bind: Arrow functions capture this lexically at definition time and also cannot be rebound, but they're more explicit about this behavior.

  7. Use call/apply for one-time context: If you need to invoke a function with a specific context once without creating a bound function, use .call() or .apply() instead of .bind().

Top comments (0)