Създаване на масиви

Преди ECMAScript 6, имаше два основни начина за създаване на масиви с Array конструктор и масив синтаксис. И двата подхода изискват индивидуален списък с елементите на масива и които иначе са доста ограничени. Опциите за превръщане на масиво подобен обект (обект с цифрови индекси и length свойство) в масив също са доста ограничени и често изискват допълнителен код. За да направи нещата по-лесни, ECMAScript 6 добавя два нови метода за създаване на масиви: Array.of() и Array.from().

Метода Array.of()

Една от причините, поради които ECMAScript 6 добавя нови методи за създаване е да помогне на програмистите да избягват приумицата за създаване на масиви с Array конструктора. Конструктора new Array() всъщност се държи по различен начин в зависимост от вида и броя на аргументите, подадени към него. Например:

let items = new Array(1, 2);        // length is 2
console.log(items.length);          // 2
console.log(items[0]);              // 1
console.log(items[1]);              // 2

items = new Array(2);
console.log(items.length);          // 2
console.log(items[0]);              // undefined
console.log(items[1]);              // undefined

items = new Array(3, "2");
console.log(items.length);          // 2
console.log(items[0]);              // 3
console.log(items[1]);              // "2"

items = new Array("2");
console.log(items.length);          // 1
console.log(items[0]);              // "2"
			

Когато на Array конструктора е подадена една единствена цифрова стойност, length свойството на масива се определя с тази стойност. Ако се подаде една единствена не-цифрова стойност, тогава тази стойност става един единствен елемент в масива. Ако са подадени няколко стойности (цифрови или не), тогава тези стойности стават елементи на масива. Това поведение е едновременно объркващо и рисковано, тъй като не винаги можем да бъдем наясно с типа на данните, които се подават.

ECMAScript 6 въвежда Array.of() за да реши този проблем. Метода Array.of() работи по начин, подобен на Array конструктора. Единствената разлика е премахването на специалния случай по отношение на единичната цифрова стойност. Метода на Array.of() винаги създава масив, съдържащ неговите аргументи, независимо от броя или типа на аргументите. Ето няколко примера:

let items = Array.of(1, 2);         // length is 2
console.log(items.length);          // 2
console.log(items[0]);              // 1
console.log(items[1]);              // 2

items = Array.of(2);
console.log(items.length);          // 1
console.log(items[0]);              // 2

items = Array.of("2");
console.log(items.length);          // 1
console.log(items[0]);              // "2"
			

За да създадете масив с Array.of() метода, просто му подавате стойностите, които искате във вашия масив. Първият пример тук създава масив, съдържащ две числа, втория масив съдържа едно число, а последния масив съдържа един string. Това е подобно на използването на масив литерал, което означава, че можем да използваме масив литерал вместо Array.of() за локални масиви през по-голямата част от времето. Но ако някога ви се наложи да подадете Array конструктор във функция, тогава може би е по-добре да подадете Array.of() за да се осигури последователно поведение. Например:

function createArray(arrayCreator, value) {
    return arrayCreator(value);
}

let items = createArray(Array.of, value);
			

В този код, функцията createArray() приема arrayCreator функиця и стойност, за вмъкване в масива. След това можем да подадем Array.of, като първи аргумент на createArray() за създаване на нов масив. Би било опасно, директно подаване на Array, ако не може да се гарантира, че value няма да бъде номер.

info
Метода Array.of() не използва Symbol.species свойство (обсъдено в Глава 9) за да определи типа на върнатата стойност. Вместо това използва текущ конструктор (this вътре в of() метода) за да определи правилния тип данни, които да върне.

Метода Array.from()

Превръщането на не-масиви обекти в действителни масиви винаги е било тромаво в JavaScript. Например, ако имате arguments обект (който е масиво-подобен) и искате да го използвате, като масив, тогава първо трябва да го преобразувате. За да се превърне масиво-подобен обект в масив в ECMAScript 5 ще напишете функция, като тази:

function makeArray(arrayLike) {
    var result = [];

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

    return result;
}

function doSomething() {
    var args = makeArray(arguments);

    // use args
}
			

Този подход създава result масивa и копира всеки елемент от arguments в новия масив. Макар, че работи това е една прилична сума от код, за да се изпълни една сравнително проста операция. Програмистите, скоро открили начин, с който да се съкрати количеството код с помощта на метода за масиви slice(), използван върху масиво-подобни обекти:

function makeArray(arrayLike) {
    return Array.prototype.slice.call(arrayLike);
}

function doSomething() {
    var args = makeArray(arguments);

    // use args
}
			

Този код е функционално еквивалентен на предишния пример и работи, защото определя this стойността за slice() към масиво-подобен обект. Тъй като на slice() му трябват само цифрови индекси и length свойство за да функционира правилно, всеки масиво-подобен обект ще работи.

Въпреки, че това изисква по-малко писане, никак не е ясно, че Array.prototype.slice.call() означава “превръщане в масив.” За щастие ECMAScript 6 добавя метода Array.from(), като по разбираем начин за преобразуване на обекти в масиви.

Можем да подадем iterable или масиво-подобен обект, като първи аргумент и Array.from() ще върне масив. Ето един прост пример:

function doSomething() {
    var args = Array.from(arguments);

    // use args
}
			

