Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Usage and Custom Implementation of JavaScript's apply, call, and bind Methods

Tech 2

Core Differences and Basic Usage

const userCtx = { username: "luna" };
function displayProfile(age, gender) {
  console.log(`Username: ${this.username}, Age: ${age}, Gender: ${gender}`);
}
// apply accepts target context and array of parameters, executes immediately
displayProfile.apply(userCtx, [24, "female"]);
// call accepts target context and discrete parameters, executes immediately
displayProfile.call(userCtx, 24, "female");
// bind returns a new function with bound context and pre-filled parameters, requires explicit execution
displayProfile.bind(userCtx, 24, "female")();

Custom apply Implementation

The core challenge of implementing apply is expanding the input parameter array into discrete arguments for functoin invocation, which can be handled via eval for broad compatibility.

Function.prototype.customApply = function (targetCtx, paramList) {
  // Fallback to global window object if no valid context is provided
  const executionCtx = targetCtx || window;
  // Attach current function as a temporary method to target context to bind this pointer
  executionCtx.tempFn = this;
  // Handle empty parameter list edge case
  if (!paramList || paramList.length === 0) {
    const result = executionCtx.tempFn();
    delete executionCtx.tempFn;
    return result;
  }
  // Build argument string for eval execution
  const argStr = paramList.map((_, idx) => `paramList[${idx}]`).join(",");
  const runResult = eval(`executionCtx.tempFn(${argStr})`);
  // Clean up temporary property to avoid side effects
  delete executionCtx.tempFn;
  return runResult;
};

Test verification:

const calcCtx = { base: 10 };
function computeSum(addVal1, addVal2) {
  console.log(this.base + addVal1 + addVal2);
}
computeSum.customApply(calcCtx, [5, 3]);

Output:

18

Custom call Implementation

call shares almost identical logic with apply, the only difference being that it accepts discrete parameters instead of a single parameter array.

Function.prototype.customCall = function (targetCtx) {
  const executionCtx = targetCtx || window;
  executionCtx.tempFn = this;
  // Extract all parameters passed after the context argument
  const params = Array.prototype.slice.call(arguments, 1);
  if (params.length === 0) {
    const result = executionCtx.tempFn();
    delete executionCtx.tempFn;
    return result;
  }
  const argStr = params.map((_, idx) => `params[${idx}]`).join(",");
  const runResult = eval(`executionCtx.tempFn(${argStr})`);
  delete executionCtx.tempFn;
  return runResult;
};

Test verification:

const calcCtx = { base: 10 };
function computeSum(addVal1, addVal2) {
  console.log(this.base + addVal1 + addVal2);
}
computeSum.customCall(calcCtx, 5, 3);

Output:

18

Custom bind Implementation

bind has 3 key behaviors that need too be replicated:

  1. Returns a new function with pre-bound context
  2. Supports partial parameter application (curying)
  3. Ignores pre-bound context when the returned function is used as a constructer, and preserves the original function's prototype chain for generated instances
Function.prototype.customBind = function (targetCtx) {
  const executionCtx = targetCtx || window;
  const originalFn = this;
  // Extract pre-filled parameters passed during bind invocation
  const prefilledParams = Array.prototype.slice.call(arguments, 1);
  
  function BoundFn() {
    // Combine pre-filled parameters with parameters passed during actual invocation
    const fullParams = prefilledParams.concat(Array.prototype.slice.call(arguments));
    // Use new instance as context if called as constructor, otherwise use pre-bound context
    const runCtx = this instanceof originalFn ? this : executionCtx;
    return originalFn.customApply(runCtx, fullParams);
  }
  // Bridge prototype chain to preserve inheritance for instances generated via new
  function ProtoBridge() {}
  ProtoBridge.prototype = originalFn.prototype;
  BoundFn.prototype = new ProtoBridge();
  
  return BoundFn;
};

Test verification:

const testCtx = { val: 2 };
function TestConstructor(p1, p2) {
  console.log(this.val, p1, p2);
}
TestConstructor.prototype.sharedProp = 100;

const BoundConstructor = TestConstructor.customBind(testCtx, 5);
// Normal function invocation
BoundConstructor(7);
// Invocation as constructor
const instance = new BoundConstructor(7);
// Prototype inheritance check
console.log(instance.sharedProp);

Output:

2 5 7
undefined 5 7
100

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.