Създаване на arrays

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

Метода Array.of()

Една от причините, поради които ECMAScript 6 добавя нови методи за създаване е да помогне на програмистите да избягват приумицата за създаване на array с 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 свойството на array се определя с тази стойност. Ако се подаде една единствена не-цифрова стойност, тогава тази стойност става един единствен елемент в array. Ако са подадени няколко стойности (цифрови или не), тогава тези стойности стават елементи на array. Това поведение е едновременно объркващо и рисковано, тъй като не винаги можем да бъдем наясно с типа на данните, които се подават.

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

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 с Array.of() метода, просто му подавате стойностите, които искате във вашия array. Първият пример тук създава array, съдържащ две числа, втория array съдържа едно число, а последния array съдържа един string. Това е подобно на използването на array литерал, което означава, че можем да използваме array литерал вместо Array.of() за локални arrays през по-голямата част от времето. Но ако някога ви се наложи да подадете Array конструктор във функция, тогава може би е по-добре да подадете Array.of() за да се осигури последователно поведение. Например:

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

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

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

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

Метода Array.from()

Превръщането на non-arrays обекти в действителни arrays винаги е било тромаво в JavaScript. Например, ако имате arguments обект (който е масиво-подобен) и искате да го използвате, като array, тогава първо трябва да го преобразувате. За да се превърне масиво-подобен обект в array в 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 и копира всеки елемент от arguments в новия array. Макар, че работи това е една прилична сума от код, за да се изпълни една сравнително проста операция. Програмистите, скоро открили начин, с който да се съкрати количеството код с помощта на метода 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() означава “превръщане в array.” За щастие ECMAScript 6 добавя метода Array.from(), като по разбираем начин за преобразуване на обекти в arrays.

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

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

    // use args
}
			

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

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

Mapping Conversion

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

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 преди съхранение на елементите. Ако преобразуващата функция е върху даден обект, можете по желание да подадете трети аргумент на 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 свойство в array. Например:

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(), за преобразуване на неговите стойности в array. Преобразуващата функция добавя едно към всеки брой, така полученият array съдържа 2, 3 и 4, вместо 1, 2 и 3.

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

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

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

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

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

Методите find() и findIndex() приемат два аргумента, функция за обратно извикване и не задължителната стойност за използване this вътре във функцията за обратно извикване. На функцията за обратно извикване е подаден елемент от array, индекса на този елемент в array и самия array, същите аргументи, като за map() и forEach(). Обратното извикване трябва да върне true, ако дадената стойност съвпада с някои критерии, които сте задали. И двата, find() и findIndex() спират търсенето в array при първото връщане на 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 в numbers.

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

Метода fill()

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

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), запълва array с елементи в индекси 1 и 2 със 0. Извикването на fill() с втори и трети аргумент ни дава възможност да запълним няколко елемента наведнъж без да се презаписва целия array.

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

Метода copyWithin()

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

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

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

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

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

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

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

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

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

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

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

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

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

Typed Arrays

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

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

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

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

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

  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 array адрес.

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

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

Array buffer

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

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

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

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

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

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


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

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

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

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

Манипулиране на array buffer с Views

Array buffer представлява място в паметта и views е интерфейс, чрез който се манипулира тази памет. View работи в array buffer или част от байтовете на array buffer, като чете и пише данни в един от цифровите типове данни. Типа 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 към същия array buffer, което може да бъде полезно, ако искате да използвате само част от място в паметта за цялото приложение, вместо динамично разпределяне на пространството, когато е необходимо.

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

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

  • buffer - array 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, който действа върху целия array buffer и view2, който работи върху малък участък от array buffer. Тези views са с еквивалентни буфер свойства, тъй като и двете работят върху същия array buffer. The byteOffset и byteLength са различни за всяко view, обаче. Те отразяват само част от array buffer, където всяко views работи.

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

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

За всеки от осемте цифрови типове данни в JavaScript DataView прототипа има метод за запис на данни, както и метод за четене на данни от array buffer. Имената на методите за всички, започват със “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 
	    	

Този код използва дву-байтов array buffer за съхраняване на две 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() ред в array buffer.

new ArrayBuffer(2)       0000000000000000

view.setInt8(0, 5);         0000010100000000

view.setInt8(1, -1);       0000010111111111

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

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

Typed Arrays Are Views