Извикването на Array.from() създава нов масив на базата на елементите в arguments. Така че, args е инстанция на Array, който съдържа същите стойности в същите позиции, както arguments.

info
Array.from() също използва this за да определи типа на масива, който трябва да върне.

Mapping Conversion

Ако искате да отидете една крачка напред, можете да предоставите на Array.from() преобразуваща функция, като втори аргумент. Тази функция работи въху всяка стойност от масиво-подобния обект и я преобразува в някаква окончателната форма преди съхранение на резултата в съответния индекс на крайния масив. Например:

function translate() {
    return Array.from(arguments, (value) => value + 1);
}

let numbers = translate(1, 2, 3);

console.log(numbers);               // 2,3,4
			

Тука, Array.from() подава (value) => value + 1, като преобразуваща функция, която добавя едно за всеки елемент в масива преди съхранение на елементите. Ако преобразуващата функция е върху даден обект, можете по желание да подадете трети аргумент на Array.from(), който представлява стойността на this за преобразуващата функция:

let helper = {
    diff: 1,

    add(value) {
        return value + this.diff;
    }
};

function translate() {
    return Array.from(arguments, helper.add, helper);
}

let numbers = translate(1, 2, 3);

console.log(numbers);               // 2,3,4
			

Този пример използва helper.add() метод, като преобразуваща функция за преобразуването. Тъй като, helper.add() използва this.diff свойството, трябва да се предостави трети аргумент за Array.from() за определяне на стойността на this. Благодарение на третия аргумент, Array.from() може лесно да се справя с преобразуване на данни без да е необходимо да се използва bind() или уточняване на стойността на this по някакъв друг начин.

Използване на Iterables

Метода Array.from() работи, както с масиво-подобни обекти така и с iterables. Това означава, че метода може да превърне всеки обект с Symbol.iterator свойство в масив. Например:

let numbers = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
};

let numbers2 = Array.from(numbers, (value) => value + 1);

console.log(numbers2);              // 2,3,4
	    	

В този код, numbers обекта е iterable, така че може да се подаде директно към Array.from(), за преобразуване на неговите стойности в масив. Преобразуващата функция добавя едно към всеки брой, така полученият масив съдържа 2, 3 и 4, вместо 1, 2 и 3.

info
Ако един обект е едновременно масиво-подобен и iterable, Array.from() използва итератор за определяне на стойностите за преобразуване.

Нови методи за всички масиви

Продължавайки тенденцията от ECMAScript 5, ECMAScript 6 добавя няколко нови метода за масиви. Методите find() и findIndex() са предназначени за подпомагане на програмистите използващи масиви с някакви стойности, а fill() и copyWithin() са до голяма степен вдъхновени от използването за случаи с typed arrays, форма на масив въведена в ECMAScript 6, която използва само цифри.

Методите find() и findIndex()

Преди ECMAScript 5, търсенето през масиви е било тромаво, защото не е имало вградени методи затова. ECMAScript 5 добавя indexOf() и lastIndexOf(), които най-накрая позволяват на програмистите да търсят конкретни стойности вътре в масива. Като голямо подобрение, тези два метода са все още доста ограничени, защото можете да търсите само една стойност в даден момент. Например, ако искате да намерите първото четно число в поредица от числа, ще трябва да напишете свой собствен код за да го направите. ECMAScript 6 решава този проблем, чрез въвеждане на два нови метода: find() и findIndex().

Методите find() и findIndex() приемат два аргумента, функция за обратно извикване и не задължителната стойност за използване this вътре във функцията за обратно извикване. На функцията за обратно извикване е подаден елемент от масив, индекса на този елемент в масива и самия масив, същите аргументи, като за map() и forEach(). Обратното извикване трябва да върне true, ако дадената стойност съвпада с някои критерии, които сте задали. И двата, find() и findIndex() спират търсенето в масива при първото връщане на true от функцията за обратно извикване

Единствената разлика между тези методи е, че find() връща стойността, докато findIndex() връща индекса, на който е установена стойността. Ето един пример:

let numbers = [25, 30, 35, 40, 45];

console.log(numbers.find(n => n > 33));         // 35
console.log(numbers.findIndex(n => n > 33));    // 2
	    	

Този код използва, find() и findIndex() за намиране на първата стойност в numbers масива, която е по-голяма от 33. Извикването на find() връща 35, докато findIndex() връща 2, местоположението на 35 в umbers масива.

И двата, find() и findIndex() са полезни за намиране на елемент в масив, който съвпада с условието, а не със стойността. Ако искате само да намерите стойност, тогава indexOf() и lastIndexOf() са по-добър избор.

Метода fill()

Метода fill() запълва един или повече елементи в масив с конкретна стойност. Когато се подаде стойност, fill() презаписва всички стойности в масива с тази стойност. Например:

let numbers = [1, 2, 3, 4];

numbers.fill(1);

console.log(numbers.toString());    // 1,1,1,1
	    	

Тука, извикването на numbers.fill(1) променя всички стойности в numbers на 1. Ако искате да се променят само някои от елементите, а не всички от тях, може евентуално да включите индекси за начало и край, като например:

let numbers = [1, 2, 3, 4];

numbers.fill(1, 2);

console.log(numbers.toString());    // 1,2,1,1

numbers.fill(0, 1, 3);

console.log(numbers.toString());    // 1,0,0,1
	    	

