Orlok: Javascript und css-Selectoren

Beitrag lesen

Hallo Rolf

zu diesem Code hätte ich Fragen.

var figcaptions = [].slice.call(document.querySelectorAll('.gallery figcaption'));

var contents = figcaptions.map(function(figcaption) {
  return figcaption.textContent;
});
  1. Warum [].slice und nicht Array.prototype.slice? Für [].slice muss die JS Engine ein Dummy-Array erzeugen. Wegwerfobjekte belasten den Garbage Collector unnötig. Oder sind die JITs so schlau, dass sie das erkennen?

Du hast recht, man könnte hier Array.prototype.slice genau so gut oder besser auch ohne den Umweg über Dummy-Array und Prototypen-Kette aufrufen.

Gäbe es Array.from (gegebenenfalls mit Polyfill) nicht, würde ich der von dir genannten Variante wohl den Vorzug geben.

Allerdings kaum aus Gründen der Performance, denn zum einen gehe ich – auch wenn ich gerade keine Quelle zitieren kann – in der Tat davon aus, dass so etwas wie [].slice.call von den meisten Engines heute wegoptimiert wird, und zum anderen, selbst wenn nicht, würde das in den allermeisten Fällen wohl im Grundrauschen untergehen.

Der insgesamt relevantere Aspekt ist diesbezüglich wohl eher die Lesbarkeit des Codes. Zwar ist [].slice.call(indexedCollection) ein sehr häufig verwendetes Muster – und in diesem Bereich etwas erfahreneren Entwicklern und Entwicklerinnen in der Regel gut bekannt, aber besser lesbar ist doch die direkte Referenzierung.

2. Warum überhaupt slice? Das macht ja einfach nur eine Kopie. Die NodeList ist zwar live, aber da JS single-threaded ist, sollte sie sich während des map Durchlaufs nicht ändern. Kann man es nicht direkt so schreiben?

var contents = Array.prototype.map.call(
    document.querySelectorAll('.gallery figcaption'),
    function(figcaption) { return figcaption.textContent; }
);

Ja, gut gesehen! So kann man es natürlich auch schreiben.

Die Methode Array.prototype.map ist generisch, das heißt, das Kontextobjekt muss kein Array sein. Das kann auch eine NodeList, eine HTMLCollection, ein arguments-Objekt oder ein korrekt präpariertes planes Objekt sein.

Wichtig ist nur das Vorhandensein einer length-Eigenschaft und entsprechender Indizes als Eigenschaftsnamen. Solange hier lediglich abgebildet und nicht innerhalb der Rückruffunktion das DOM verändert wird, ist das unproblematisch.

EDIT: Bei querySelectorAll übrigens ohnehin, denn die zurückgegebene NodeList ist AFAIS statisch, und nicht live.

  1. Würde etwas dagegen sprechen, NodeList zu augmentieren? Sowas habe ich heute noch im Büro eingebaut, ich fand es praktisch, node.querySelectorAll.map(...) schreiben zu können.

Das ist wohl nur von Fall zu Fall zu beantworten.

Allgemein ist es keine gute Idee, mit anderen Programmen gemeinsam genutzte Objekte zu verändern, es sei denn, es geht darum standardisierte Funktionen nachzubauen, die von der konkreten Hostumgebung nicht unterstützt werden.

Das Problem ist halt, dass möglicherweise zwei Programme denselben Eigenschaftsnamen nutzen möchten, aber eine (womöglich leicht) unterschiedliche Implementierung im Sinn haben, zum Beispiel f(item, index) versus f(index, item).

Wenn es keine anderen Programme gibt, nicht zu erwarten ist, dass welche hinzukommen, oder man davon ausgehen kann, die Sache im Auge zu behalten, dann spricht nichts dagegen. Also wenn… ;-)

Und bevor man den .map Polyfill aus der MDN abschreibt - kann man einfach das hier machen?

NodeList.prototype.map = NodeList.prototype.map || Array.prototype.map;

Klar. :-) Ich sehe keinen Grund, weshalb das nicht funktionieren sollte. Das Polyfill bräuchte man ja nur dann, wenn Array.prototype.map nicht unterstützt wird, was heute wohl kaum noch vorkommen dürfte.

Viele Grüße,

Orlok