В ECMAScript 6, typed arrays са всъщност специфичните за типа views за array buffer. Вместо да се използва общ DataView обект, за да работи върху array buffer можете да използвате обекти, които налагат специфични типове данни. Има осем специфични типа 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 също зависи от типа на array. Докато един елемент в Int8Array е дълъг един байт, Float64Array използва осем байта за елемент. За щастие, елементите са достъпни чрез цифрови индекси, точно както редовни arrays, което позволява да се избегнат до някъде неудобните повиквания на “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 е с подаване на номер към конструктора. Този номер представлява броя на елементите (не байтове) за разпределяне в array. Конструктора ще създаде нов буфер с правилния брой байтове за представяне на редицата на елементите в array и можем да получим достъп до броя на елементите в 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 е array с място за два елемента. Всяко 16-битово цяло число изисква два байта за стойност, така array е разделен на четири байта. Създадения floats е array с място за пет елемента, така броят на необходимите байтове е 20 (четири байта на елемент). И в двата случая се създава нов буфер и може да бъде достъпен, използвайки buffer свойството ако е необходимо.

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

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

  • Typed Array - всеки елемент се копира в нов елемент на нов typed array. Например, ако се подаде int8 на Int16Array конструктора, стойностите на int8 ще бъдат копирани в един int16 array. Новият typed array има различен array buffer от този, който му е подаден.
  • Iterable- итератора на обекта се извиква за да извлече елементите и да ги вмъкне в typed array. Конструктора ще хвърли грешка ако някой от елементите е невалиден за view типа.
  • Array- елементите на 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 и го инициализира с array от две стойности. След това, се създава Int32Array и му се подава Int16Array. Стойностите 25 и 50 са копирани от ints1 в ints2, като двата typed arrays имат съвсем отделни буфери. Същите номера са представени в двата typed arrays, но ints2 има осем байта за представяне на данните, докато ints1 има само четири.

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

Typed arrays и редовни arrays са сходни по няколко начина и както вече видяхте в тази глава, typed arrays могат да бъдат използвани, като редовни 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
За разлика от редовните arrays, не можете да променяте размера на typed array използвайки length свойството. Length свойството не е достъпно за записване, така че всеки опит да го промените се игнорира в non-strict режим и хвърля грешка в strict режим.

Общи методи

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

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

Имайте в предвид, че докато всички тези методи действат също, както тези на Array.prototype, те не са абсолютно същите. Методите на typed arrays имат допълнителни проверки за безопасност на цифровия тип и когато се връща array, ще се върне един typed array вместо редовен 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() метод за създаване на нов array базиран на стойности от ints. Преобразуващата функция удвоява всяка стойност в array и връща нова в Int16Array.

Едни и същи итератори

Typed arrays имат същите три итератора, както редовните array. Това са методите entries(), keys() и values(). Това означава, че позволяват използването на оператора spread и for-of цикъл с typed arrays, по същия начин, както бихте го направили с редовни 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
			

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

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

Накрая, всички typed arrays имат статичните методи of() и from(), които работят подобно на Array.of() и Array.from() методите. Разликата е, че методите за typed arrays връщат typed array вместо обикновен 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 могат да бъдат създадени също толкова лесно, колкото редовните arrays.

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

Най-значимата разлика между typed arrays и редовни arrays е, че typed arrays не са редовни 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 и не може да се индентифицира, като array. Това разграничение е важно, защото докато typed arrays и редовни arrays са сходни, има много начини, по които typed arrays се държат по различен начин.

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

Докато редовните arrays могат да растат и свиват при взаимодействие с тях, typed arrays винаги остават със същия размер. Вие не може да присвоите стойност на несъществуващ цифров индекс в typed array, като можем с редовни arrays, тъй като 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, така че се въвежда нула вместо стойността. Дължината на array остава същата и въпреки, че 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, не е нужно да се притесняваме за хвърляне на грешка, когато са налице невалидни данни, защото никога няма да има невалидни данни в array.

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

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

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

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

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

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

Метода set() приема array (било typed или редовен) и по избор отместване, с което да въвежда данните, ако не се подаде нищо, отместването по подразбиране е нула. Данните от array-аргумента се копират в дестинацията на 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() копира две стойности за първия и втория елемент в array. Второто извикване на set() използва отместване с 2 за да покаже, че стойностите трябва бъдат поставени в array, започвайки от третия елемент.

Метода 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 в този пример. Array subints1 е клонинг на ints, който съдържа същата информация. Array subints2 започва да копира данни от индекс 2 и съдържа само последните два елемента на array (75 и 100). Array subints3 съдържа елементите в средата на ints array, тъй като subarray() се извиква с аргументите за начален и краен индекс.