В този пример, numbers.fill(1, 2) започва попълването на елементи от индекс 2. Индекса за край не е уточнен с трети аргумент, така че numbers.length се счита за краен индекс, което означава, че последните два елемента в numbers ще се запълнят с 1. Следващата операция, numbers.fill(0, 1, 3), запълва масив с елементи в индекси 1 и 2 със 0. Извикването на fill() с втори и трети аргумент ни дава възможност да запълним няколко елемента наведнъж без да се презаписва целия масив.

info
Ако индексите за начало или край са отрицателни, тогава тези стойности се добавят към дължината на масива, за да се определи крайното местоположение. Така например, начално местоположение от -1 дава array.length-1, като индекс, където array е масива върху, който fill() се извиква.

Метода copyWithin()

Метода copyWithin() е подобен на fill() в това, че променя няколко елемента в масив едновременно. Обаче, вместо да се посочва единична стойност за запълване, copyWithin() ви позволява да копирате стойност за запълване на елементите в масива от самия масив. За да постигнете това, трябва да подадете два аргумента към copyWithin(), индекса, от където метода трябва да започне запълването на стойности и индекса, от където започва копирането на стойности.

Например, за да копирате стойностите от първите два елемента в масива, върху последните два елемента в масива, може да го направите по следния начин:

let numbers = [1, 2, 3, 4];

//поставяне на стойности в масив, започвайки от индекс 2
//копиране на стойности в масив, започвайки от индекс 0
numbers.copyWithin(2, 0);

console.log(numbers.toString());    // 1,2,1,2
	    	

Този код запълва стойности в numbers започвайки от индекс 2, така че двата индекса 2 и 3 ще бъдат презаписани. Вторият аргумент на copyWithin() е 0, който показва началото на копиране на стойности от индекс 0 и да продължи, докато няма повече елементи за копиране вътре.

По подразбиране, copyWithin() винаги копира стойностите до края на масива, но можете да предоставите трети не задължителен аргумент, за ограничаване на елементите, които трябва да бъдат презаписани. Този трети аргумент е с изключващ краен индекс, в който копирането на стойности спира. Ето един пример:

let numbers = [1, 2, 3, 4];

//поставяне на стойности в масив, започвайки от индекс 2
//копиране на стойности в масив, започвайки от индекс 0
//спиране на копирането на стойности, в индекс 1
numbers.copyWithin(2, 0, 1);

console.log(numbers.toString());    // 1,2,1,4
	    	

В този пример, само стойността от индекс 0 се копира, защото избора за краен индекс е определен на 1. Последния елемент в масива остава не променен.

info
Както и при fill() метода, ако се подаде отрицателно число за всеки аргумент на copyWithin(), дължината на масива автоматично се добавя към тази стойност, за да се определи индекса за използване.

Случаи на употреба за fill() и copyWithin() може да не са очевидни за вас в този момент. Това е така, защото тези методи са възникнали първоначално за typed arrays и след това за съгласуваност са добавени за редовни масиви. Както ще научите в следващата част, ако използвате typed arrays за манипулиране на бита с пробитово изместване на редицата, тези методи са много полезни.

Typed Arrays

Typed arrays са масиви със специално предназначение, предназначени за работа с цифрови типове (не всички типове, както може да изглежда от името). Произхода на typed arrays може да бъде проследен обратно до WebGL, пристанище на Open GL ES 2.0 предназначен за работа в уеб-страници с <canvas> елемент. Typed arrays са създадени, като част от този порт, за осигуряване на бърза пробитова аритметика в JavaScript.

Аритметиката с числа в JavaScript е твърде бавна за WebGL, защото числата се съхраняват в 64-битов десетичен формат и се преобразуват в 32-битови цели числа когато е необходимо. Така че typed arrays са въведени за заобикаляне на това ограничение и осигуряване на по-добра производителност за аритметични операции. Концепцията е, че всяко едно число може да бъде третирано, като масив от битове и по този начин може да се използват познатите методи за масиви в JavaScript.

ECMAScript 6 прие typed arrays, като официална част на езика, за да се гарантира по-добра съвместимост в цялата JavaScript машина и оперативна съвместимост с JavaScript масивите. Докато версията на typed arrays в ECMAScript 6 не е точно същата, както версията на WebGL, те са достатъчно сходни, за да направи версията на ECMAScript 6 еволюция на версията на WebGL, а не по-различен подход.

Цифрови типове данни

JavaScript номерата се съхраняват в IEEE 754 формат, използвайки 64 бита за съхранение с плаваща запетая за представяне на броя. Този формат представлява integers (цели) и floats (десетични) числа в JavaScript, с преобразуване между двата формата, което се случва често, когато номерата се променят. Масивите позволяват съхранението и манипулирането на осем различни цифрови типа:

  1. Signed 8-bit integer (int8)
  2. Unsigned 8-bit integer (uint8)
  3. Signed 16-bit integer (int16)
  4. Unsigned 16-bit integer (uint16)
  5. Signed 32-bit integer (int32)
  6. Unsigned 32-bit integer (uint32)
  7. 32-bit float (float32)
  8. 64-bit float (float64)

Ако искате да представите int8 в JavaScript номер, вие ще загубите 56 бита. Тези битове може по-добре да се използват за съхранение на допълнителни int8 стойности или друг номер, който изисква по-малко от 56 бита. Използването на бита по-ефективно е един от случаите на употреба на typed arrays адрес.

