JavaScript/Objekte/Array/fill

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Die von Array.prototype an alle Arrays vererbte Methode fill kann dazu verwendet werden, einem Teil oder auch allen Elementen eines Arrays den gleichen Wert zuzuweisen. Da es sich bei fill um eine generische Methode handelt, kann sie auch in Kontext von Objekten aufgerufen werden, bei denen es sich nicht um Arrays handelt.



Syntax

Array.prototype.fill(value[, start[, end]])


Attribute
Writable true
Enumerable false
Configurable true


Prototyp


Eigenschaften


Bedeutung

Die Methode fill besitzt drei Parameter, von denen die letzten beiden optional sind. Der erste Parameter spezifiziert den Wert, der für die Elemente des Arrays gesetzt werden soll. Der zweite und dritte Parameter bestimmen den Start- und Endpunkt der Befüllung. Der Rückgabewert der Methode ist das Array auf dem sie aufgerufen wurde.


Beispiel
const array = [2, 3, 5];

console.log(array.fill(7)); // [7, 7, 7]

console.log(Array(3).fill(5)); // [5, 5, 5]


Wird die Methode wie in dem Beispiel oben mit nur einem Argument aufgerufen, dann wird allen Elementen der übergebene Wert zugewiesen, da der Standardwert für den zweiten Parameter die erste Indexposition ist und der Standardwert für den dritten Parameter der Wert der Eigenschaft length des Objektes, auf dem die Methode aufgerufen wurde.


Beispiel
const array = ['Hypnos', 'Momos', 'Keren'];

const filled = array.fill('Nemesis', 1);

console.log(filled); // [Hypnos, Nemesis, Nemesis]


Entsprechend wird bei der Übergabe von zwei Argumenten an die Methode allen Elementen, beginnend mit dem angegebenen Startindex bis zum Ende des Arrays, der als erstes Argument übergebene Wert zugewiesen.


Beispiel
const array = ['Gaia', 'Tartaros', 'Nyx', 'Eros'];

const filled = array.fill('Chaos', 1, 3);

console.log(filled); // [Gaia, Chaos, Chaos, Eros]


Wird jedoch auch ein drittes Argument übergeben und ist der angegebene Index kleiner als der Wert der Eigenschaft length des Objektes, dann werden die Elemente nur bis zu dem Index befüllt, der Eins kleiner ist als der durch das dritte Argument bestimmte Endpunkt.


Beispiel
const array = [11, 13, 17, 19];

console.log(array.length); // 4

console.log(array.fill(23, 2, 5)); // [11, 13, 23, 23]


Ist der als drittes Argument an die Methode fill übergebene Wert hingegen größer als der Wert der Eigenschaft length des Objektes, auf dem die Methode aufgerufen wurde, dann werden alle Elemente bis zum letzten Index mit dem Wert befüllt.


Beispiel
const array = ['Tethys', 'Rhea', 'Iapetos'];

console.log(array.fill('Titan', 1, 1)); // [Tethys, Rhea, Iapetos]

console.log([1, 2, 3].fill(8, 2, 1)); // [1, 2, 3]


Entspricht der als zweites Argument übergebene Index dem Wert der als Endpunkt bestimmt wurde, dann wird keinem Element der angegebene Wert zugewiesen. Das Gleiche gilt in dem Fall, dass der übergebene Wert für den Startindex größer ist als der Wert, der das Ende der Befüllung markiert.


Beispiel
const array = [29, 31, 37].fill( );

for (let value of array) {
  console.log(value); // 3x undefined
}


Wird die Methode fill ohne Argumente aufgerufen, dann produziert dies keinen Fehler. Da formale Parameter einer Funktion, wenn kein Argument für den Parameter übergeben wurde, automatisch mit dem Standardwert undefined initialisiert werden, wird hier stattdessen entsprechend für jedes Element dieser Wert gesetzt.

Relative Werte für Start und Ende

