Подтвердить что ты не робот

Как функция Function.bind.bind(Function.call) не работает?

У нас есть эта строка в моей базе кода:

var uncurryThis = Function.bind.bind(Function.call);

Я пытаюсь работать. Предположительно, это происходит. Как мне это решить?

Я предполагаю, что это версия Function.bind, чей собственный this привязан к Function.call. Не помогает мне. И я не нашел каких-либо применений, поэтому я даже не уверен, что вы называете это автономным или нужно называть его "как метод", но знаете, связать его сначала.

4b9b3361

Ответ 1

Он передает функцию call функции bind, причем самой функцией bind является значение this. Таким образом, вы получаете обратную оболочку вокруг функции bind, которая организует для this функцию call при ее вызове. Это, в свою очередь, функция, которая позволяет создавать оболочку вокруг функции call, привязанной к некоторому аргументу, который вы передаете.

Если вы не пили кофе без остановок, так как вы проснулись сегодня утром, шаг за шагом:

  • Function.bind.bind является ссылкой на функцию bind. Ссылка создается из свойства — точка путаницы 1 — самой функции bind. Помните, что функция bind при вызове с некоторой функцией как объект используется для создания обертки вокруг этой функции с this, связанной с первым переданным аргументом.
  • Таким образом, вызов функции возвращает вам функцию. Эта функция работает так, как будто вы вызвали Function.call.bind(something).
  • Если вы передаете некоторую случайную функцию в качестве аргумента этой функции, вы получите обратно оболочку вокруг случайной функции, которая при вызове будет действовать как randomFunction.call(whatever).

Итак:

function random() {
  alert(this.foo);
}

var bb = Function.bind.bind(Function.call);

var randomcall = bb(random);

randomcall({ foo: "hello world" }); // alerts "hello world"

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

random.call(someObject);

Но этот волшебный трюк "bind-bind-call" дает вам дешевый способ создания вариации вашей функции, которая позволяет избежать явно закодированного вызова .call(). Он также позволяет вам немного висеть на верхней позиции разработчика.

edit — Я собираюсь испортить строку перфорации выше, потому что я просто подумал о хорошей причине использовать трюк call + call для получения функции, которая организует вызов какой-либо желаемой функции, которая рассчитывает работать через this на некоторых "владелец". Скажем, у вас есть массив строк, и вы хотите получить версию этих строк в нижнем регистре. Вы могли бы написать это:

var uc = ["Hello", "World"];
var lc = uc.map(function(s) { return s.toLowerCase(); });

Но с магической функцией "bb" мы могли бы также написать:

var uc = ["Hello", "World"];    
var tlc = bb(String.prototype.toLowerCase);
var lc = uc.map(tlc);

Не так много улучшений, написанных таким образом, но если бы нужно было создать набор обертованных bb() оберток всех удобных методов прототипов String, это может иметь больше смысла. Конечно, у всех есть цена, и, вероятно, дело в том, что такие обертки будут иметь некоторое влияние на производительность. (Если бы такие обычаи были обычными, то время от времени, возможно, было бы улучшено.)

Ответ 2

OK. Вы знаете, что делает bind? Это метод функций для фиксации своего аргумента this и возвращает новую функцию. Это можно упростить:

function bind(context) {
    var fn = this;
    return function() {
        return fn.apply(context, arguments);
    };
}

Я сокращу вызовы функций с контекстами в более функциональном стиле с большим количеством частичного приложения: bind fn (context) → fn context. С аргументами: (bind fn (context)) (...) равно fn context (...).

Аналогично, call принимает значение this, но вместо того, чтобы возвращать функцию, оно применяется прямо сейчас: call fn (контекст,...) → fn context (...).

Итак, теперь давайте получим код: bind.call(bind, call). Здесь вы применяете bind в bind с call как это значение: bind bind (вызов). Позвольте развернуть это (с приведенным выше правилом), чтобы связать call. Что, если мы теперь предоставили ему некоторые аргументы?

bind bind (вызов) (fn) (контекст,...)

bind call (fn) (контекст,...)

вызов fn (контекст,...)

п <суб > контекстсуб > (...)

Шаг за шагом, мы могли бы сделать

uncurryThis = bind bind (вызов) // bind call

func = uncurryThis (метод) // вызов method

result = func (context,...) // метод context (...)

Практический пример использования - это любые "классные" методы, которые должны быть преобразованы в статическую функцию, взяв объект (на который метод будет вызываться) в качестве первого аргумента:

var uncurryThis = Function.bind.bind(Function.call);
var uc = uncurryThis(String.prototype.toUpperCase);
uc("hello") // in contrast to "hello".toUpperCase()

Это может быть полезно, если вы не можете поместить вызов метода, но вам нужна статическая функция; например как в

["hello", "world"].map(uc) // imagine the necessary function expression

Кроме того, метод, который вы хотите вызвать, может не быть методом самого объекта, как в

var slice = uncurryThis(Array.prototype.slice);
slice(arguments) // instead of `Array.prototype.slice.call(arguments)` everywhere

Если это помогает, здесь также явная реализация без каких-либо привязок:

function uncurryThis(method) {
    return function(context/*, ...*/)
        return method.apply(context, Array.prototype.slice.call(arguments, 1));
    };
}