Всички операции и обекти свързани с typed arrays са центрирани около осемте типа данни. За да ги използвате, обаче, ще трябва да създадете array buffer (буфер масив) за съхранение на данни.

info
В тази книга, аз ще се отнасям към тези типове със съкращенията, показани в скобите. Тези съкращения не се показват в действителния JavaScript код, те са просто обозначение за много по-дълги описания.

Буфер масив

В основата на всички typed arrays е буфер масива, който е място в паметта и съдържа определен брой байтове. Създаването на буфер масив е равносилно на извикване на malloc() в C за разпределяне на памет без да се уточнява, какво ще се съдържа в нея. Можете да създадете буфер масив с помощта на ArrayBuffer конструктора, както следва:

let buffer = new ArrayBuffer(10);   // allocate 10 bytes
	    		
	    	

Просто подавате броя байтове, които буфер масив трябва да съдържа, когато извикате конструктора. Това let изявление създава буфер масив с дължина 10 байта. След като е създаден буфер масива, можете да извлечете броя на байтовете в него с помощта на byteLength свойството:

let buffer = new ArrayBuffer(10);   // allocate 10 bytes
console.log(buffer.byteLength);     // 10
	    	

Можете също да използвате slice() метода за създаване на нов буфер масив, който съдържа част от съществуващ буфер масив. Метода slice() работи подобно на slice() метода за масиви , в който се подава начален и краен индекс, като аргументи и връща нова инстанция на ArrayBuffer съставена от елементите на оригинала. Например:

let buffer = new ArrayBuffer(10);   // allocate 10 bytes


let buffer2 = buffer.slice(4, 6);
console.log(buffer2.byteLength);    // 2	    		
	    	

В този код, buffer2 е създаден, чрез извличане на байтове в индексите 4 и 5. Точно, както при версията за масиви на този метод, втория аргумент за slice() е с изключващ краен индекс.

Разбира се, създаването само на място за съхранение не е много полезно, без да могат да се записват данни в него. За да направите това, ще трябва да създадете view.

info
Един буфер масив винаги представлява точния брой байтове, определени при неговото създаване. Можете да променяте данните съдържащи се в рамките на буфер масива, но не и големината на самия буфер масив.

Манипулиране на буфер масив с Views

Буфер масива представлява място в паметта и views е интерфейс, чрез който се манипулира тази памет. View работи в буфер масив или част от байтовете на буфер масива, като чете и пише данни в един от цифровите типове данни. Типа DataView е общ view, който позволява да се работи с всички осем типа цифрови данни.

За да използваме DataView, първо трябва да създадем инстанция на ArrayBuffer и след това да го използваме за да създадем new DataView. Ето един пример:

let buffer = new ArrayBuffer(10),
    view = new DataView(buffer);	
	    	

Обекта view в този пример, има достъп до целият 10 байтов buffer. Алтернативно можем да създадем view само за част от буфера. Просто предоставяме байт за отместване и евентуално, броя на байтовете, които да се включат в това отместване. Когато няма включени определен брой байтове, DataView ще мине от отместването до края на буфера по подразбиране. Например:

let buffer = new ArrayBuffer(10),
    view = new DataView(buffer, 5, 2);  // cover bytes 5 and 6
	    	

Тука, view действа само върху байтове в индекси 5 и 6. Този подход позволява да създавадете няколко views към същия буфер масив, което може да бъде полезно, ако искате да използвате само част от място в паметта за цялото приложение, вместо динамично разпределяне на пространството, когато е необходимо.

Извличане на View информация

Можете да извлечате информация от view, използвайки следните само за четене свойства.

  • buffer - буфер масив, с който view е обвързан
  • byteOffset - втори аргумент към DataView конструктора, ако е предвиден (0 е по подразбиране)
  • byteLength - трети аргумент към DataView конструктора, ако е предвиден (буфера има byteLength по подразбиране)

Използвайки тези свойства, можете да определите, къде точно view работи, например:

let buffer = new ArrayBuffer(10),
    view1 = new DataView(buffer),        // cover all bytes
    view2 = new DataView(buffer, 5, 2);  // cover bytes 5 and 6

console.log(view1.buffer === buffer);       // true
console.log(view2.buffer === buffer);       // true
console.log(view1.byteOffset);              // 0
console.log(view2.byteOffset);              // 5
console.log(view1.byteLength);              // 10
console.log(view2.byteLength);              // 2
	    	

Този код създава view1, който действа върху целия буфер масив и view2, който работи върху малък участък от буфер масива. Тези views са с еквивалентни буфер свойства, тъй като и двете работят върху същия буфер масив. The byteOffset и byteLength са различни за всяко view, обаче. Те отразяват само част от буфер масива, където всяко views работи.

Разбира се, четенето на информация от паметта не е много полезно, само по себе си. Трябва да се запишат данни във и да се чететат тези данни от паметта, за да се извлече някаква полза.

Четене и записване на данни

За всеки от осемте цифрови типове данни в JavaScript DataView прототипа има метод за запис на данни, както и метод за четене на данни от буфер масив. Имената на методите за всички, започват със “set” или “get”, последвани от съкращението на типа данни. Например, ето списък на методите за четене и записване, които могат да работят върху int8 и uint8 стойности:

  • getInt8(byteOffset, littleEndian) - Четене на int8 започвайки от byteOffset
  • setInt8(byteOffset, value, littleEndian) - Записване на int8 започвайки от byteOffset
  • getUint8(byteOffset, littleEndian) - Четене на uint8 започвайки от byteOffset
  • setUint8(byteOffset, value, littleEndian) - Записване на uint8 започвайки от byteOffset