Ist der über den zweiten Funktionsparameter spezifizierte Wert für den Startindex kleiner als Null, dann wird dieser intern zunächst mit dem Wert der Eigenschaft length addiert. Ist das Ergebnis dieser Addition größer als Null, dann wird das Ergebnis als relativer Startpunkt für die Befüllung verwendet, sonst wird 0 als Startindex gesetzt.


Beispiel
const array = ['Pontos', 'Ourea', 'Uranos'];

console.log(array.length); // 3

console.log(array.fill('Gaia', -1)); // [Pontos, Ourea, Gaia]


Das Array in dem Beispiel oben enthält drei Elemente, was demnach auch der Wert der Eigenschaft length dieses Arrays ist. Beim Aufruf der Methode fill wird nun als Startindex der Wert -1 übergeben und dieser wird mit der Länge des Arrays zu dem Wert 2 addiert. Da dieser Wert größer ist als 0, wird dem letzten Element des Arrays mit dem Index 2 der übergebene String Gaia zugewiesen.


Beispiel
const array = [3, 5, 7];

console.log(array.length); // 3

console.log(array.fill(11, -4)); // [11, 11, 11]


Wird wie in diesem Beispiel für den Startindex ein Wert an die Methode übergeben, der kleiner ist als Null, und ergibt die Addition dieses Wertes mit dem Wert der Eigenschaft length einen Wert, der ebenfalls kleiner ist als Null, dann wird die erste Indexposition des Arrays als Startwert verwendet.


Beispiel
const array = ['Apate', 'Geras', 'Eris'];

console.log(array.length); // 3

console.log(array.fill('Nyx', 0, -2)); // [Nyx, Geras, Eris]


Mit dem Wert des dritten Parameters der Methode wird genauso verfahren wie mit dem Parameter für den Startindex. Das heißt, wenn der übergebene Wert kleiner als Null ist, dann wird er zu dem Wert der Eigenschaft length addiert. Ist das Ergebnis der Addition größer als Null, wird der berechnete Wert als Endpunkt verwendet, sonst Null. Entsprechend wird wie in dem Beispiel oben bei einer Länge von 3 und einem angegebenen Wert von -2 der Index 1 als Endpunkt verwendet und nur dem Element mit dem Index 0 der dazu bestimmte Wert zugewiesen.


Beispiel
const array = [41, 43, 47, 53];

console.log(array.length); // 4

console.log(array.fill(61, -5, -2)); // [61, 61, 47, 53]


Die Berechnung von relativem Start- und Endpunkt erfolgt dabei unabhängig voneinander. Werden wie in dem Beispiel oben sowohl für den zweiten als auch für den dritten Parameter negative Werte übergeben, dann wird zunächst der Startindex berechnet. Da hier die Addtion des als zweites Argument übergebenen Wertes mit der Länge des Arrays einen negativen Wert ergibt, nämlich -1, wird zunächst der Startindex auf 0 gesetzt. Die folgende Addition des für den Endpunkt übergebenen Wertes mit der Länge des Arrays ergibt den Wert 2, weshalb hier im Ergebnis nur den ersten beiden Elementen mit den Indizes 0 und 1 der Wert 61 zugewiesen wird.

Typumwandlungen

Wird der Methode bei ihrem Aufruf als zweites oder drittes Argument ein Wert vom Datentyp Number übergeben, bei dem es sich nicht um eine Ganzzahl handelt, dann wird kein Type Error geworfen, sondern wie sonst auch das Ergebnis der abstrakten Operation ToInteger verwendet, die grundsätzlich auf dem übergebenen Wert ausgeführt wird.


Beispiel
const array = ['Nereus', 'Thaumas'];

console.log(array.fill('Pontos', NaN, 1)); // [Pontos, Thaumas]


Da nach den Regeln von ToInteger der Wert NaN durch 0 ersetzt wird und als Endpunkt in dem Beispiel oben der Wert 1 bestimmt wurde, wird hier entsprechend nur der Wert an der ersten Indexposition ersetzt. Anders verhält es sich hingegen bei dem Wert Infinity, der von ToInteger unverändert zurückgegeben wird.


Beispiel
console.log([11, 17, 23].fill(5, Infinity)); // [11, 17, 23]

