Как работает контекст в методе call()

#javascript #underscore.js

#язык JavaScript #underscore.js

Вопрос:

Для серии упражнений, которые воссоздают Underscore.js, Я пытаюсь понять, как метод call() работает под капотом.

Я понимаю, как работает метод call() в приведенном ниже примере.

 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" 

Однако мне трудно понять идею контекста, как в iteratee.call(context, collection[i], i, collection) .

Вот упражнение, над которым я работаю:

 // _.each(collection, iteratee, [context]) // Iterates over a collection of elements (i.e. 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 lt; collection.length; i  ) {  iteratee.call(context, collection[i], i, collection);  }  } else if (collection !== null) {  Object.entries(collection).map(([key, value]) =gt; {  iteratee.call(context, value, key, collection);  });  }   return collection; }; 

Заранее спасибо за вашу помощь.

Ответ №1:

Много лет назад было принято использовать слово «контекст» для обозначения значения, которое this должно быть во время вызова функции. (Это не очень хороший термин, и он попал в немилость.) Что делает это _.each определение, так это принимает необязательный параметр context и использует его для задания того , что this происходит при вызове iteratee переданной функции, чтобы this в iteratee вызове было все, что context есть. (Если context это не предусмотрено, это будет undefined , и this внутри iteratee будет либо undefined [в строгом режиме], либо глобальный объект [в свободном режиме].)

Например, предположим, что у вас есть объект с методом, и вы хотите вызвать этот метод для каждой записи в массиве с помощью _.each . Ты можешь сделать это вот так:

 const obj = {  id: "some ID",  method(value) {  console.log(`this.id = ${this.id}, value = ${value}`);  } }; _.each([1, 2, 3], obj.method, obj);  

Живой Пример:

 "use strict";  const _ = {}; _.each = function (collection, iteratee, context) {  if (Array.isArray(collection)) {  for (let i = 0; i lt; collection.length; i  ) {  iteratee.call(context, collection[i], i, collection);  }  } else if (collection !== null) {  Object.entries(collection).map(([key, value]) =gt; {  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); 

Если бы вы не указали obj в качестве третьего параметра, this.id in obj.method не работал бы корректно , потому this что его не было obj бы, это был бы либо undefined (строгий режим), либо глобальный объект. Например (строгий режим):

 "use strict";  const _ = {}; _.each = function (collection, iteratee, context) {  if (Array.isArray(collection)) {  for (let i = 0; i lt; collection.length; i  ) {  iteratee.call(context, collection[i], i, collection);  }  } else if (collection !== null) {  Object.entries(collection).map(([key, value]) =gt; {  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); // lt;== Note no third argument 

Метод встроенного массива forEach также имеет этот параметр (называемый thisArg ), как и многие другие методы встроенного массива.

Но в наши дни мне кажется, что чаще всего вместо этого используется функция стрелки-оболочки:

 _.each([1, 2, 3], value =gt; obj.method(value));  

Ответ №2:

Вы можете рассматривать this как неявный параметр всех функций. Чтобы проиллюстрировать это, следующая функция выдаст ошибку из-за отсутствия объявленной переменной banana :

 function fruit() {  console.log(banana); }  fruit(); 

Но следующая функция прекрасна, потому this что является неявно объявленным параметром:

 'use strict';  function fruit() {  console.log(this); }  fruit(); 

Вы не можете передать this напрямую в качестве аргумента, потому что тогда вам придется повторно объявить имя:

 function broken(this) {  console.log(this.name); }  broken({name: 'Ann'}); 

Единственно допустимые способы передачи this аргумента функции-либо вызвать ее как метод, либо использовать call или apply :

 // the following lines are equivalent anObject.aMethod(argument1, argument2); aMethod.call(anObject, argument1, argument2); aMethod.apply(anObject, [argument1, argument2]);   

Во всех случаях aMethod будет рассматриваться anObject как его this аргумент. Однако первый случай работает только в том случае , если aMethod является свойством anObject , в то время как другие всегда работают. Единственное различие между call и apply заключается в том, что последнее принимает все аргументы после this в одном массиве.

Теперь перейдем к той черте, которая вас смутила:

 iteratee.call(context, collection[i], i, collection)  

iteratee это просто любая функция, и context это просто переменная, которая будет указана в качестве ее неявного this аргумента. context Переменная могла иметь любое другое имя, например thisArg или object .