Методa get приема два аргумента: byteOffset (байт за отместване) за четене от и не задължителна булева стойност, която посочва дали стойността следва да се чете, като little-endian (малък-къс). ( Little-endian означава, че най-маловажният бит е в байт 0, вместо в последния байт.) Метода set приема три аргумента: byteOffset да записва от, стойността за записване и не задължителна булева стойност, която посочва дали стойността трябва да се съхранява в little-endian формат.

Въпреки, че аз само показвам методите, които можете да използвате с 8-битови стойности, съществуват същите методи за работа с 16- и 32-битови стойности. Просто сменете 8 във всяко име с 16 или 32. Наред с всички тези целочислени методи, DataView също има следните методи за четене и записване на десетични числа:

  • getFloat32(byteOffset, littleEndian) - Четене на float32 започвайки от byteOffset
  • setFloat32(byteOffset, value, littleEndian) - Записване на float32 започвайки от byteOffset
  • getFloat64(byteOffset, littleEndian) - Четене на float64 започвайки от byteOffset
  • setFloat64(byteOffset, value, littleEndian) - Записване на float64 започвайки от byteOffset

За да видите методите "get" и "set" в действие, да разгледаме следния пример:

let buffer = new ArrayBuffer(2),
    view = new DataView(buffer);

view.setInt8(0, 5);
view.setInt8(1, -1);

console.log(view.getInt8(0));       // 5
console.log(view.getInt8(1));       // -1 
	    	

Този код използва дву-байтов буфер масив за съхраняване на две int8 стойности. Първата стойност се определя с отместване 0, а втората е с отместване 1, отразявайки факта, че всяка стойност обхваща пълен байт (8 бита). Тези стойности по-късно са извлечени от позициите си с метода getInt8(). Докато този пример използва int8 стойности, можете да използвате всеки от осемте цифрови типа с техните съответните методи.

Views са интересни, защото те ви позволяват да четете и пишете във всякакъв формат във всяка точка във времето, независимо от това, как се съхраняват данните преди това. Например записване на две int8 стойности и четенето на буфера с int16 метод работи добре, като в този пример:

let buffer = new ArrayBuffer(2),
    view = new DataView(buffer);

view.setInt8(0, 5);
view.setInt8(1, -1);

console.log(view.getInt16(0));      // 1535
console.log(view.getInt8(0));       // 5
console.log(view.getInt8(1));       // -1 
	    	

Извикването на view.getInt16(0) чете всички байтове във view и интерпретира тези байтове, като номера 1535. За да разберем защо това се случва, да разгледаме Фигура 10-1, която показва какво прави всеки setInt8() ред в буфер масива.

new ArrayBuffer(2)       0000000000000000

view.setInt8(0, 5);         0000010100000000

view.setInt8(1, -1);       0000010111111111

Буфер масива започва с 16 бита, които са нули. Записването на 5 към първия байт със setInt8 въвежда няколко 1-ци (в 8-битово представяне на 5, като 00000101). Записвайки -1 към втория байт определя всички битове в този байт към 1, което е второто допълнение за представянето на -1. След второто извикване на setInt8(), буфер масивa съдържа 16 бита и getInt16() чете тези битове като единo 16-битово цяло число, което е 1535 в decimal.

Обекта DataView е идеален за случай на употреба, които смесва различни типове данни по този начин. Въпреки това, ако използвате един специфичен тип данни, тогава тип-спецификацията на view за типа e по-добър избор.

Typed Arrays Are Views

В ECMAScript 6, typed arrays са всъщност специфичните за типа views за буфер масив. Вместо да се използва общ DataView обект, за да работи върху буфер масива можете да използвате обекти, които налагат специфични типове данни. Има осем специфични типа views, съответстващи на осемте типа цифрови данни плюс един допълнителен по избор за uint8 стойности.

Таблица 10-1 показва съкратен вариант на пълния списък на тип-спецификацията за view от раздел 22.2 на ECMAScript 6 спецификацията.

Constructor Name Element
Size
Description Equivalent
C Type
Int8Array 1 8-bit 2’s
complement
signed
integer
signed
char
Uint8Array 1 8-bit
unsigned
integer
unsigned
char
Uint8ClampedArray 1 8-bit
unsigned
integer
(clamped
conversion)
unsigned
char
Int16Array 2 16-bit 2’s
complement
signed
integer
short
Uint16Array 2 16-bit
unsigned
integer
unsigned
short
Int32Array 4 32-bit 2’s
complement
signed
integer
int
Uint32Array 4 32-bit
unsigned
integer
unsigned
int
Float32Array 4 32-bit IEEE
floating
point
float
Float64Array 8 64-bit IEEE
floating
point
double

В лявата колона са изброени typed arrays конструкторите, a другите колони описват данните, които всеки typed array може да съдържа. Uint8ClampedArray е също, като Uint8Array, освен в случаите когато стойностите са по-малки от 0 и по-големи от 255. В този случай Uint8ClampedArray ще преобразува стойности по-ниски от 0 на 0 (-1 ще стане 0, например) и стойности по-големи от 255 на 255 (300 ще стане 255, например).

