Collections

Have several native data structures for advanced usages.

Mainly use Array / Object to create custom or complicated data structures

Before ES6

After ES6

  • TypedArrays
  • Maps & WeakMaps
  • Sets & WeakSets
  • TypedArray is not one of the object type. It's a collection of a bunch of objects.
     
  • In JS, array's not the same as traditional languages like C or Java. It's not a consecutive space in memory and accessed by address offset. If we want to improve performance, we could use ArrayBuffer, which is more like using malloc in C. The OS will allocate a consecutive memory space.
     
  • Can't control buffer(ArrayBuffer) directly. Must access it with view (TypedArray/DataView). Example will be shown later.
     
  • Metaphorically speaking, it's like you have to register the type of land (agriculture/industrial/residence) before using.

TypedArrays

There are 9 types

TypedArrays

  • Int8Array
  • Uint8Array
  • Uint8ClampedArray
  • Int16Array
  • Uint16Array
  • Int32Array
  • Uint32Array
  • Float32Array
  • Float64Array

TypedArrays

let buf = new ArrayBuffer(32)
buf.byteLength	// declare a space with 32 byte (256 bits)

let arr = new Uint16Array(buf)
arr.length;	// use this buffer to store Uint16 data, 256 ÷ 16 = 16 slots

Endiannes(Bytes Order)

When CPU access multiple bytes, there are two kinds of rules.

  • Little-endian: Start with less significant byte, also called LSB.
  • Big-endian: Start with most significant byte, also called MSB.

Endiannes(Bytes Order)

let buf = new ArrayBuffer(2)

let view8 = new Uint8Array(buf)
let view16 = new Uint16Array(buf)

view16[0] = 3085;	// The binary representation is 0000110000001101 for integer 3085
view8[0];   // Most CPU use LSB, therefore, the binary representation for view8[0] is 00001101 (13 in decimal)
view8[1];   // The binary representation for view8[1] is 00001100 (12 in decimal)

view8[0].toString(16);		// "d" in hex
view8[1].toString(16);		// "c" in hex

// swap (as if endian!)
let tmp = view8[0];
view8[0] = view8[1];
view8[1] = tmp;

view16[0];		// The binary represnentation becomes 0000110100001100 (3340 in decimal)

Note: The same buffer could be controlled by many Views. If one view changes the buffer, the result will be reflected when using other views!

  • Share most methods in Array.prototype, including map, forEach, reduce, filter...
     
  • For the methods that would alter the size/length, for example, push, unshift, splice..., they could not be used.

TypedArrays - Prototype

let a = new Uint32Array(3)
a[0] = 10;
a[1] = 20;
a[2] = 30;

let b = a.map(ele => ele * 2)
b.forEach(ele => console.log(ele))	// 20 40 60

b.push(100)  // Uncaught TypeError: b.push is not a function

Since the number of bits are fixed, it could lead to an overflow!

TypedArrays - Overflow

let a = new Uint8Array(3)
a[0] = 10;
a[1] = 20;
a[2] = 30;

let b = a.map(ele => ele * ele)
b.forEach(ele => console.log(ele))	// 100 144 132

The biggest UInt for 1 byte (8 bits) could store is 255. The second and third slot (400 & 900) has an overflow.

TypedArray also have the method .from. It could receive an Iterable and return a whole new TypedArray/Array.

TypedArrays - Overflow

let a = new Uint8Array(3)
a[0] = 10;
a[1] = 20;
a[2] = 30;

let b = Uint16Array.from(a, ele => ele * ele)
console.log(b)	// Uint16Array(3) [100, 400, 900]

Array sorts in alphabetical order, whereas TypedArray sorts in numeric order.

Sort Between TypedArrays & Arrays

let a = [1,2,3,10,11]
let b = new UInt8Array(a)
a.sort()	// [1, 10, 11, 2, 3]
b.sort()	// [1, 2, 3, 10, 11]

When using objects, if keys are also objects, they will be overwritten. All keys will be  stringified into "[object Object]".

Maps

let obj = {}

let x = { id: 1 }
let y = { id: 2 }

obj[x] = 'foo'
obj[y] = 'bar'

console.log(obj[x])  // bar
console.log(obj[y])  // bar

We could solve this issue by using Map. Anything could be used as a key without being stringified.

Maps

let m = new Map()

let x = { id: 1 }
let y = { id: 2 }

m.set(x, 'foo')
m.set(y, 'bar')

m.get(x)  // foo
m.get(y)  // bar
m.size // 2

m[x] // Can't use [] (bracket notation), could only use .set / .get

m.has(x)	// true
m.delete(x)	// can't use the keyword delete, must use .delete method in Map
m.has(x)	// false
m.size // 1
m.clear()	// clear all key/value pairs in Map

We could also pass a 2d array to construct a map. The first element of every subarray will be used as the key, while the second element will be used as the value. Elements after the second one will not be used.

Maps

let m = new Map([
  ['a', [1,2,3]],
  ['b', [4,5,6]]
])

m.get('a')	// [1,2,3]
m.get('b')	// [4,5,6]

We could use .values to extract the values of the Map, however, the return value will be an iterator. We could use ...(spread operator) or for-of to convert it into an array. (The same as .keys)

Maps - Get Values

let m = new Map([
  ['a', 10]
  ['b', 20]
])

let vals = [...m.values()]	// [10, 20]

Array.from(m.values())	// Could also use Array.from

for (let val of map.values()) {
  console.log(val)
}

Like sets in math. Only hold unique values.

Sets

let s = new Set()

var x = { id: 1 },
    y = { id: 2 }

s.add( x )
s.add( y )
s.add( x )

s.size	// 2

s.delete( y )
s.size	// 1

s.clear()
s.size		

Sets

let s = new Set()

var x = { id: 1 },
    y = { id: 2 },
    z = { id: 1}

s.add(x)
s.add(y)
s.add(z)

s.size	// 3 -> although x and z look identical in content, they point to different objects with their own memory space

let w = z
s.add(w)
s.size // 3 -> w & z points to the same space (same space in memory)

Sets

let s = new Set()

var x = { id: 1 },
    y = { id: 2 },
    z = { id: 3}

s.add(x).add(y).add(z)

[...s.keys()]	// { id: 1 } { id: 2 } { id: 3 }

[...s.values()]	// { id: 1 } { id: 2 } { id: 3 }

[...s.entries()]
// [{ id: 1 },{ id: 1 }] [{ id: 2 },{ id: 2 }] [{ id: 3 },{ id: 3 }]

ES6 Collections

By ianyshuang

ES6 Collections

  • 154