const array = ['Pan', 'Zelos', 'Tyche'];

console.log(array.fill('Iris', -Infinity)); // [Iris, Iris, Iris]


Wird der Methode fill der Wert Infinity übergeben, unabhängig davon, ob als Wert für den Start oder das Ende der Befüllung, dann gelten hier die im ersten Abschnitt beschriebenen Regeln. Das heißt, wenn etwa wie in dem Beispiel oben der Wert Infinity als Startindex angegeben wird, dann passiert nichts, da der Wert immer größer oder gleich dem Wert für den Endpunkt ist. Wird hingegen wie in der zweiten Variante des Beispiels die negative Unendlichkeit als Startwert spezifiziert, dann ergibt die Addition mit dem Wert der Eigenschaft length natürlich immer noch einen negativen Wert, sodass dieser wie beschrieben durch 0 ersetzt wird. Entsprechend werden hier im Beispiel alle Elemente des Arrays mit dem angegebenen Wert, also dem String Iris befüllt.


Beispiel
const array = ['Aither', 'Hemera'].fill('Typhoeos', 0, Infinity);

console.log(array); // [Typhoeos, Typhoeos]

console.log([31, 61, 71].fill(5, 0, -Infinity)); // [31, 61, 71]


Bei der Übergabe des Wertes Infinity für den dritten Parameter greifen wie gesehen ebenfalls die allgemeinen Regeln. Das heißt, wenn wie in der ersten Variante des Beispiels oben der angegebene Wert größer ist als der Wert der Eigenschaft length des Objektes, dann wird der Wert von length als Endpunkt gesetzt, sodass bezogen auf das Beispiel oben die Werte beider Elemente des Arrays ersetzt werden. In der zweiten Variante wird der Index der das Ende der Befüllung markiert schließlich auf 0 gesetzt, da die Addtion des Übergebenen Wertes mit dem Wert der Eigenschaft length immer noch einen negativen Wert ergibt. Entsprechend findet hier keine Befüllung mit dem angegebenen Wert statt.


Beispiel
const array = ['Okeanos', 'Themis', 'Kronos'];

console.log(array.fill('Kreios', 1.3)); // [Okeanos, Kreios, Kreios]

console.log([2, 3, 5].fill(7, -2.8)); // [2, 7, 7]


Handelt es sich bei dem übergebenen Wert vom Datentyp Number weder um den Wert NaN, noch um Infinity oder eine Ganzzahl, dann wird von ToInteger das Ergebnis der auf dem Absolutbetrag des Wertes ausgeführten Abrundungsfunktion zurückgegeben.


Hinweis:
Der absolute Betrag (abs) einer Zahl ist definiert als der Abstand zu Null, weshalb beispielsweise -5 und 5 jeweils den Absolutbetrag 5 ergeben. Es ist in diesem Zusammenhang also zu beachten, dass die Abrundungsfunktion (floor) hier immer auf dem absoluten Betrag des übergebenen Wertes durchgeführt wird und nicht auf dem Wert selbst, denn dies würde bei einer negativen Zahl natürlich ein anderes Ergebnis bringen.


So ist in der ersten Variante des Beispiels der Absolutbetrag des angegebenen Wertes für den Startindex die Zahl 1.3 und die nächste, nicht größere Ganzzahl ist 1, was dann auch der für den Startindex verwendete Wert ist. In der zweiten Variante ist der Absolutbetrag 2.8, was abgerundet 2 ergibt, aber da das Vorzeichen bei dem Rückgabewert von ToInteger nicht verändert wird, wird hier im Ergebnis die Zahl -2 zurückgegeben. Weil nun wie gesehen bei dem Versuch, einen positiven Indexwert zu erhalten, negative Werte mit dem Wert der Eigenschaft length addiert werden, der hier 3 ist, wird in der zweiten Variante des Beispiels schließlich ebenfalls der Wert 1 für den Startindex gesetzt.


Beispiel
const array = ['Keto', 'Eurybia'];

console.log(array.fill('Gaia', null, true)); // [Gaia, Eurybia]

console.log([1, 2, 3].fill(5, Symbol(0))); // Type Error