Тyped arrays операциите работят само върху определен тип данни. Например, всички операции на Int8Array работят с int8 стойности. Размерът на елемент в typed arrays също зависи от типа на масива. Докато един елемент в Int8Array е дълъг един байт, Float64Array използва осем байта за елемент. За щастие, елементите са достъпни чрез цифрови индекси, точно както редовни масиви, което позволява да се избегнат до някъде неудобните повиквания на “set” и“get” методите на DataView.

Размер на елемент

Всеки typed array се състои от няколко елемента и размерът на елементите е броят на байтовете, които всеки елемент представлява. Тази стойност се съхранява в BYTES_PER_ELEMENT свойството на всеки конструктор и за всеки отделен случай, така че лесно можем да дадем заявка за размера на елемента.

console.log(UInt8Array.BYTES_PER_ELEMENT);    // 1
console.log(UInt16Array.BYTES_PER_ELEMENT);   // 2

let ints = new Int8Array(5);
console.log(ints.BYTES_PER_ELEMENT);          // 1
	  	

Създаване на специфични типове Views

Конструктора на typed arrays приема няколко типа аргументи, така че има няколко начина за създаване на typed array. Първо, можем да създадем нов typed array с подаване на същите аргументи, които DataView приема (buffer, незадължителен byteOffset и незадължителен byteLength). Например:

let buffer = new ArrayBuffer(10),
    view1 = new Int8Array(buffer),
    view2 = new Int8Array(buffer, 5, 2);

console.log(view1.buffer === buffer);       // true
console.log(view2.buffer === buffer);       // true
console.log(view1.byteOffset);              // 0
console.log(view2.byteOffset);              // 5
console.log(view1.byteLength);              // 10
console.log(view2.byteLength);              // 2
	  	

В този код, двете views са две UInt8Array инстанции, които използват buffer. И двата view1 и view2 имат същите buffer, byteOffset и byteLength свойства, които съществуват за DataView инстанцията. Преминаването към използване на typed array е лесно, където можете да използвате DataView толкова дълго, колкото работите само с един цифров тип.

Вторият начин за създаване на typed array е с подаване на номер към конструктора. Този номер представлява броя на елементите (не байтове) за разпределяне в масива. Конструктора ще създаде нов буфер с правилния брой байтове за представяне на редицата на елементите в масива и можем да получим достъп до броя на елементите в масива с помощта на свойството length. Например:

let ints = new Int16Array(2),
    floats = new Float32Array(5);

console.log(ints.byteLength);       // 4
console.log(ints.length);           // 2

console.log(floats.byteLength);     // 20
console.log(floats.length);         // 5
	  	

Създадения ints масив е с място за два елемента. Всяко 16-битово цяло число изисква два байта за стойност, така масива е разделен на четири байта. Създадения floats масив е с място за пет елемента, така броят на необходимите байтове е 20 (четири байта на елемент). И в двата случая се създава нов буфер и може да бъде достъпен, използвайки buffer свойството ако е необходимо.

worning
Ако не се подаде никакъв аргумент към конструктора на typed array, конструктора действа все едно, че му е подадена 0. Това създава typed array, който не може да побере никакви данни, защото 0 байта са разпределени на буфера.

Третият начин за създаване на typed array е да подадем един обект, като единствен аргумент към конструктора. Обекта може да бъде някой от следните:

  • Typed Array - всеки елемент се копира в нов елемент на нов typed array. Например, ако се подаде int8 на Int16Array конструктора, стойностите на int8 ще бъдат копирани в един int16 масив. Новият typed array има различен буфер масив от този, който му е подаден.
  • Iterable- итератора на обекта се извиква за да извлече елементите и да ги вмъкне в typed array. Конструктора ще хвърли грешка ако някой от елементите е невалиден за view типа.
  • Array- елементите на масива се копират в нов typed array. Конструктора ще хвърли грешка ако някой от елементите е невалиден за типа.
  • Array-Like Object (масиво-подобен обект)- държи се по същия начин, като array.

Във всеки то тези случаи, нов typed array се създава с данните от обекта източник. Това може да бъде особено полезно, когато искате да инициализирате typed array с някои стойности, като тези:

let ints1 = new Int16Array([25, 50]),
    ints2 = new Int32Array(ints1);

console.log(ints1.buffer === ints2.buffer);     // false

console.log(ints1.byteLength);      // 4
console.log(ints1.length);          // 2
console.log(ints1[0]);              // 25
console.log(ints1[1]);              // 50

console.log(ints2.byteLength);      // 8
console.log(ints2.length);          // 2
console.log(ints2[0]);              // 25
console.log(ints2[1]);              // 50
	  	

Този пример създава Int16Array и го инициализира с масив от две стойности. След това, се създава Int32Array и му се подава Int16Array. Стойностите 25 и 50 са копирани от ints1 в ints2, като двата typed arrays имат съвсем отделни буфери. Същите номера са представени в двата typed arrays, но ints2 има осем байта за представяне на данните, докато ints1 има само четири.

Прилики между Typed arrays и редовни масиви

Typed arrays и редовни масиви са сходни по няколко начина и както вече видяхте в тази глава, typed arrays могат да бъдат използвани, като редовни масиви в много ситуации. Например, можем да проверим, колко елемента са в typed array използвайки length свойство и можем да получим достъп до елементите директно с помощта на цифровите индекси. Например:

