2013-04-10 3 views
8

У меня есть множество функций и поиск краткого способа вызова каждого в порядке.Вызовите каждую функцию в списке

fns = [ 
    function a() { console.log('a') }, 
    function b() { console.log('b') }, 
    function c() { console.log('c') }, 
] 

это работает:

fns.map(function (f) { f() }) 

и так делает это:

fns.map(function (f) { Function.call.call(f) }) 

однако это вызывает TypeError:

fns.map(Function.call.call) 

Почему не последний пример работы ?

ответ

4

Этот упрощенный код демонстрирует аналогичный вопрос:

var x = Function.call.call; 
x(alert); 

В этом случае, когда Function.call.call вызывается, он не помнит контекст, из которого она возникла (т.е. Function.call). Чтобы сохранить этот контекст, вы могли бы использовать этот нечестивый конструкт трюк:

Function.call.bind(Function.call) 

возвращает новую функцию, причем контекст Function.call привязан к себе, таким образом, сохраняя контекст. Вы можете сохранить это выражение в новой переменной:

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

Теперь callFn(alert) идентичен alert.call(). Обратите внимание, что любые дополнительные аргументы будут передаваться как есть, поэтому callFn(alert, window) будет вызывать alert.call(window). Понимание этого поведения важно в ситуациях, когда callFn вызывается как часть обратного вызова, такого как Array.forEach, в результате чего три аргумента передаются на каждой итерации.

fns.forEach(callFn); 

В вашем случае, ни одна из функций внутри fns не используют аргументы, которые передаются, но за кулисами они называются так:

fns[0].call(0, fns) 

Так this равен индексу элемента (т.е. Number(0)) и arguments[0] равен массиву функций. Острый наблюдатель, возможно, заметил, что значение элемента падает между трещинами, хотя на него все еще можно ссылаться, используя arguments[0][this] или, альтернативно, arguments.callee (устаревший).

+0

Бинго, вот и все! Большое спасибо. – georg

+0

Хорошее продолжение обсуждения аргументов, но почему «вызывающе»? Что не так просто с аргументами [0] [this] '? – georg

+0

@ thg435 Это хороший момент; что будет работать в этом случае :) обновлено, спасибо! –

5
for (var i = 0, len = fns.length; i < len; i++) { 
    fns[i].call(); 
}; 

Работает fiddle.

Используйте Function.prototype.call и Function.prototype.apply, как указано выше, для вызова функции. С помощью call и apply вы можете передать область выполнения и arguments на вызов функции. Если вам это не нужно, вы можете просто сделать:

for (var i = 0, len = fns.length; i < len; i++) { 
     fns[i](); 
}; 

О коде:

Array.prototype.map это функция, которая принимает callback и scope as parameters. В первых двух примерах вы используете анонимную функцию как callback, и вы вызываете автоматически переданный ей параметр Array.prototype.map. Для расширения ваш код эквивалентен этому:

function single(element) { 
    element(); 
}; 
fns.map(single); 

Таким образом, вышесказанное совершенно верно. Следуя тому же принципу, используя Function.call.call, вы вызываете функцию f, переданную как параметр по карте.

Но в вашем третьем примере вы заставляете прямой call через Function.call.prototype.call, однако в этом случае f больше не получает передается в качестве параметра, который означает, что ваш Function.call.call будет пытаться вызвать undefined, следовательно, вы получите TypeError. Когда вы ставите Function.call.call внутри map(), вы находитесь NOT, передавая callback в качестве аргумента.

call будет немедленно. Function.call.call - это то же самое, что и Function.call.call(), или Function.call.call(undefined), которое будет немедленно оценено при использовании так же, как в третьем примере.

+3

ОП, скорее всего, уже все это знает. Вопрос спрашивает * почему *, а не * как *. – Jon

+0

Я фактически писал то же самое за это время! +1 – Jon

3

TypeError происходит потому, что Function.prototype.call внутренне вызывает this (с данным контекстом и параметрами, но это не важно в обсуждении).

Давайте перепишем рабочую

fns.map(function (f) { Function.call.call(f) }) 

как эквивалентное

fns.map(function (f) { 
    var invoker = Function.call; 
    invoker.call(f); 
}) 

Теперь очевидно, что invoker вызывается с f как this. Когда он внутренне пытается вызвать this, в свою очередь ваша функция вызывается, как и ожидалось.

Теперь посмотрите на это:

fns.map(Function.call.call); 

и эквивалентная форма

fns.map(function (f) { 
    var invoker = Function.call; 
    invoker.call(); 
}) 

Это должно быть очевидно, что здесь, когда invoker вызывается this является undefined; поэтому он не может быть вызван сам, и это приводит к возникновению TypeError.

Смежные вопросы