Ist der für den zweiten oder dritten Parameter übergebene Wert nicht vom Datentyp Number, dann wird nach den allgemeinen, in der abstrakten Operation ToNumber beschriebenen Regeln eine Typumwandlung durchgeführt, so dies möglich ist. Dabei wird zum Beispiel der primitive Wert null in die Zahl 0 und die boolsche Variable true in den Wert 1 konvertiert. Eine Umwandlung von einem Wert des Datentyps Symbol in eine Zahl ist allerdings nicht möglich, weshalb bei dem zweiten Aufruf der Methode fill in dem Beispiel oben ein Typfehler geworfen wird.


Beispiel
const number = new Number(2);

console.log(typeof number); // object

console.log([10, 20, 30].fill(50, 0, number)); // [50, 50, 30]


Unproblematisch ist es hingegen, wenn als zweites oder drittes Argument ein Objekt vom Typ Number (Wrapper Object) übergeben wird, da in diesem Fall automatisch der primitive Wert, der in der internen Eigenschaft [[NumberData]] des Objektes hinterlegt ist, ausgelesen wird. Allerdings wird auch im umgekehrten Fall, dass an einer Stelle an der ein Objekt erwartet, aber ein primitiver Wert vom Datentyp Number vorgefunden wird, automatisch eine entsprechende Konvertierung vorgenommen, weshalb numerische Werte grundsätzlich als Literal notiert und nicht durch den Konstruktor Number erzeugt werden sollten.

Generische Verwendung

Die Verwendung der Methode fill ist nicht auf exotische Objekte vom Typ Array beschränkt, sondern es können auch andere Objekttypen durch sie manipuliert werden. Der Aufruf der Methode fill im Kontext eines Objektes, bei dem es sich nicht um ein Array handelt, kann beispielsweise unter Verwendung der Methode call erfolgen, die von Function.prototype an fill vererbt wird. Dabei wird das Objekt, das als Wert der Funktionsvariable this der Methode verwendet werden soll, als erstes Argument übergeben. Die weiteren an call übergebenen Argumente werden beim Aufruf an die Methode weitergereicht.


Beispiel
function zeus (hera, hades, poseidon) {
  if (arguments.length > 3) {
    Array.prototype.fill.call(arguments, false, 3);
    return arguments[3];
  }
}

const value = zeus('Hera', 'Hades', 'Poseidon', true);

console.log(value); // false


So kann fill beispielsweise im Kontext des Objektes ausgeführt werden, welches über die Funktionsvariable arguments referenziert werden kann und das die beim Funktionsaufruf übergebenen Argumente enthält. Allerdings ist es grundsätzlich nicht empfehlenswert mit dem Objekt arguments selbst zu arbeiten, das heißt, man sollte dessen Inhalt bei Bedarf in ein richtiges Array kopieren, statt generische Arraymethoden im Kontext von arguments aufzurufen.


Beispiel
const object = {
  length : 3
};

console.log([ ].fill.call(object, true)[2]); // true


Wie das Beispiel oben zeigt, kann die Methode sogar auf planen Objekten aufgerufen werden. Verfügt das jeweilige Objekte jedoch über keine Eigenschaft length dann passiert nichts. Sonst werden wie oben zu sehen ist, auch nicht existierende Objekteigenschaften mit den Indizes als Schlüssel neu angelegt und ihnen wird der an fill übergebene Wert zugewiesen.


Beispiel
const typed = new Float32Array(16);

console.log(typeof typed.fill); // function

console.log(Array.prototype.fill == typed.fill); // false


Wird wie in dem Beispiel oben auf einem Typed Array die Methode fill aufgerufen, dann handelt es sich hierbei übrigens nicht um die Methode fill, welche von Array.prototype vererbt wird, sondern vielmehr um die Methode gleichen Namens, die durch das Prototypen-Objekt des jeweiligen Konstruktors vererbt wird, der das typisierte Array erzeugt hat. Hier im Beispiel handelt es sich also um die eigene Methode von Float32Array.prototype. Der Algorithmus der beiden Methoden ist davon abgesehen aber identisch, sodass die Arraymethode auch im Kontext des typisierten Arrays aufgerufen werden kann.

