Para una serie de ejercicios que están recreando Underscore.js, estoy tratando de entender cómo funciona el método call() debajo del capó.
Entiendo cómo funciona el método call() en el siguiente ejemplo.
let person = { firstName:"John", lastName: "Doe", fullName: function() { return this.firstName + " " + this.lastName; } } let myObject = { firstName:"Mary", lastName: "Doe", } person.fullName.call(myObject); // Will return "Mary Doe" Sin embargo, me cuesta entender la idea de contexto como en iteratee.call(context, collection[i], i, collection) .
Este es el ejercicio en el que estoy trabajando:
// _.each(collection, iteratee, [context]) // Iterates over a collection of elements (ie array or object), // yielding each in turn to an iteratee function, that is called with three arguments: // (element, index|key, collection;), and bound to the context if one is passed. // Returns the collection for chaining. _.each = function (collection, iteratee, context) { if (Array.isArray(collection)) { for (let i = 0; i < collection.length; i++) { iteratee.call(context, collection[i], i, collection); } } else if (collection !== null) { Object.entries(collection).map(([key, value]) => { iteratee.call(context, value, key, collection); }); } return collection; };Gracias de antemano por tu ayuda.
Hace muchos años era común usar la palabra "contexto" para referirse al valor que this debía tener durante una llamada a una función. (No es un buen término y ha caído en desgracia). Lo que hace esa definición _.each es aceptar un parámetro opcional, context , y usarlo para establecer qué es this al llamar a la función iteratee que se pasó para que this dentro de la llamada iteratee es cualquier context . (Si no se proporciona el context , será undefined , y this dentro de iteratee será undefined [en modo estricto] o el objeto global [en modo flexible]).
Por ejemplo, suponga que tiene un objeto con un método y desea llamar a ese método para cada entrada en una matriz usando _.each . Puedes hacerlo así:
const obj = { id: "some ID", method(value) { console.log(`this.id = ${this.id}, value = ${value}`); } }; _.each([1, 2, 3], obj.method, obj);Ejemplo en vivo:
"use strict"; const _ = {}; _.each = function (collection, iteratee, context) { if (Array.isArray(collection)) { for (let i = 0; i < collection.length; i++) { iteratee.call(context, collection[i], i, collection); } } else if (collection !== null) { Object.entries(collection).map(([key, value]) => { iteratee.call(context, value, key, collection); }); } return collection; }; const obj = { id: "some ID", method(value) { console.log(`this.id = ${this.id}, value = ${value}`); } }; _.each([1, 2, 3], obj.method, obj); Si no proporcionó obj como tercer parámetro, this.id en obj.method no funcionaría correctamente porque this sería obj , sería undefined (modo estricto) o el objeto global. Por ejemplo (modo estricto):
"use strict"; const _ = {}; _.each = function (collection, iteratee, context) { if (Array.isArray(collection)) { for (let i = 0; i < collection.length; i++) { iteratee.call(context, collection[i], i, collection); } } else if (collection !== null) { Object.entries(collection).map(([key, value]) => { iteratee.call(context, value, key, collection); }); } return collection; }; const obj = { id: "some ID", method(value) { console.log(`this.id = ${this.id}, value = ${value}`); } }; _.each([1, 2, 3], obj.method); // <== Note no third argument El método de matriz forEach también tiene este parámetro (llamado thisArg ), al igual que muchos otros métodos de matriz integrados.
Pero en estos días, me parece que es más común usar una función de flecha de envoltura en su lugar:
_.each([1, 2, 3], value => obj.method(value));Puede pensar en this como un parámetro implícito de todas las funciones. Para ilustrar, la siguiente función arrojará un error porque no hay una variable banana declarada:
function fruit() { console.log(banana); } fruit(); Pero la siguiente función está bien porque this es un parámetro declarado implícitamente:
'use strict'; function fruit() { console.log(this); } fruit(); No puede pasar this directamente como argumento, porque entonces tendría que volver a declarar el nombre:
function broken(this) { console.log(this.name); } broken({name: 'Ann'}); Las únicas formas válidas de pasar un argumento this a una función son llamarlo como un método o usar call o apply :
// the following lines are equivalent anObject.aMethod(argument1, argument2); aMethod.call(anObject, argument1, argument2); aMethod.apply(anObject, [argument1, argument2]); En todos los casos, aMethod verá a anObject como su argumento this . Sin embargo, el primer caso solo funciona si aMethod es una propiedad de anObject , mientras que los demás siempre funcionan. La única diferencia entre call y apply es que este último toma todos los argumentos después de this en una sola matriz.
Ahora para llegar a la línea que te confundió:
iteratee.call(context, collection[i], i, collection) iteratee es simplemente cualquier función, y context es solo una variable que se proporcionará como this argumento implícito. La variable de context podría haber tenido cualquier otro nombre, como thisArg u object .