Ответ 3

когда мы вызываем bind для функции, он возвращает новую функцию, которая заменяется контекстом:

function random() {
  alert(this.foo);
}
var newRandom = random.bind({foo:"hello world"}) //return new function same as //`random` with `this` is replaced by object {foo:"hello world"}

то же самое:

Function.bind.bind(Function.call)
// return new Function.bind with its `this` is replaced by `Function.call`

Он имеет следующий источник (используется упрощенная версия функции bind, заданная @Bergi):

var bb = function bind(context){
  var fn = Function.call;
  return function() {
        return Function.call.apply(context, arguments); //also replace fn here for easier reading
    };
}

Обратите внимание, что здесь контекст будет функцией, например random, поэтому wen call bb (random) имеет функцию newRandom как:

newRandom = function(){
   return Function.call.apply(random, arguments); //also replace 
}
//`apply` function replace `this` of Function.call to `random`, and apply Function(now become `random`) with arguments in `arguments` array.

Ответ 4

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

Контекст:

Предположим, мы хотим использовать строковый массив в нижнем регистре. Это можно сделать так:

[‘A, ‘B].map(s => s.toLowerCase())

Скажем, по какой-то причине я хочу сделать этот призыв более общим. Мне не нравится, как s связан с this и жирная стрелка связана с toLowerCase().

Как насчет этого?

[‘A, ‘B].map(String.prototype.toLowerCase)

Ну, это не работает, потому что map передает элемент в качестве первого аргумента, но String.prototype.toLowerCase принимает аргументов. Ожидается, что входная строка будет передана следующим this.

Итак, вопрос в том, можем ли мы создать функцию- wrapper которая сделает эту работу?

[‘A, ‘B].map(wrapper(String.prototype.toLowerCase))

wrapper возвращает функцию, которая превращает первый аргумент, передаваемый в this для String.prototype.toLowerCase использовать.

Я утверждаю, что ваш uncurryThis === wrapper.


Доказательство:

Так что давайте не будем пытаться понять unCurryThis все сразу. Вместо этого позвольте использовать некоторые формулы для преобразования unCurryThis во что-то более понятное.

Сначала несколько формул:

instance.function(...args)
=== (instance.constructor.prototype).function.call(instance, ...args)
=== (Class.prototype).function.call(instance, ...args) [1]
=== (Class.prototype).function.bind(instance)(...args) [2]

Например,

Class === String
instance === 'STRING'
function === toLowerCase
args === []
---
'string'.toLowerCase()
=== ('STRING'.constructor.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.bind('STRING')()

Так что давайте просто слепо применяем эти формулы, не беспокоясь о том, как выглядит эта запутанная uncurryThis:

'string'
=== (wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING')

// Function.bind is not really the generic form because it not using the prototype
// Here Function is an instance of a Function and not the constructor.prototype
// It is similar to calling Array.bind or someFunction.bind
// a more correct version would be
// someFunction.constructor.prototype.bind === Function.prototype.bind, so
=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING')

// Apply formula 2
// instance.function(...args) === (Class.prototype).function.bind(instance)(...args) [2]
// Class === Function
// function === bind
// instance === Function.prototype.call
// ...args === String.prototype.toLowerCase
=== instance.function(...args)('STRING')
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING')

// Apply formula 2 again
// Class == Function
// function == call
// instance === String.prototype.toLowerCase
// ...args === 'STRING'
=== instance.function(...args)
=== (String.prototype.toLowerCase).call('STRING')

// Apply formula 1
instance.function(...args) === (Class.prototype).function.call(instance, ...args) [1]
// Class === String
// function === toLowerCase
// instance === 'STRING'
// args === []
=== instance.function(...args)
=== 'STRING'.toLowerCase(...[])
=== 'STRING'.toLowerCase()

// So we have
(wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== 'STRING'.toLowerCase()
=== 'string'

Обратное доказательство:

Таким образом, вы можете спросить: "Как этот парень даже получил функцию uncurryThis "?

Вы можете отменить доказательство, чтобы получить его. Я просто копирую уравнения сверху, но в обратном порядке:

'STRING'.toLowerCase()
=== (String.prototype.toLowerCase).call('STRING') // apply formula [1]
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING') // apply formula [2]

// At this point, you might wonder why 'uncurryThis !== (Function.prototype.call).bind)
// since it also takes (String.prototype.toLowerCase)('STRING')
// This is because passing in (Function.prototype.call).bind) as an argument
// is the same as passing in Function.prototype.bind
// 'this' binding isn't done unless you call
// (Function.prototype.call).bind)(String.prototype.toLowerCase)
// at that exact moment.
// If you want to be able to pass unCurryThis as a function, you need to bind the
// Function.prototype.call to the Function.prototype.bind.

=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING') // apply formula 2
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING') // un-generic-ize
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (wrapper)(String.prototype.toLowerCase)('STRING')

=>

unCurryThis === wrapper === Function.bind.bind(Function.call)

Все еще довольно странно следовать, но попробуйте написать, что такое Class, function, instance и args каждый раз, когда я применяю формулы [1] и [2], и это должно иметь смысл.