Eigene Eigenschaften

Die Methode fill besitzt zwei eigene Eigenschaften, nämlich name und length. Der Wert der Eigenschaft name ist die Zeichenkette fill, also der Name der Methode und die Werte der internen Attribute dieser Eigenschaft sind false für Writable, false für Enumerable und true für Configurable. Die Eigenschaft ist also standardmäßig schreibgeschützt, kann aber unter Verwendung von Methoden wie zum Beispiel defineProperty von Object verändert werden.


Beispiel
const fill = Array.prototype.fill;

console.log(Object.getOwnPropertyNames(fill)); // [length, name]

console.log(fill.name); // fill


Die Eigenschaft length der Methode fill hat den Wert 1, da die Funktion nur über einen formalen Parameter verfügt, welcher den für die Befüllung bestimmten Wert erwartet. Optionale Parameter werden bei der Berechnung der Länge einer Funktion ebenso wie Restparameter grundsätzlich nicht berücksichtigt.


Beispiel
const length = [ ].fill.length;

console.log(length); // 1


Die Werte der internen Attribute der Eigenschaft length sind dieselben wie bei der Eigenschaft name, also false für Writable, false für Enumerable und true für Configurable. Auch bei dieser Eigenschaft kann der Wert also nicht durch einfache Zuweisung (Assignment), sondern nur durch Definition verändert werden.

Beispiel für Polyfill

Da die Methode fill erst in der sechsten Edition der Sprache standardisiert wurde und sie zum Zeitpunkt der Lektüre dieses Artikels gegebenenfalls noch nicht von allen relevanten Ausführungsumgebungen unterstützt wird, kann es sinnvoll sein, ein Polyfill für die Methode bereitzustellen, das wie in dem folgenden Beispiel aussehen kann.


Beispiel
if (!Array.prototype.fill) {

  Array.prototype.fill = function fill (value) {
    if (this == null) {
      throw new TypeError('this is null or undefined');
    }
    var object = Object(this),
        length = object.length >>> 0,
        start = arguments[1] >> 0,
        key = start < 0
          ? Math.max(length + start, 0)
          : Math.min(start, length),
        argument = arguments[2],
        end = argument === undefined
          ? length
          : argument >> 0,
        limit = end < 0
          ? Math.max(length + end, 0)
          : Math.min(end, length);
    while (key < limit) {
      object[key] = value;
      key ++;
    }
    return object;
  };

}


Darüber hinaus kann der Stand der Unterstützung durch die wichtigsten Ausführungsumgebungen durch einen Blick auf die entsprechende Tabelle zur Kompatibilität von kangax in Erfahrung gebracht werden.


Hinweis:
Normalerweise ist es schlechte Praxis, an eingebauten Objekten Veränderungen vorzunehmen, da sich alle gegenwärtig oder zukünftig eingebundenen Programme innerhalb einer globalen Umgebung dieselben Objekte teilen. Die Manipulation dieser Objekte kann also zu Konflikten zwischen Programmen und mithin zu Fehlern führen. Die Bereitstellung von Polyfills für eventuell von der Ausführungsumgebung nicht unterstützte, standardkonforme Methoden wie in diesem Beispiel bildet jedoch eine Ausnahme von dieser Regel, denn die Verwendung von Sprachmitteln sollte grundsätzlich nicht davon abhängig gemacht werden, ob diese wirklich von allen Ausführungsumgebungen unterstützt werden, sondern allein davon, ob deren Verwendung für die Bewältigung der jeweiligen programmiertechnische Aufgabe die beste Wahl darstellt.


Schließlich könnte statt eines Polyfills auch ein Compiler verwendet werden, der moderne und noch nicht zufriedenstellend unterstützte Features der Sprache in eine kompatible Syntax übersetzt.

Weblinks

  • ECMA 2017 (7th Edition, ECMA-262 Draft): ToInteger
  • ECMA 2015 (6th Edition, ECMA-262): ToInteger