2010-10-15 16 views
22

Underscore.js verwenden, kann ich folgendes schreiben, die 42 zurückgibt:Underscore.js: wie Kette benutzerdefinierte Funktionen

_([42, 43]).chain() 
    .first() 
    .value() 

I benutzerdefinierte Funktion haben, die nicht Teil von Underscore.js double() genannt:

function double(value) { return value * 2; }; 

Ich möchte diese Funktion in einer Underscore-Kette aufrufen können, als wäre es Teil von Underscore. Ich möchte folgende schreiben, die ich mag würde 84 zurückzukehren:

_([42, 43]).chain() 
    .first() 
    .double() 
    .value() 

Dies kann nicht funktionieren, da Unders nicht double() definieren. Ich konnte tap() wie in verwenden:

_([42, 43]).chain() 
    .first() 
    .tap(double) 
    .value() 

Dies ist gültig, aber tap wendet die Funktion auf ihr Argument und gibt das Argument, nicht das Ergebnis der Funktion. So sieht es für mich aus, als würde ich eine Art von tap benötigen, die das Ergebnis der auf ihr Argument angewandten Funktion zurückgibt. Gibt es so etwas in Underscore.js? Fehle ich etwas sehr offensichtlich?

+4

Seien Sie sich bewusst, dass "double" ein [* zukünftiges reserviertes Wort *] (http://bclary.com/2004/11/07/#a-7.5.3) ist und Implementierungen einen 'SyntaxError' auslösen können, wenn es als Identifikator – CMS

+0

Alessandro, hast du schon herausgefunden, dass ich dir die einzig richtige Antwort auf diese Frage gegeben habe? Was ich ausgebe, verschmutzt den Unterstrich-Namensraum überhaupt nicht. Die Bibliothek "underscore.js" hat genau das vorausgesagt, wonach Sie gefragt haben, und genau dafür eine Funktion eingebaut. –

+0

Underscore hat jetzt sowohl 'tap' (führt eine Funktion zum Ändern jedes Elements aus) als auch' map' (führt eine Funktion aus, die ein neues Element zurückgibt). – CMircea

Antwort

23

keine tap zu finden, den Wert wieder durch die Funktion zurückgibt, läuft, definiere ich eine, die ich take und _ hinzufügen:

_.mixin({take: function(obj, interceptor) { 
    return interceptor(obj); 
}}); 

Ich habe dann unter der Annahme:

function double(value) { return value * 2; }; 

kann ich schreiben:

_([42, 43]).chain() 
    .first()    // 42 
    .take(double)  // Applies double to 42 
    .value()    // 84 

Sie können take als map auf Objekte anstelle von Listen betrachten. Willst du damit experimentieren? Siehe hierzu example on jsFiddle.

+1

'apply' könnte ein besserer Name sein –

+4

@Gabe, ich dachte, es' apply' zu nennen, aber der Name ist bereits auf Funktionen definiert, und es hat eine andere Semantik: 'f.apply (obj, argArray)' statt 'obj.take (f, argArray)'. Sie glauben nicht, dass dies Verwirrung sein könnte? (Ehrlich gesagt, da ich selbst nicht überzeugt bin.) – avernet

+1

Für diejenigen, die suchen, ist 'tap' jetzt Teil des Unterstrichkerns. –

5

So haben Sie eine benutzerdefinierte Funktion:

function double(value) { return value * 2; } 

Sie können mixin verwenden Unders zu erweitern damit:

_.mixin({ double:double }); 

Jetzt können Sie Ihre Funktion aus dem Objekt Unders rufen _:

_.double(42); // 84 

und aus dem umgebrochenen Objekt, das vonzurückgegeben wird:

_([42, 43]).chain() 
    .first() 
    .double() // double made it onto the wrapped object too 
    .value(); // 84 
+0

ja, das ist eine Möglichkeit, es zu tun. Etwas, was ich nicht mag, ist, dass es "_" verschmutzt. I.e. Dies ist nicht praktikabel, wenn Sie überall in Ihrem Code eine Verkettung verwenden und daher viele dieser Funktionen haben. – avernet

+0

Ich schlug eine Alternative vor, die erfordert, dass ich nur eine Funktion zu _ _ hinzufüge - siehe die Antwort auf meine eigene Frage. Ich werde damit experimentieren und sehen, wie gut das in der Praxis funktioniert. – avernet

-1

Funktioniert map dafür?

_([42, 43]).chain() 
    .first() 
    .map(double) 
    .value() 

bearbeiten

aus der Dokumentation, es sieht aus wie map nur, wenn Sie es vor dem Aufruf von first platzieren funktionieren würde:

_([42, 43]).chain() 
    .map(double) 
    .first() 
    .value() 
+0

rechts, 'map' schneidet nicht ab, da es nur auf der Liste funktioniert. Hier brauche ich eine Art "Karte", die an einzelnen "Elementen" funktioniert. – avernet

+0

@avernet Sie könnten einfach Präfix und Postfix-Karte mit zuerst. – arcseldon

-1

Mit compose ist eine andere Art und Weise mit der Umgang Situation, aber ich denke, das Hinzufügen einer Funktion wie takeas I suggested earlier ist eine bessere Lösung. Noch hier ist, wie der Code wie mit compose aussehen:

function double(value) { return value * 2; }; 

_.compose(
    double, 
    _.first, 
    _.bind(_.identity, _, [42, 43]) 
)(); 

Der Anfangswert versehen ist, durch eine Funktion, die den Wert zurückgibt (hier erfolgt durch currying identity) in und die Funktionen müssen aufgelistet werden muss, eine andere, die das Gegenteil von dem ist, was du mit einer Kette hast, die mir ziemlich unnatürlich erscheint.

+0

Ich stimme zu, dass dies viel hässlicher ist als die andere Lösung, die Sie gepostet haben. –

5

In Ordnung, ich bin frisch vom Lesen des unterstrichenen Quellcodes zum ersten Mal. Aber ich denke, Sie so etwas wie dies tun können:

function double(value) { return value * 2; }; 

var obj = _([42, 43]).addToWrapper({double:double}); 

obj.chain() 
    .first() 
    .double() 
    .value(); 

Die Syntax/Details nicht richtig sein könnte, aber der Kern Punkt ist: Wenn Sie _([42,43]) nennen, du rufst du als Funktion unterstreichen. Wenn Sie dies tun, instanziiert es ein neues Objekt und mischt dann die meisten Unterstreichungsfunktionen in das Objekt ein. Dann gibt es dieses Objekt an Sie zurück. Sie können dann Ihrem Objekt eigene Funktionen hinzufügen, und nichts davon belastet den Namespace "_".

So sah der Code von underscore.js für mich aus. Wenn ich falsch liege, würde ich gerne herausfinden, und hoffentlich wird jemand erklären warum.

EDIT: Ich habe tatsächlich Underscore.js seit etwa einem Monat stark verwendet, und ich habe mich ziemlich damit vertraut gemacht. Ich jetzt wissen es verhält sich wie ich hier sagte. Wenn Sie _ als Konstruktorfunktion aufrufen, erhalten Sie Ihren eigenen "Namespace" (nur ein Objekt) zurück, und Sie können mit addToWrapper() Dinge hinzufügen, die in Ihrem Namespace, aber nicht im "globalen" "_" angezeigt werden. Namensraum. Also ist das Feature, das der OP wollte, bereits eingebaut. (Und ich war wirklich beeindruckt von Unterstreichung, übrigens, es ist sehr, sehr schön gemacht).

+0

Charlie, ja, du könntest das tun. Ich hätte das in meiner Frage erwähnen sollen: Ich möchte es nicht tun, weil es den '_' Namensraum verschmutzen würde. (Stellen Sie sich vor, Sie hätten eine große Codebase mit Underscore, und jedes Mal, wenn Sie dies tun möchten, müssen Sie die Funktion Underscore hinzufügen.) – avernet

+2

@Alessandro Vernet - Nein, es wird den Namensraum nicht verschmutzen. Das ist die Schönheit davon. Wenn Sie _ ([42,43]) aufrufen, wird ein * brandneuer * Namespace nur für Sie zurückgegeben. Wenn Sie dann "double" hinzufügen, hat Ihr eigener Namespace doppelte, aber _ selbst nicht. –

+0

-1 _ ([42, 43]).addToWrapper liefert undefined –

0

Niemand antwortete tatsächlich wie gewünscht, also hier ist meine Antwort auf die Anfrage, die ich getestet habe. Bitte lesen Sie den folgenden Hinweis in Bezug auf zuerst.

function doubleValue(x) { return x * 2; } 
var rt = _.chain([42,43]) 
     .first(1) 
     .map(doubleValue) 
     .value(); 
console.log(rt); // prints out [84] 

// Beachten Sie, dass es wichtig ist, .First zu sagen,(), wie viele Elemente, die Sie wollen, sonst können Sie sonst ein leeres Array erhalten. Denken Sie zuerst daran, wie ich die erste Anzahl von Elementen WILL, also.erstes (2) zurückkehrt [84], [86] usw.

+0

Dies ist v.close, aber es fehlt ein zusätzlicher Aufruf von first(), um das Ergebnis aus seinem Array auszupacken. – arcseldon

+0

Sie haben den Code eindeutig nicht gelesen, es ist Array nicht ein einzelner Wert, der zurückgibt, wenn Sie nur .first (1) angeben, die für einen Wert war. Ich denke, du musst lesen, was Leute schreiben, bevor du dumme Kommentare hinzufügst. –

+0

Bitte, keine Notwendigkeit für die Emotion :). Das OP fragte nach einem Wert, nicht nach einem Singleton-Array oder einem Array mit mehreren Werten. Ja, _.first ist nur ein Alias ​​für andere Sprachen wie Haskell oder JS-Bibliotheken wie Ramda. Sicher, wenn die Frage auf N Werte erweitert würde, würde dies gelten. Auch zuerst (2) gibt [84, 86] (nach dem Mapping) und nicht [84] [86] zurück, wie Sie in der Anmerkung angegeben haben. Würde Ramda empfehlen - die Lösung wird einfach R.pipe (R.head, R.multiply (2)) ([42, 43]); Oder wenn du tun möchtest, was du illustriertest: R.pipe (R.take (2), R.map (R.multiply (2))) ([42, 43]) Viel Glück – arcseldon

0

Viele Möglichkeiten, um dies leicht zu erreichen, hier ist eine solche Lösung:

_.chain([42,43]) 
    .first(1) 
    .map(double) 
    .first() 
    .value(); 
    // 84 

Allerdings würde ich empfehlen Ramda dann mit Auto-Curry mit und Rohr/compose sind diese Arten von Aufgaben trivial:

R.pipe(R.head, R.multiply(2))([42, 43]); // 84 

equivalent to: 

R.compose(R.multiply(2), R.head)([42, 43]); // 84 

Wenn Sie die Lösung erweitern wollte so nehmen, sagen die ersten 2 Elemente, anstelle eines einzelnen Wertes, vom OP angefordert, dann:

R.pipe(R.take(2), R.map(R.multiply(2)))([42, 43]) // [84, 86] 

In Ramda ist R.compose jedoch bevorzugt. So oder so, für triviale Aufgaben wie diese ist es eher bequem und einfach zu bedienen.

0

Sieht aus wie lodash umgesetzt hat genau das, was Sie suchen:

_.thru(value, interceptor) 

aus der Dokumentation:

Diese Methode wie _.tap Ausnahme ist, dass sie das Ergebnis der Interceptor kehrt

https://lodash.com/docs#thru

Verwandte Themen