let ints = new Int16Array([25, 50]);

console.log(ints.length);          // 2
console.log(ints[0]);              // 25
console.log(ints[1]);              // 50

ints[0] = 1;
ints[1] = 2;

console.log(ints[0]);              // 1
console.log(ints[1]);              // 2
	  	

В този код, се създава нов Int16Array с два елемента. Елементите се четат и пишат, като се използват техните цифрови индекси и тези стойности автоматично се съхраняват и преобразуват в int16 стойности, като част от операцията. С това приликите не свършват обаче.

info
За разлика от редовните масиви, не можете да променяте размера на typed array използвайки length свойството. Length свойството не е достъпно за записване, така че всеки опит да го промените се игнорира в non-strict режим и хвърля грешка в strict режим.

Общи методи

Typed arrays включват голям брой методи, които са функционално еквивалентни на методите за редовни масиви. Можете да използвате следните методи за масиви върху typed array:

  • copyWithin() - копира стойностите за масив от самия масив, започвайки от целата до края на масива.
  • fill() - замества всички елементи в масива от началния до крайния индекс със статична стойност.
  • filter() - създава нов масив с всички елементи, които преминават теста изпълняван от тестващата функция.
  • find() - връща стойността на първия съвпадащ елемент от масива отговарящ на тестващата функция.
  • findIndex() - връща индекса на първия съвпадащ елемент от масива отговарящ на тестващата функция.
  • forEach() - изпълнява предоставената функция по-веднъж за всеки елемент в масива.
  • indexOf() - връща позицията при първата поява на определена стойност в масива или -1, ако не е намерена.
  • join() - свързва всички елементи на масива в string и връща този string.
  • keys() - връща нов масив iterator, който съдържа ключ за всеки индекс на масива.
  • lastIndexOf() - връща позицията при последната поява на определена стойност в масива или -1, ако не е намерена.
  • map() - създава нов масив с резултата от извикването на предоставената функция за всеки елемент в този масив.
  • reduce() - прилага акумулираща функция върху всяка стойност в масив от ляво на дясно, за да сведе масива до една единствена стойност.
  • reduceRight() - прилага акумулираща функция върху всяка стойност в масив от дясно на ляво, за да сведе масива до една единствена стойност.
  • reverse() - обръща реда на елементите в масив.
  • slice() - връща избраните елементи от масив в нов обект масив.
  • some() - тества дали някой елемент в масива преминава теста изпълняван от предоставената функция.
  • sort() - сортира елементите на масив на място и връща масив.
  • values() - връща нов Array Iterator обект, който съдържа стойностите за всеки индекс в масива.

Имайте в предвид, че докато всички тези методи действат също, както тези на Array.prototype, те не са абсолютно същите. Методите на typed arrays имат допълнителни проверки за безопасност на цифровия тип и когато се връща масив, ще се върне един typed array вместо редовен масив (поради Symbol.species). Ето един прост пример, който демонстрира разликата:

let ints = new Int16Array([25, 50]),
    mapped = ints.map(v => v * 2);

console.log(mapped.length);        // 2
console.log(mapped[0]);            // 50
console.log(mapped[1]);            // 100

console.log(mapped instanceof Int16Array);  // true
	   	

Този пример използва map() метод за създаване на нов масив базиран на стойности от ints. Преобразуващата функция удвоява всяка стойност в масива и връща нова в Int16Array.

The Same итератори

Typed arrays имат същите три итератора, както редовните масиви. Това са методите entries(), keys() и values(). Това означава, че позволяват използването на оператора spread и for-of цикъл с typed arrays, по същия начин, както бихте го направили с редовни масиви. Например:

let ints = new Int16Array([25, 50]),
    intsArray = [...ints];

console.log(intsArray instanceof Array);    // true
console.log(intsArray[0]);                  // 25
console.log(intsArray[1]);                  // 50
			

Този код създава нов масив intsArray съдържащ същите данни, както typed array ints. Както и при другите iterables, оператора spread е един лесен начин за превръщане на typed arrays в редовни масиви.

of() и from() методи

Накрая, всички typed arrays имат статичните методи of() и from(), които работят подобно на Array.of() и Array.from() методите. Разликата е, че методите за typed arrays връщат typed array вместо обикновен масив. Ето няколко примера, които използват тези методи, за да създадат typed array:

let ints = Int16Array.of(25, 50),
    floats = Float32Array.from([1.5, 2.5]);

console.log(ints instanceof Int16Array);        // true
console.log(floats instanceof Float32Array);    // true

console.log(ints.length);       // 2
console.log(ints[0]);           // 25
console.log(ints[1]);           // 50

console.log(floats.length);     // 2
console.log(floats[0]);         // 1.5
console.log(floats[1]);         // 2.5
			

Методите of() и from() в този пример, се използват за създаване на Int16Array и Float32Array. Тези методи гарантират, че typed arrays могат да бъдат създадени също толкова лесно, колкото редовните масиви.

Разлики между typed arrays и редовни масиви

Най-значимата разлика между typed arrays и редовни масиви е, че typed arrays не са редовни масиви. Това означава, че те не наследяват от Array и Array.isArray() връща false, когато се подаде на typed array. Например:

