Implementaciones personalizadas de call, bind y apply en JavaScript

Los métodos call, bind y apply son funcionalidades del prototipo Function en JavaScript, diseñadas para mdoificar el contexto de ejecución (el valor de this) de una función.

Consideraciones Generales

Si bien las implementaciones nativas de estos métodos están escritas en C++, podemos simular su comportamiento utilizando JavaScript puro. Es importante tener en cuenta algunas particularidades:

  • Si se proporciona un valor de tipo primitivo (como un número, cadena o booleano) como argumento para this, JavaScript lo envolverá automáticamente en su correspondiente tipo objeto (Number, String, Boolean) para permitir la asignación de propiedades. La función Object() puede usarse para realizar esta conversión explícitamente.
  • En modo no estrcito, si no se especifica un contexto de this o se pasa null o undefined, el valor de this dentro de la función se referirá al objeto global (window en navegadores).

Implementación Personalizada de call

El método call permite invocar una función con un this específico y argumentos pasados individualmente.


Function.prototype.iCall = function (thisContext, ...args) {
  // 1. Guardar la función original a la que se llama
  const originalFunction = this;

  // 2. Determinar el contexto de 'this'. Si es nulo/indefinido, usar window (en modo no estricto).
  //    De lo contrario, convertir el contexto proporcionado a un objeto.
  const context = (thisContext === null || thisContext === undefined)
    ? window
    : Object(thisContext);

  // 3. Asignar la función original como una propiedad temporal del objeto de contexto.
  context.tempFunction = originalFunction;

  // 4. Invocar la función a través del objeto de contexto, pasando los argumentos.
  const result = context.tempFunction(...args);

  // 5. Eliminar la propiedad temporal del objeto de contexto.
  delete context.tempFunction;

  // 6. Devolver el resultado de la ejecución de la función.
  return result;
};

// Ejemplo de uso:
function displayUserInfo(city, country) {
  console.log(`Contexto 'this':`, this);
  console.log(`Ciudad: ${city}, País: ${country}`);
}

const userDetails = { name: 'Alice' };

console.log("--- Ejemplo iCall ---");
displayUserInfo.iCall(userDetails, 'New York', 'USA');
// Salida esperada:
// Contexto 'this': { name: 'Alice' }
// Ciudad: New York, País: USA

displayUserInfo.iCall(null, 'London', 'UK');
// Salida esperada (en modo no estricto):
// Contexto 'this': Window {...}
// Ciudad: London, País: UK

Implementación Personalizada de apply

Similar a call, apply invoca una función con un this específico, pero los argumentos se pasan como un array.


Function.prototype.iApply = function (thisContext, argsArray) {
  // 1. Guardar la función original
  const originalFunction = this;

  // 2. Determinar el contexto de 'this', similar a iCall
  const context = (thisContext === null || thisContext === undefined)
    ? window
    : Object(thisContext);

  // 3. Asignar la función como propiedad temporal
  context.tempFunction = originalFunction;

  // 4. Invocar la función usando el operador spread para desempaquetar el array de argumentos
  const result = context.tempFunction(...argsArray);

  // 5. Eliminar la propiedad temporal
  delete context.tempFunction;

  // 6. Devolver el resultado
  return result;
};

// Ejemplo de uso:
function displayProductDetails(price, currency) {
  console.log(`Contexto 'this':`, this);
  console.log(`Precio: ${price} ${currency}`);
}

const productInfo = { id: 'XYZ123' };
const details = [50, 'USD'];

console.log("\n--- Ejemplo iApply ---");
displayProductDetails.iApply(productInfo, details);
// Salida esperada:
// Contexto 'this': { id: 'XYZ123' }
// Precio: 50 USD

const dataString = "Sample Data";
const formatArgs = ['PDF', 'A4'];
displayProductDetails.iApply(dataString, formatArgs);
// Salida esperada (en modo no estricto):
// Contexto 'this': String { "Sample Data" }
// Precio: PDF A4

Implementación Personalizada de bind

El método bind crea una nueva función que, cuando es llamada, tiene su palabra clave this establecida con el valor proporcionado, junto con una secuencia de argumentos iniciales. No invoca la función inmediatamente.


Function.prototype.iBind = function (thisContext, ...initialArgs) {
  // 1. Guardar la función original
  const originalFunction = this;

  // 2. Determinar el contexto de 'this'
  const context = (thisContext === null || thisContext === undefined)
    ? window
    : Object(thisContext);

  // 3. Devolver una nueva función. Esta nueva función contendrá el contexto y los argumentos iniciales.
  return function (...laterArgs) {
    // Dentro de esta nueva función, asignamos temporalmente la función original al contexto
    // y la ejecutamos con los argumentos iniciales y los argumentos pasados posteriormente.
    context.tempFunction = originalFunction;
    const result = context.tempFunction(...initialArgs, ...laterArgs);

    // Es importante eliminar la propiedad temporal después de la ejecución
    // para evitar efectos secundarios en el objeto de contexto.
    delete context.tempFunction;

    // Devolver el resultado de la ejecución.
    return result;
  };
};

// Ejemplo de uso:
function greet(greeting, punctuation) {
  console.log(`Contexto 'this':`, this);
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Bob' };

console.log("\n--- Ejemplo iBind ---");
// Creamos una nueva función 'boundGreet' con 'person' como contexto y 'Hello' como saludo inicial.
const boundGreet = greet.iBind(person, 'Hello');

// Ahora llamamos a 'boundGreet' con argumentos adicionales ('!').
boundGreet('!');
// Salida esperada:
// Contexto 'this': { name: 'Bob' }
// Hello, Bob!

// También podemos llamar a la función devuelta por bind con más argumentos.
const anotherBoundGreet = greet.iBind({ name: 'Charlie' });
anotherBoundGreet('Hi', '. Welcome!');
// Salida esperada:
// Contexto 'this': { name: 'Charlie' }
// Hi, Charlie. Welcome!

Etiquetas: JavaScript call apply bind this

Publicado el 7-27 16:30