2014-06-19 13 views
5

Pracuję nad ponownym zapisaniem kodu źródłowego dla wielu standardowych funkcji Underscore.js do pracuję nad moimi umiejętnościami JavaScript i trochę utknąłem z _.every/_.all. Wygląda na to, że w samej bibliotece funkcja jest zapisywana tylko przy użyciu istniejącej funkcji _.each, ale jestem zachęcany do napisania wersji przy użyciu mojej wersji _.reduce (która już zawiera moją wersję _.each). Podałem kod dla obu funkcji poniżej.Jak ponownie napisać _.every/_. All z Underscore.js za pomocą _.reduce (i _.each)

Pierwszy test moja _.every funkcji (patrz poniżej, jak również) nie jest taki, w którym wszystkie fałszywe wartości są przekazywane w użyciu funkcji _.identity (po prostu zwrócić wartość wprowadzoną jako argument) jako iterator:

Test:

it('fails for a collection of all-falsy results', function() { 
    expect(_.every([null, 0, undefined], _.identity)).to.equal(false); 
    }); 

mam kilka pytań, dlaczego mój _.every funkcja zawodzi pokazano powyżej testy, wraz z wieloma innymi badaniami (eg; zmieszanych prawda/fałsz, wartości nieokreślonych wartości, etc.):

-Gdy wywołujesz funkcję iteratora, czy muszę używać iterator.call lub iterator.apply? Jeśli tak, to jakich użyć i jak określić argumenty?

-Jakie korzyści polega na tym, że zamiast korzystać z _.reduce zamiast _.each, szczególnie, gdy biblioteka Underscore.js nie używa _.reduce?

-Dlaczego nie potrzebuje powrotu na miano dwukrotnie, raz przy wywołaniu funkcji _.reduce, a raz wewnątrz funkcji anonimowej określonym terminie _.reduce (Zastanawiałem się to również podczas budowania funkcji, które wykorzystują funkcję _.map)? Dla mnie wygląda na to, że zwracam wynik funkcji _.reduce, która już zwraca coś.

_.every:

_.every = function(collection, iterator) { 
    // TIP: Try re-using reduce() here. 
    return _.reduce(collection, function(allFound, item) { 
     return iterator(item) && allFound; 
    }, true); 
    }; 

_.each:

_.each = function(collection, iterator) { 
    // define spec for arrays 
    if (Array.isArray(collection)) { 
    for(var i = 0; i < collection.length; i++) { 
     iterator(collection[i], i, collection); 
    } 
    } 

    // define spec for objects 
    else { 
    for(var key in collection) { 
     iterator(collection[key], key, collection); 
    } 
    } 
}; 

_.reduce:

_.reduce = function(collection, iterator, accumulator) { 

    // add condition to set accumulator if no explicit starting value is given. 
    if (arguments.length < 3) { 
     accumulator = collection[0]; 
    } 

    _.each(collection, function(value) { 
     accumulator = iterator(accumulator, value); 
    }); 

    return accumulator; 
    }; 

Odpowiedz

4

test nie przechodzi, ponieważ nie jest powrocie false jak oczekiwano (chociaż zwraca wartość falsey).

_.every = function(collection, iterator) { 
    return _.reduce(collection, function(allFound, item) { 
     return iterator(item) && allFound; 
    }, true); 
}; 

Co się dzieje, gdy wrócisz iterator(item) && allFound jest to, że jeśli iterator(item) jest falsey (ale nie false), nie powróci false, ale wartość iterator(item). Aby to sprawdzić samodzielnie, otwórz REPL i wpisz undefined && true; wynikiem będzie undefined, a nie false.

Więc jeśli chcesz, aby to wyraźnie zwrócić false, a nie tylko wartość falsey, będziesz musiał przymusić go do boolean. Możesz wykonać albo Boolean(truthy_or_falsey_value) lub !!truthy_or_falsey_value.I zazwyczaj wolą te ostatnie, tak zmienić implementację wygląda następująco:

_.every = function(collection, iterator) { 
    return _.reduce(collection, function(allFound, item) { 
     return !!iterator(item) && allFound; 
    }, true); 
}; 

Twoje inne pytania:

Po wywołaniu funkcji iteracyjnej, muszę używać iterator.call lub iterator.apply? Jeśli tak, to jakich użyć i jak określić argumenty?

To zależy od tego, jaki jest Twój cel. call i apply są używane przede wszystkim wtedy, gdy chcesz kontrolować wartość słowa kluczowego this w treści funkcji. Niektóre z wbudowanych metod tablic JavaScript (takich jak Array.prototype.map i Array.prototype.filter) pobierają thisArg, co jest dostarczane do wywołania zwrotnego za pomocą call lub apply. Jeśli chodzi o różnicę między call i apply, to tylko w jaki sposób obsługiwane są te argumenty. Aby uzyskać więcej informacji, patrz this answer.

Jakie korzyści ma do korzystania reduce tutaj raczej niż tylko each, zwłaszcza gdy biblioteka underscore.js nie używa reduce?

Prawdopodobnie brak lub bardzo mało. Może występować różnica w wydajności, ale najlepszym sposobem, aby się tego dowiedzieć, jest profilowanie obu podejść.

Dlaczego powrót trzeba nazwać dwa razy, raz po wywołaniu funkcji _.reduce, a raz wewnątrz funkcji anonimowej określonym terminie _.reduce

Jeśli chcesz funkcję - dowolna funkcja - aby zwrócić wartość, musisz wywołać funkcję return z poziomu tej funkcji. Nie możesz oczekiwać, że zadzwonisz pod numer return z funkcji wewnętrznej i oczekujesz, że funkcja otaczająca w magiczny sposób zrozumie, że ma ona z kolei zwrócić wartość wywoływanej funkcji. Niektóre języki domyślnie zwracają wartość ostatniego wyrażenia w funkcji, jeśli return nie jest jawnie wywoływana, co jest wygodne lub mylące, w zależności od perspektywy. Jeśli masz doświadczenie z takim językiem (na przykład Ruby), wszystkie instrukcje return mogą wydawać ci się zbyt wygórowane.

Jako komentarz redakcyjny, uważam, że iterator jest złym wyborem nazwy dla funkcji testowania. W rzeczywistości nie jest to iteracja nad czymkolwiek (funkcja, do której jest argumentem, wykonuje jakąkolwiek iterację). Lepsza nazwa może być bardzo ogólna: callback lub cb. Termin "predicate" oznacza funkcję, która odwzorowuje wartość na true lub false, co jest moją preferowaną terminologią. Innym powszechnym wyborem jest po prostu test, ponieważ jest to przecież tylko funkcja, która wykonuje filtr binarny na swoim argumencie.