Piotr Kowalski @piecioshka
Cyfrowy Polsat, Warsaw
JavaScript Ninja. Mac lover. Open source fan.
Blogger.
Organizer WarsawJS.
Author of several libs in @npm
"Kto chce szuka sposobu, kto nie chce szuka powodu."
let MAP = {}
let foo = { name: 'foo' }
let bar = { name: 'bar' }
MAP[foo] = "Hello"
MAP[bar] = "World"
console.log(MAP[foo]) // ???
My interview question.
let MAP = new Map()
let foo = { name: 'foo' }
let bar = { name: 'bar' }
MAP.set(foo, "Hello")
MAP.set(bar, "World")
console.log(MAP.get(foo)) // ???
SIMD:
Float32Array,
Float64Array,
Int8Array,
Int16Array,
Int32Array,
Uint8Array,
Uint8ClampedArray,
Uint16Array,
Uint32Array
DOM:
DOMStringList,
DOMStringMap,
DOMTokenList,
HTMLCollection,
HTMLAllCollection,
HTMLFormControlsCollection,
HTMLOptionsCollection,
NodeList,
RadioNodeList,
SVGAnimatedLengthList,
SVGAnimatedNumberList,
SVGAnimatedTransformList,
SVGLengthList,
SVGNumberList,
SVGPointList,
SVGStringList,
SVGTransformList
Misc:
ArrayBuffer
AudioTrackList,
CSSRuleList,
ClientRectList,
DataTransferItemList,
FileList,
MediaKeys,
MediaList,
MediaQueryList,
MimeTypeArray,
PluginArray,
SourceBufferList,
SpeechGrammarList,
StylePropertyMap,
StyleSheetList,
TextTrackCueList,
TextTrackList,
TouchList,
TrackDefaultList,
VTTRegionList,
VideoTrackList
Object
Array
Map
✨Set
✨WeakMap
✨WeakSet
✨Map
or Set
every day⁉️... | Map | Set | WeakMap | WeakSet |
---|---|---|---|---|
Chrome | 38 | 38 | 36 2014-07 | 36 |
Firefox | 13 | 13 | 6 | 34 |
IE | 11 | 11 | 11 | x |
Opera | 25 | 25 | 23 | 23 |
Safari | 7.1 | 7.1 | 7.1 | 9 |
Map
The Map object is a simple key/value map. Any value (both objects and primitive values) may be used as either a key or a value. MDN @ 2016
// #1: API: Hashmap
// --------------------------------------------------
let map = new Map()
map.set('foo', 'bar')
// instead of: object[key] = value
map.size // 1
// instead of: array.length
map.get('foo') // 'bar'
// instead of: object[key]
// #2: API
// --------------------------------------------------
// Remove pair key/value (by reference or primitive)
map.delete(key) // instead of: array.splice(0, 1)
// Clear collection
map.clear() // instead of: array.length = 0
// #3: API
// --------------------------------------------------
// Check if key is used in collection
map.has(key)
// instead of:
object.hasOwnProperty(key) // or: key in object
array.indexOf(value) !== -1
array.includes(value) // ES2016 (a.k.a. ES7) way
// #4: API: Magic
// --------------------------------------------------
// Get pair key/value form collection {MapIterator}
map.entries() // similar to: Object.entries(object)
// Get keys from collection {MapIterator}
map.keys() // similar to: Object.keys(object)
// Get values from collection {MapIterator}
map.values() // similar to: Object.values(object)
// #5: API: Simple iteration
// --------------------------------------------------
// Iteration through collection
map.forEach((value, key) => /* ... */)
// the same in arrays
// New loop `for..of` (ES2015)
for (let [key, value] of map.entries()) {
map.delete(key) // true
}
// #6: API: WTF?
// --------------------------------------------------
let map = new Map()
map.set() // Map { undefined => undefined }
map.size // 1
// In arrays
let array = []
array.push()
array.length // 0
// #7: API: "Holy" couple key/value
// --------------------------------------------------
let map = new Map()
let rand = Math.random()
map.set([1, 2, 3], Math.random())
map.set([1, 2, 3], Math.random())
map.set([1, 2, 3], rand)
map.set([1, 2, 3], rand)
map.size // 4
// #8: API: .. but unique key!
// --------------------------------------------------
let map = new Map()
let rand = Math.random()
map.set('item', Math.random())
map.set('item', rand) // overwrite previous
map.size // 1
map // Map {"item" => 0.199...}
// #9: API: Adding without coercing
// --------------------------------------------------
let map = new Map()
map.set(5, 'foo')
map.set("5", 'bar')
console.log(map.size) // 2
// #1: Example: Array as key
// --------------------------------------------------
let map = new Map()
map.set(['win8', 'moz'], 'a').set(['xp', 'chrome'], 'b')
function check(collection, ...browsers) {
for (let [key, value] of collection)
// "xp,firefox" === "win8,moz"
if (String(browsers) === String(key))
return value
}
check(map, 'xp', 'chrome') // 'b'
// #2: Example: Async iteration
// --------------------------------------------------
map.set({ foo: 1 }, 'a').set({ foo: 2 }, 'b')
let iterator = map.values() // {MapIterator}
let interval = setInterval(() => {
let item = iterator.next()
if (item.done) {
clearInterval(interval)
return
}
console.log(item.value) // 'a' => [ONE SECOND] => 'b'
}, 1000)
// #3: Example: Subclassing of Map
// --------------------------------------------------
class MultiMap extends Map {
set(...args) {
args.map(([key, value]) => super.set(key, value))
return this
}
}
let map = new MultiMap()
map.set(['key', 'value'], ['key2', 'value2'])
// Map {"key" => "value", "key2" => "value2"}
Map
is cool⁉️Set
The Set object lets you store unique values of any type, whether primitive values or object references. MDN @ 2016
// #2: API
// --------------------------------------------------
let set = new Set([1, 2, 3, 4])
set.add(value) // instead of: array.push(value)
// NOT: set.set() // ??? undefined
// Removing is by value (or reference), not index.
set.delete(3) // true
set.clear() // ...and we have empty Set
set.has(value) // Check by reference
set.get(key) // TypeError: set.get is not a function
// #3: API: Magic
// --------------------------------------------------
// Get values from collection {SetIterator}
set.entries()
set.keys()
set.values()
// hmmm...
// set.values() and set.keys() returns same Iterator
// #6: API: Simple iteration
// --------------------------------------------------
let set = new Set([1, 1, 2])
set.forEach((item) => {
console.log(item) // 1, 2
})
// #1: Example: AssetsLoader: loadImage helper
// --------------------------------------------------
function loadImage(path /* string */) {
return new Promise((resolve, reject) => {
let image = new Image()
let on = image.addEventListener
on('load', () => resolve(image))
on('error', () => reject(image))
image.src = path
})
}
// #1: Example: AssetsLoader: test case
// --------------------------------------------------
let al = new AssetsLoader()
al.addImage('../assets/images/plants.jpg')
al.addImage('../assets/images/sky.jpg')
al.loadImages()
.catch((error) => {
console.error(error)
})
.then((images) => {
console.info('Loaded successful', images)
})
// #1: Example: AssetsLoader v1
// --------------------------------------------------
class AssetsLoader {
constructor() {
this._images = new Set()
}
addImage(path /* string */) {
return this._images.add(loadImage(path))
}
loadImages() {
return Promise.all(this._images)
}
}
// #2: Example: AssetsLoader v2: Subclassing Set
// --------------------------------------------------
class AssetsLoader {
constructor() {
this._images = new Images()
}
addImage(path /* string */) {
return this._images.add(path)
}
loadImages() {
return this._images.load()
}
}
// #2: Example: AssetsLoader v2: Subclassing Set
// --------------------------------------------------
class Images extends Set {
add(path) {
return super.add(loadImage(path))
}
load() {
return Promise.all(this.keys())
}
}
// #3: Example: Remove duplicated item (Array)
// --------------------------------------------------
let array = [1, 2, 3, 1, 3]
// Constructor expects another collection
[...new Set(array)] // [1, 2, 3]
// #3: Example: Remove duplicated items (Map)
// --------------------------------------------------
let map = new Map()
map.set({ foo: 1 }, 'a').set({ foo: 2 }, 'b')
[...new Set(map.keys())]
// [{ foo: 1 }, { foo: 2 }]
[...new Set(map.values())]
// ['a', 'b']
Set
is cool⁉️WeakMap
The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. The keys must be objects and the values can be arbitrary values. MDN @ 2016
// #1: API: Simple API
// --------------------------------------------------
let weakMap = new WeakMap()
let key = { foo: 1 }
weakMap.set([1], 5) // WeakMap { [1] => 5 }
weakMap.set(key, 6) // WeakMap { [1] => 5, {foo: 1} => 6 }
weakMap.get(key) // 6
weakMap.has(key) // true
weakMap.delete(key) // true
// #2: API: "Holy" couple key/value
// --------------------------------------------------
let weakMap = new WeakMap()
weakMap.set([1, 2], Math.random())
weakMap.set([1, 2], Math.random())
weakMap.set([1, 2], Math.random())
weakMap.size // ??? undefined
weakMap // WeakMap {[1, 2] => 0.609..., [1, 2] => 0.268..., [1, 2] => 0.183...}
WeakMap
keys can ONLY be objects⁉️
// #3: API: How it works?
// --------------------------------------------------
let users = [{ name: 'Peter' }, { name: 'Kate' }]
let weakMap = new WeakMap()
weakMap.set(users[0], 'some text 1')
weakMap.set(users[1], 'some text 2')
console.log(weakMap.get(users[0])) // 'some text 1'
users.splice(0, 1)
console.log(weakMap.get(users[0])) // 'some text 2'
☟︎ 2016-09-12 11:38:43.753 ☟︎
☝︎ 2016-09-12 11:38:59.656 ☝︎
WeakMap
is cool⁉️WeakSet
The WeakSet object lets you store weakly held objects in a collection. MDN @ 2016
// #1: API: Simple API
// --------------------------------------------------
let weakSet = new WeakSet()
// Adding ONLY objects
weakSet.add([1])
weakSet.add(1)
// TypeError: Invalid value used in weak set
// ... by reference (not value)
weakSet.has([1]) // false
weakSet.delete([1]) // false
// #2: API: How it works?
// --------------------------------------------------
let users = [{ name: 'Peter' }, { name: 'Kate' }]
let weakSet = new WeakSet()
weakSet.add(users[0])
weakSet.add(users[1])
console.log(weakSet) // WeakSet {Object {name: "Kate"}, Object {name: "Peter"}}
users.splice(0, 1)
console.log(weakSet) // WeakSet {Object {name: "Kate"}, Object {name: "Peter"}}
☟︎ 2016-09-12 15:13:09.329 ☟︎
☝︎ 2016-09-12 15:13:17.837 ☝︎
WeakSet
is cool⁉️Map
& Set
can use primitives and objects as keysSet
& WeakSet
has unique keysWeakMap
& WeakSet
can use ONLY objects as keysWeakMap
& WeakSet
don't have size
& forEach
propertyWeakMap
& WeakSet
held items weakly, so GC will remove its when are unused
// Symbol(Symbol.toStringTag)
String(new Map) === "[object Map]"
String(new Set) === "[object Set]"
String(new WeakMap) === "[object WeakMap]"
String(new WeakSet) === "[object WeakSet]"
Type | Operations / seconds |
---|---|
Object | 2,590,044 ops/sec ±6.32% (72 runs sampled) |
Array | 675,882 ops/sec ±13.49% (65 runs sampled) |
Map | 152,399 ops/sec ±9.67% (63 runs sampled) |
Set | 184,469 ops/sec ±6.27% (78 runs sampled) |
WeakMap | 199,211 ops/sec ±1.93% (82 runs sampled) |
WeakSet | 202,026 ops/sec ±1.91% (82 runs sampled) |
Type | usedJSHeapSize |
---|---|
Object | 7.977 KB |
Array | 5.281 KB |
Map | 28.625 KB |
Set | 13.219 KB |
WeakMap | 15.844 KB |
WeakSet | 9.484 KB |
window.name = { foo: 1 }
console.log(window.name) // [object Object]
// what happen here?
// [immutable type]
const object = {}
object.a = 1
console.log(object.a) // 1
// why we can do this?
// [immutable reference]
typeof null === 'object' // true
// why?
// [empty reference to object]
Number.isFinite('1.2') // false | ES6
isFinite('1.2') // true | ES3
// OMG
// [more robust]
typeof class {} === "function" // true
// why?
// [syntactic sugar]
Object.observe(object, (changes) => {
console.log(changes)
})
// but today...
Object.observe // undefined
// what can I do? use Proxy!
let object = {}
object = new Proxy(object, {
set: (o, prop, value) => {
console.warn(`${prop} is set to ${value}`)
o[prop] = value
},
get: (o, prop) => {
console.warn(`${prop} is read`)
return o[prop]
}
})