Использование apply или call для создания функции pointfree? - PullRequest
0 голосов
/ 14 декабря 2018

Скажем, я хочу создать функцию, которая обрезает свой ввод, одним способом, который я могу сделать, это просто

x => x.trim()

Другим способом будет

x => String.prototype.trim.call(x)

, который имеетпреимущество в том, что если x определил переопределение для .trim(), я все равно могу получить реализацию из прототипа.По сути, если я хочу использовать те же самые вещи в .map(), я могу сделать либо:

[" foo", "bar "].map(x => x.trim())
[" foo", "bar "].map(x => String.prototype.trim.call(x));

Я хочу сделать функцию pointfree .Чего я не понимаю, так это почему я не могу использовать функцию без вызова, созданную с помощью .call

[" foo", "bar "].map(String.prototype.trim.call);

Я также пытался .apply, потому что тамЕсли аргументы отсутствуют, то это одно и то же.

Аналогично, вне карты вы также не можете использовать функцию .call function

> fn = String.prototype.trim.call
[Function: call]
> fn('asdf')
TypeError: fn is not a function

Так что мои вопросы:

  1. Выше функция, возвращаемая String.prototype.trim.call, что делает?Что это за функция?
  2. Почему моя попытка с .call не работает?
  3. Есть ли способ сделать это с .bind, .apply или .call?

Ответы [ 2 ]

0 голосов
/ 14 декабря 2018

Чего я не понимаю, так это почему нельзя использовать функцию без вызова, созданную с помощью .call

[" foo", "bar "].map(String.prototype.trim.call);

Проблема в том, что всефункции наследуют один и тот же метод call от Function.prototype;таким образом, вышеприведенное эквивалентно

[" foo", "bar "].map(Function.prototype.call);

Для того, чтобы ваш код работал, call необходимо будет "помнить", что вы получили его от String.prototype.trim, а не от какой-либо другой функции;но это просто не то, как вызовы методов работают в JavaScript.В выражении вызова метода, таком как foo.bar(), foo - это не просто объект, свойство bar которого вызывается как функция, это также объект, который неявно передается как this в эта функция.В случае String.prototype.trim.call(x) единственная причина, по которой call знает, чтобы вызвать String.prototype.trim, заключается в том, что вы используете синтаксис вызова метода, поэтому он может получить его из this.


  1. Выше функция, возвращаемая String.prototype.trim.call, что делает?Что это за функция?

Это метод, который принимает объект и ноль или более аргументов и который при вызове функции вызывает эту функцию как метод этого объектапередавая эти аргументы.

Почему моя попытка с .call не работает?

Потому что он пытается вызвать call как свободную функцию, а не как метод;поэтому call не получает аргумент this, сообщающий ему, какую функцию вызывать, поэтому он не может выполнять свою работу.(Скорее всего, он получит «объект по умолчанию», например, window, в качестве аргумента this; но есть некоторые тонкости, в которых я не уверен. Вы можете попробовать [" foo", "bar "].map(function () { return this; }) в своем тестовом стенде, чтобы увидеть, что этодает вам в вашем окружении.)

Есть ли в любом случае сделать это с .bind, .apply или .call?

Да;Вы можете написать:

[" foo", "bar "].map(Function.prototype.call.bind(String.prototype.trim))

, где Function.prototype.call.bind(String.prototype.trim) - это функция, которая при вызове вызывает call для String.prototype.trim.(Другими словами, bind обрабатывает «запоминание», что String.prototype.trim - это объект, который вы хотите передать как this.)

Тем не менее, я действительно думаю, что ваша оригинальная версия,

[" foo", "bar "].map(x => x.trim())

значительно выше.Если кто-то переопределил trim(), вы должны верить, что это было по уважительной причине, и что вы действительно должны вызывать это переопределение вместо того, чтобы принудительно использовать унаследованное вместо него.

0 голосов
/ 14 декабря 2018

Интересный вопрос!Это сводится к сигнатуре Array.prototype.map :

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg])

Ключом к пониманию поведения, которое вы заметили, является необязательный thisArg - это контекст, который будет предоставленк обратному вызову

Кроме того, из спецификации :

Если для сопоставления указан параметр thisArg, он будет использоваться в качестве значения этого обратного вызова.В противном случае в качестве значения this будет использоваться значение undefined.

Таким образом, это означает, что undefined будет использоваться в качестве контекста для обратного вызова, который в данном случае равен String.prototype.trim.call

Вот где это становится интересным ... нормальный контекст для Function.prototype.call (который наследует String.prototype.trim.call) - это функция, которую он вызывает, то есть String.prototype.trim в данном случае.Первый аргумент Function.prototype.call предоставляет контекст для функции, которая будет вызываться.

В случае << Array >>.map(String.prototype.trim.call) контекст, предоставленный для String.prototype.trim.call, равен undefined, что означает, что мы пытаемся завершить попыткувызвать undefined с контекстом элемента из массива.Все чисто?Это определенно немного извращает ум.

Мы можем воспользоваться этим поведением, чтобы добиться желаемого эффекта, предоставив String.prototype.trim как thisArg в << Array >>.map(callback, thisArg) и используя Function.call в качестве обратного вызова:

console.log([" foo", "bar "].map(Function.call, String.prototype.trim));

Еще один способ думать о том, что String.prototype.trim предоставляет контекст для Function.call для создания связанной функции, а затем каждый элемент массива предоставляет контекст длясвязанная функция.

...