Как Function.bind.bind(Function.call) не работает?

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

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

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

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

3 ответа

Это проходит 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(), Это также позволяет вам немного дольше оставаться на позиции старшего фронтенд-разработчика.

редактировать - я собираюсь испортить изюминку выше, потому что я просто подумал о хорошей причине использовать трюк bind + 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, это может иметь больше смысла. Конечно, у всего есть цена, и, вероятно, такие упаковщики будут иметь некоторое влияние на производительность. (Если бы подобные практики были распространены, то время выполнения, вероятно, можно было бы улучшить.)

ХОРОШО. Знаешь что bind делает? Это метод функций, чтобы исправить их this аргумент и возвращает новую функцию. Это может быть упрощено до:

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

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

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

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

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

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

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

fn context (…)

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

uncurryThis = bind bind (вызов) // обязательный звонок

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

результат = func(контекст, …) // контекст метода (…)

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

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));
    };
}

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

Контекст:

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

[‘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's 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], и это должно иметь смысл.

Когда мы вызываем функцию 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Поэтому мы называем BB (случайным), мы имеем 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.
Другие вопросы по тегам