let ints = new Int16Array([25, 50]);

console.log(ints instanceof Array);     // false
console.log(Array.isArray(ints));       // false
			

Променливата ints е typed array, така че не е инстанция на Array и не може да се индентифицира, като масив. Това разграничение е важно, защото докато typed array и редовни масиви са сходни, има много начини, по които typed arrays се държат по различен начин.

Разлики в поведението

Докато редовните масиви могат да растат и свиват при взаимодействие с тях, typed arrays винаги остават със същия размер. Вие не може да присвоите стойност на несъществуващ цифров индекс в typed array, като можем с редовни масиви, тъй като typed array игнорира тази операция. Ето един пример:

let ints = new Int16Array([25, 50]);

console.log(ints.length);          // 2
console.log(ints[0]);              // 25
console.log(ints[1]);              // 50

ints[2] = 5;

console.log(ints.length);          // 2
console.log(ints[2]);              // undefined
			

Въпреки присвояването на 5 в цифров индекс 2, в този пример, ints масива не расте изобщо. Дължината остава същата и стойността се изхвърля.

Тyped arrays също имат проверки, за да се гарантира, че се използват само валидни типове данни. Нулата се използва на мястото на всички невалидни стойности. Например:

let ints = new Int16Array(["hi"]);

console.log(ints.length);       // 1
console.log(ints[0]);           // 0
			

Този код се опитва да използва стойността на string "hi" в Int16Array. Разбира се, strings са невалиден тип данни в typed arrays, така че се въвежда нула вместо стойността. Дължината на масива остава същата и въпреки, че ints[0] съществува, тя просто съдържа 0.

Същото ограничение се прилага за всички методи, които променят стойности в typed array. Например, ако функция се подаде към map(), който връща невалидна стойност за typed array, се използва нула за отговор:

let ints = new Int16Array([25, 50]),
    mapped = ints.map(v => "hi");

console.log(mapped.length);        // 2
console.log(mapped[0]);            // 0
console.log(mapped[1]);            // 0

console.log(mapped instanceof Int16Array);  // true
console.log(mapped instanceof Array);       // false
			

Тъй като, string стойността "hi" не е 16-битово цяло число, то се заменя с 0 в резултата на масива. Благодарение на това поведение за коригиране на грешки в typed arrays, не е нужно да се притесняваме за хвърляне на грешка, когато са налице невалидни данни, защото никога няма да има невалидни данни в масива.

Липсващи методи

Последната разлика между typed arrays и редовни масиви, е че в typed arrays липсват няколко метода, които са на разположение за редовни масиви. Следните методи не са на разположение за typed arrays:

  • concat() - обединява два или повече масива. Този метод не променя съществуващите масиви, а вместо това връща нов масив.
  • pop() - премахва последния елемент от масив и връща този елемент.
  • push() - добавя един или повече елементи в края на масивa и връща масив с новата дължина.
  • shift() - премахва първият елемент от масив и връща този елемент.
  • splice() - променя съдържанието на масив чрез премахване на съществуващите или с добавяне на нови елементи.
  • unshift() - добавя един или повече елементи в началото на масива и връща масив с новата дължина.

С изключение на concat(), другите методи могат да променят размера на масив. Typed arrays не могат да променят размера си, поради което тези методи не са достъпни за typed arrays. Метода concat() не е достъпен, тъй като не е ясно за резултата, какво ще конкатенира в два typed arrays (особено ако те се занимават с различни типове данни).

Допълнителни методи

Накрая, typed arrays имат два метода, които не присъстват в редовните масиви: set() и subarray(). Тези два метода са противоположни, set() позволява копиране на друг масив в съществуващ typed array, докато subarray() извлича част от съществуващ typed array в нов typed array.

Метода set() приема масив (било typed или редовен) и по избор отместване, с което да въвежда данните, ако не се подаде нищо, отместването по подразбиране е нула. Данните от масива-аргумент се копират в дестинацията на typed array, като същевременно се гарантира, че се използват само валидни типове данни. Ето един пример:

let ints = new Int16Array(4);

ints.set([25, 50]);
ints.set([75, 100], 2);

console.log(ints.toString());   // 25,50,75,100
	    	

Този код създава Int16Array с четири елемента. Първото извикване на set() копира две стойности за първия и втория елемент в масива. Второто извикване на set() използва отместване с 2 за да покаже, че стойностите трябва бъдат поставени в масива, започвайки от третия елемент.

Метода subarray() приема по избор начален и краен индекс (крайния индекс е изключващ, както в slice() метода) и връща нов typed array. Можете също така да пропуснете двата аргумента, за да създадете клонинг на typed array. Например:

let ints = new Int16Array([25, 50, 75, 100]),
    subints1 = ints.subarray(),
    subints2 = ints.subarray(2),
    subints3 = ints.subarray(1, 3);

console.log(subints1.toString());   // 25,50,75,100
console.log(subints2.toString());   // 75,100
console.log(subints3.toString());   // 50,75
	    	

Тука са три typed arrays, създадени от оригинала ints в този пример. Масива subints1 е клонинг на ints, който съдържа същата информация. Масива subints2 започва да копира данни от индекс 2 и съдържа само последните два елемента на масива (75 и 100). Масива subints3 съдържа елементите в средата на ints масива, тъй като subarray() се извиква с аргументите за начален и краен индекс.