#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
.