Table of Contents
ES6-guide
ECMAScript 6 (ES6) guide
CHECK SUMMARY TO SEE TABLE OF CONTENT
I want to share with you some thoughts, snippets of code and tell you a little about the upcoming ES6. It’s my own road to know it before it will be a standard.
You might have noticed about ES6 a lot lately. This is because the standard is targeting ratification in June 2015.
See draft - ECMAScript 2015
ECMAScript 2015 is a significant update to the language. Previous (ES5) was standardized in 2009. Frameworks like AngularJS, Aurelia, ReactJS, Ionic start using it today.
ES6 includes a lot of new features:
- arrows
- classes
- enhanced object literals
- template strings
- destructuring
- default + rest + spread
- let + const
- iterators + for..of
- generators
- unicode
- modules
- module loaders
- map + set + weakmap + weakset
- proxies
- symbols
- subclassable built-ins
- promises
- math + number + string + object APIs
- binary and octal literals
- reflect api
- tail calls
I will try to describe each of these in the next stories, so stay updated.
Thanks to the use of transpilers (Babel, Traceur and others) we can actually use it right now until browsers fully catch up.
ES6 Repl in Chrome Devl Tools - Scratch JS
1
Future
is
bright
.
People from the community denounced these words. I have only one to add: we can handle it easily!
let + const
First topic about ECMAScript 2015 is let + const. If you are familiar with JavaScript, you have probably known the term: scope. If you are not that lucky, don’t worry about it. I’ll explain that in a few words below.
Why I mentioned something about JavaScript scope? This is because let and const have a very strong connection with that word. Firstly, imagine and old way (and still valid) to declare a new variable in your JS code using ES5:
1
// ES5
2
3
var
a
=
1
;
4
5
if
(
1
===
a
)
{
6
var
b
=
2
;
7
}
8
9
for
(
var
c
=
0
;
c
<
3
;
c
++
)
{
10
// …
11
}
12
13
function
letsDeclareAnotherOne
()
{
14
var
d
=
4
;
15
}
16
17
console
.
log
(
a
);
// 1
18
console
.
log
(
b
);
// 2
19
console
.
log
(
c
);
// 3
20
console
.
log
(
d
);
// ReferenceError: d is not defined
21
22
// window
23
console
.
log
(
window
.
a
);
// 1
24
console
.
log
(
window
.
b
);
// 2
25
console
.
log
(
window
.
c
);
// 3
26
console
.
log
(
window
.
d
);
// undefined
- We can see that variable a is declared as global. Nothing surprising.
- Variable b is inside an if block, but in JavaScript it doesn’t create a new scope. If you are familiar with other languages, you can be disappointed, but this is JavaScript and it works as you see.
- The next statement is a for loop. C variable is declared in this for loop, but also in the global scope.
- Until variable d is declared in his own scope. It’s inside a function and only function creates new scopes.
Variables in JavaScript are hoisted to the top!
Hoisting is JavaScript’s default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function).
In JavaScript, a variable can be declared after it has been used. In other words - a variable can be used before it has been declared!
One more rule, more aware: JavaScript only hoists declarations, not initialization.
1
// scope and variable hoisting
2
3
var
n
=
1
;
4
5
(
function
()
{
6
console
.
log
(
n
);
7
var
n
=
2
;
8
console
.
log
(
n
);
9
})();
10
11
console
.
log
(
n
);
Let’s look for the new keywords in JavaScript ECMAScript 2015: let and const.
let
We can imagine that let is a new var statement. What is the difference? let is block scoped. Let’s see an example:
1
// ES6 — let
2
3
let
a
=
1
;
4
5
if
(
1
===
a
)
{
6
let
b
=
2
;
7
}
8
9
for
(
let
c
=
0
;
c
<
3
;
c
++
)
{
10
// …
11
}
12
13
function
letsDeclareAnotherOne
()
{
14
let
d
=
4
;
15
}
16
17
console
.
log
(
a
);
// 1
18
console
.
log
(
b
);
// ReferenceError: b is not defined
19
console
.
log
(
c
);
// ReferenceError: c is not defined
20
console
.
log
(
d
);
// ReferenceError: d is not defined
21
22
// window
23
console
.
log
(
window
.
a
);
// 1
24
console
.
log
(
window
.
b
);
// undefined
25
console
.
log
(
window
.
c
);
// undefined
26
console
.
log
(
window
.
d
);
// undefined
As we can see, this time only variable a is declared as a global. let gives us a way to declare block scoped variables, which is undefined outside it.
I use Chrome (stable version) with #enable-javascript-harmony flag enabled. Visit chrome://flags/#enable-javascript-harmony, enable this flag, restart Chrome and you will get many new features.
You can also use BabelJS repl or Traceur repl and compare results.
const
const is single-assignment and like a let, block-scoped declaration.
1
// ES6 const
2
3
{
4
const
PI
=
3.141593
;
5
PI
=
3.14
;
// throws “PI” is read-only
6
}
7
8
console
.
log
(
PI
);
// throws ReferenceError: PI is not defined
const cannot be reinitialized. It will throw an Error when we try to assign another value.
Let’s look for the equivalent in ES5:
1
// ES5 const
2
3
var
PI
=
(
function
()
{
4
var
PI
=
3.141593
;
5
return
function
()
{
return
PI
;
};
6
})();
arrow functions
A new syntactic sugar which ES6 brings us soon, called arrow functions (also known as a fat arrow function). It’s a shorter syntax compared to function expressions and lexically binds this value.
REMEMBER - Arrow functions are always anonymous.
Syntactic sugar
How does it look? It’s a signature:
1
([
param
]
[,
param
])
=>
{
2
statements
3
}
4
5
param
=>
expression
6
(
param1
,
param2
)
=>
{
block
}
..and it could be translated to:
1
()
=>
{
…
}
// no argument
2
x
=>
{
…
}
// one argument
3
(
x
,
y
)
=>
{
…
}
// several arguments
4
5
x
=>
{
return
x
*
x
}
// block
6
x
=>
x
*
x
// expression, same as above
Lambda expressions in JavaScript! Cool!
Instead of writing:
1
[
3
,
4
,
5
].
map
(
function
(
n
)
{
2
return
n
*
n
;
3
});
..you can write something like this:
1
[
3
,
4
,
5
].
map
(
n
=>
n
*
n
);
Awesome. Isn’t it? There is more!
Fixed “this” = lexical “this”
The value of this inside of the function is determined by where the arrow function is defined not where it is used.
No more bind, call and apply! No more:
1
var
self
=
this
;
It solves a major pain point (from my point of view) and has the added bonus of improving performance through JavaScript engine optimizations.
1
// ES5
2
function
FancyObject
()
{
3
var
self
=
this
;
4
5
self
.
name
=
'FancyObject'
;
6
setTimeout
(
function
()
{
7
self
.
name
=
'Hello World!'
;
8
},
1000
);
9
}
10
11
// ES6
12
function
FancyObject
()
{
13
this
.
name
=
'FancyObject'
;
14
setTimeout
(()
=>
{
15
this
.
name
=
'Hello World!'
;
// properly refers to FancyObject
16
},
1000
);
17
}
The same function
- typeof returns function
- instanceof returns Function
Limitations
- It’s cannot be used as a constructor and will throw an error when used with new.
- Fixed this means that you cannot change the value of this inside of the function. It remains the same value throughout the entire lifecycle of the function.
- Regular functions can be named.
- Functions declarations are hoisted (can be used before they are declared).
default + rest + spread
ECMAScript 2015 functions made a significant progress, taking into account years of complaints. The result is a number of improvements that make programming in JavaScript less error-prone and more powerful.
Let’s see three new features which give us extended parameter handling.
default
It’s a simple, little addition that makes it much easier to handle function parameters. Functions in JavaScript allow any number of parameters to be passed regardless of the number of declared parameters in the function definition. You probably know commonly seen pattern in current JavaScript code:
1
function
inc
(
number
,
increment
)
{
2
// set default to 1 if increment not passed
3
// (or passed as undefined)
4
increment
=
increment
||
1
;
5
return
number
+
increment
;
6
}
7
8
console
.
log
(
inc
(
2
,
2
));
// 4
9
console
.
log
(
inc
(
2
));
// 3
The logical OR operator (||) always returns the second operand when the first is falsy.
ES6 gives us a way to set default function parameters. Any parameters with a default value are considered to be optional.
ES6 version of inc function looks like this:
1
function
inc
(
number
,
increment
=
1
)
{
2
return
number
+
increment
;
3
}
4
5
console
.
log
(
inc
(
2
,
2
));
// 4
6
console
.
log
(
inc
(
2
));
// 3
You can also set default values to parameters that appear before arguments without default values:
1
function
sum
(
a
,
b
=
2
,
c
)
{
2
return
a
+
b
+
c
;
3
}
4
5
console
.
log
(
sum
(
1
,
5
,
10
));
// 16 -> b === 5
6
console
.
log
(
sum
(
1
,
undefined
,
10
));
// 13 -> b as default
You can even execute a function to set default parameter. It’s not restricted to primitive values.
1
function
getDefaultIncrement
()
{
2
return
1
;
3
}
4
5
function
inc
(
number
,
increment
=
getDefaultIncrement
())
{
6
return
number
+
increment
;
7
}
8
9
console
.
log
(
inc
(
2
,
2
));
// 4
10
console
.
log
(
inc
(
2
));
// 3
rest
Let’s rewrite sum function to handle all arguments passed to it (without validation - just to be clear). If we want to use ES5, we probably also want to use arguments object.
1
function
sum
()
{
2
var
numbers
=
Array
.
prototype
.
slice
.
call
(
arguments
),
3
result
=
0
;
4
numbers
.
forEach
(
function
(
number
)
{
5
result
+=
number
;
6
});
7
return
result
;
8
}
9
10
console
.
log
(
sum
(
1
));
// 1
11
console
.
log
(
sum
(
1
,
2
,
3
,
4
,
5
));
// 15
But it’s not obvious that the function is capable of handling any parameters. W have to scan body of the function and find arguments object.
ECMAScript 6 introduces rest parameters to help us with this and other pitfalls.
arguments - contains all parameters including named parameters
Rest parameters are indicated by three dots … preceding a parameter. Named parameter becomes an array which contain the rest of the parameters.
sum function can be rewritten using ES6 syntax:
1
function
sum
(
…
numbers
)
{
2
var
result
=
0
;
3
numbers
.
forEach
(
function
(
number
)
{
4
result
+=
number
;
5
});
6
return
result
;
7
}
8
9
console
.
log
(
sum
(
1
));
// 1
10
console
.
log
(
sum
(
1
,
2
,
3
,
4
,
5
));
// 15
Restriction: no other named arguments can follow in the function declaration.
1
function
sum
(
…
numbers
,
last
)
{
// causes a syntax error
2
var
result
=
0
;
3
numbers
.
forEach
(
function
(
number
)
{
4
result
+=
number
;
5
});
6
return
result
;
7
}
spread
The spread is closely related to rest parameters, because of … (three dots) notation. It allows to split an array to single arguments which are passed to the function as separate arguments.
Let’s define our sum function an pass spread to it:
1
function
sum
(
a
,
b
,
c
)
{
2
return
a
+
b
+
c
;
3
}
4
5
var
args
=
[
1
,
2
,
3
];
6
console
.
log
(
sum
(
…
args
));
// 6
ES5 equivalent is:
1
function
sum
(
a
,
b
,
c
)
{
2
return
a
+
b
+
c
;
3
}
4
5
var
args
=
[
1
,
2
,
3
];
6
console
.
log
(
sum
.
apply
(
undefined
,
args
));
// 6
Instead using an apply function, we can just type …args and pass all array argument separately.
We can also mix standard arguments with spread operator:
1
function
sum
(
a
,
b
,
c
)
{
2
return
a
+
b
+
c
;
3
}
4
5
var
args
=
[
1
,
2
];
6
console
.
log
(
sum
(
…
args
,
3
));
// 6
The result is the same. First two arguments are from args array, and the last passed argument is 3.
destructuring
Destructuring is one more little addition to the upcoming JavaScript standard, which helps us write code more flexibly and effectively.
It allows binding using pattern matching. We can use it for matching arrays and objects. It’s similar to standard object look up and returns undefined when value is not found.
arrays
Today it’s common to see the code such as this.
1
// ES5
2
var
point
=
[
1
,
2
];
3
var
xVal
=
point
[
0
],
4
yVal
=
point
[
1
];
5
6
console
.
log
(
xVal
);
// 1
7
console
.
log
(
yVal
);
// 2
ES6 gives us destructuring of arrays into individual variables during assignment which is intuitive and flexible.
1
// ES6
2
let
point
=
[
1
,
2
];
3
let
[
xVal
,
yVal
]
=
point
;
4
5
console
.
log
(
xVal
);
// 1
6
console
.
log
(
yVal
);
// 2
7
// .. and reverse!
8
[
xVal
,
yVal
]
=
[
yVal
,
xVal
];
9
10
console
.
log
(
xVal
);
// 2
11
console
.
log
(
yVal
);
// 1
We can even omit some values..
1
let
threeD
=
[
1
,
2
,
3
];
2
let
[
a
,
,
c
]
=
threeD
;
3
4
console
.
log
(
a
);
// 1
5
console
.
log
(
c
);
// 3
..and have nested array destructuring.
1
let
nested
=
[
1
,
[
2
,
3
],
4
];
2
let
[
a
,
[
b
],
d
]
=
nested
;
3
4
console
.
log
(
a
);
// 1
5
console
.
log
(
b
);
// 2
6
console
.
log
(
d
);
// 4
objects
As well as the array syntax, ES6 also has the ability to destructure objects. It uses an object literal on the left side of an assignment operation. Object pattern is very similar to array pattern seen above. Let’s see:
1
let
point
=
{
2
x
:
1
,
3
y
:
2
4
};
5
let
{
x
:
a
,
y
:
b
}
=
point
;
6
7
console
.
log
(
a
);
// 1
8
console
.
log
(
b
);
// 2
It supports nested object as well as array pattern.
1
let
point
=
{
2
x
:
1
,
3
y
:
2
,
4
z
:
{
5
one
:
3
,
6
two
:
4
7
}
8
};
9
let
{
x
:
a
,
y
:
b
,
z
:
{
one
:
c
,
two
:
d
}
}
=
point
;
10
11
console
.
log
(
a
);
// 1
12
console
.
log
(
b
);
// 2
13
console
.
log
(
c
);
// 3
14
console
.
log
(
d
);
// 4
mixed
We can also mix objects and arrays together and use theirs literals.
1
let
mixed
=
{
2
one
:
1
,
3
two
:
2
,
4
values
:
[
3
,
4
,
5
]
5
};
6
let
{
one
:
a
,
two
:
b
,
values
:
[
c
,
,
e
]
}
=
mixed
;
7
8
console
.
log
(
a
);
// 1
9
console
.
log
(
b
);
// 2
10
console
.
log
(
c
);
// 3
11
console
.
log
(
e
);
// 5
But I think the most interesting is that we are able to use functions which return destructuring assignment.
1
function
mixed
()
{
2
return
{
3
one
:
1
,
4
two
:
2
,
5
values
:
[
3
,
4
,
5
]
6
};
7
}
8
let
{
one
:
a
,
two
:
b
,
values
:
[
c
,
,
e
]
}
=
mixed
();
9
10
console
.
log
(
a
);
// 1
11
console
.
log
(
b
);
// 2
12
console
.
log
(
c
);
// 3
13
console
.
log
(
e
);
// 5
The same result! It gives us a lot of possibilities to use it in our code.
attention!
If the value of a destructuring assignment isn’t match, it evaluates to undefined.
1
let
point
=
{
2
x
:
1
3
};
4
let
{
x
:
a
,
y
:
b
}
=
point
;
5
6
console
.
log
(
a
);
// 1
7
console
.
log
(
b
);
// undefined
If we try to omit var, let or const, it will throw an error, because block code can’t be destructuring assignment.
1
let
point
=
{
2
x
:
1
3
};
4
5
{
x
:
a
}
=
point
;
// throws error
We have to wrap it in parentheses. Just that ☺
1
let
point
=
{
2
x
:
1
3
};
4
({
x
:
a
}
=
point
);
5
6
console
.
log
(
a
);
// 1
strings
I gonna show you a couple of changes to strings in JavaScript, which will be available when ES6 comes. A syntactic sugar, which could be helpful in daily work.
template strings
First, a string interpolation. Yep, template strings (finally) support string interpolation. ES6 brings us also support for multi-line syntax and raw literals.
1
let
x
=
1
;
2
let
y
=
2
;
3
let
sumTpl
=
`
$
{
x
}
+
$
{
y
}
=
$
{
x
+
y
}
`
;
4
5
console
.
log
(
sumTpl
);
// 1 + 2 = 3
As you can see, we can inject values to string by using ${value} syntax. Another thing to consider is grave accent - a char under the tilde (~) on a keyboard. A template literal string must be wrapped by it, to work properly.
The example above is an equivalent (in ES5) to simply (Babel version):
1
var
x
=
1
;
2
var
y
=
2
;
3
var
sumTpl
=
""
+
x
+
" + "
+
y
+
" = "
+
(
x
+
y
);
4
5
console
.
log
(
sumTpl
);
// 1 + 2 = 3
This feature is very useful and almost removes the need for a template system.
Template strings provide also multi-line syntax, which is not legal in ES5 and earlier.
1
let
types
=
`
Number
2
String
3
Array
4
Object
`
;
5
console
.
log
(
types
);
// Number
6
// String
7
// Array
8
// Object
ES5 equivalent:
1
var
types
=
"Number\nString\nArray\nObject"
;
2
console
.
log
(
types
);
// Number
3
// String
4
// Array
5
// Object
The last thing is access the raw template string content where backslashes are not interpreted. We don’t have equivalent in ES5 here.
1
let
interpreted
=
'raw\nstring'
;
2
let
esaped
=
'raw\\nstring'
;
3
let
raw
=
String
.
raw
`
raw
\
nstring
`
;
4
5
console
.
log
(
interpreted
);
// raw
6
// string
7
console
.
log
(
raw
===
esaped
);
// true
extended support for Unicode
ES6 gives us full support for Unicode within strings and regular expressions. It’s non-breaking addition allows to building global apps.
Let’s see an example:
1
let
str
=
'𠮷'
;
2
3
console
.
log
(
str
.
length
);
// 2
4
console
.
log
(
str
===
'\uD842\uDFB7'
);
// true
You can see that character 𠮷* represented by two 16-bit code units. It’s a surrogate pair in which we have a single code point represented by two code units. The length of that string is also 2.
Surrogate pairs are used in UTF-16 to represent code points above U+FFFF.
1
console
.
log
(
str
.
charCodeAt
(
0
));
// 55362
2
console
.
log
(
str
.
charCodeAt
(
1
));
// 57271
The charCodeAt() method returns the 16-bit number for each code unit.
ES6 allows encoding of strings in UTF-16. JavaScript can now support work with surrogate pairs. It gives us also a new method codePointAt() that returns Unicode code point instead of Unicode code unit.
1
console
.
log
(
str
.
codePointAt
(
0
));
// 134071
2
console
.
log
(
str
.
codePointAt
(
1
));
// 57271
3
console
.
log
(
str
.
codePointAt
(
0
)
===
0x20BB7
);
// true
It works the same as charCodeAt() except for non-BMP characters.
BMP - Basic Multilingual Plane - the first 2^16 code points.
codePointAt() returns full code point at the 0 position. codePointAt() and charCodeAt() return the same value for position 1.
We can also do a reverse operation with another new method added to ES6: fromCodePoint().
1
console
.
log
(
String
.
fromCodePoint
(
134071
));
// "𠮷"
2
console
.
log
(
String
.
fromCodePoint
(
0x20BB7
));
// "𠮷"
Unicode code unit escape sequences consist of six characters, namely \u plus four hexadecimal digits, and contribute one code unit.
Unicode code point escape sequences consist of five to ten characters, namely \u{ 1–6 hexadecimal digits }, and contribute one or two code units.
Dealing with that two definitions, above example could be represented by one code point in ES6:
1
// ES6
2
console
.
log
(
'\u{20BB7}'
);
// 𠮷
3
console
.
log
(
'\u{20BB7}'
===
'\uD842\uDFB7'
);
// true
4
5
// ES5
6
console
.
log
(
'\u20BB7); // 7!
7
console.log('
\
u20BB7
' === '
\
uD842
\
uDFB7
'
);
// false
In ES5 we get an unexpected result when we try to match one single character using regular expression.
1
console
.
log
(
/^.$/
.
test
(
str
));
// false - length is 2
ES6 allows us to use new RegExp u mode to handle code points. It is simply a new u flag (u == Unicode).
1
console
.
log
(
/^.$/u.test(str)); // true
Adding u flag allows to correctly match the string by characters instead of code units.
strings are iterable
Strings are iterable by using the for-of loop which I will cover in more detail in iterators article later. I write about it now because it enumerate Unicode code points and each may comprise one or two characters.
1
let
str
=
'abc\uD842\uDFB7'
;
2
console
.
log
(
str
.
length
);
// 5
3
for
(
let
c
of
str
)
{
4
console
.
log
(
c
);
// a
5
// b
6
// c
7
// 𠮷
8
}
We can also use spread operator to transform string into an array with full Unicode support.
1
let
str
=
'abc\uD842\uDFB7'
;
2
let
chars
=
[
…
str
];
3
console
.
log
(
chars
);
// ['a', 'b', 'c', '𠮷']
new string methods
repeat(n) - string repeats by n times
1
console
.
log
(
'abc|'
.
repeat
(
3
));
// 'abc|abc|abc|'
startsWith(str, starts = 0) : boolean - check if string starts with str, starting from starts
1
console
.
log
(
'ecmascript'
.
startsWith
(
'ecma'
));
// true
2
console
.
log
(
'ecmascript'
.
startsWith
(
'script'
,
4
));
// true
endsWith(str, ends = str.length) : boolean - check if string ends with str, ends - where the string to be checked ends
1
console
.
log
(
'ecmascript'
.
endsWith
(
'script'
));
// true
2
console
.
log
(
'ecmascript'
.
endsWith
(
'ecma'
,
4
));
// true
includes(str, starts = 0) : boolean - check if string contain str, starting from starts
1
console
.
log
(
'ecmascript'
.
includes
(
'ecma'
));
// true
2
console
.
log
(
'ecmascript'
.
includes
(
'script'
,
4
));
// true
iterators
iterator & iterable
An iterator is an object with a next method that returns { done, value } tuples.
ES6 gives us a pattern for creating custom iterators and it has a similar implementation to Java Iterable or .NET IEnumerable. It has also built-in iterables: String, Array, TypedArray, Map and Set. An iterator object can be any object with a next() method.
Iterable is an object which has Symbol.iterator method inside.
Symbol is in turn an unique and immutable data type which can be used as an identifier for object properties — no equivalent in ES5.
1
// Symbol
2
let
s1
=
Symbol
(
'abc'
);
3
let
s2
=
Symbol
(
'abc'
);
4
5
console
.
log
(
s1
!==
s2
);
// true
6
console
.
log
(
typeof
s1
);
// 'symbol'
7
8
let
obj
=
{};
9
obj
[
s1
]
=
'abc'
;
10
console
.
log
(
obj
);
// Object { Symbol(abc): 'abc' }
Let’s see a simple iterator written from scratch, which allows us to iterate through random numbers which are dynamically generated by next() method. A function returning iterable object take one argument (items) which is used to determine if the iterator should stop and returns done = true.
1
let
random1_10
=
function
(
items
=
1
)
{
2
return
{
3
[
Symbol
.
iterator
]()
{
4
let
cur
=
0
;
5
return
{
6
next
()
{
7
let
done
=
cur
===
items
,
8
random
=
Math
.
floor
(
Math
.
random
()
*
10
)
+
1
;
9
++
cur
;
10
return
{
11
done
:
done
,
12
value
:
random
13
}
14
}
15
}
16
}
17
};
18
};
19
20
for
(
let
n
of
random1_10
(
5
))
{
21
console
.
log
(
n
);
// prints 5 random numbers
22
}
Every time for-of loop call next() method, an iterator generate a random number and returns it to the loop.
If the iterator returns done = true, you can omit value, so the result will be { done: true }
1
let
random1_10
=
function
(
items
=
1
)
{
2
return
{
3
[
Symbol
.
iterator
]()
{
4
let
cur
=
0
;
5
return
{
6
next
()
{
7
if
(
cur
===
items
)
{
8
return
{
9
done
:
true
10
}
11
}
12
++
cur
;
13
return
{
14
done
:
false
,
15
value
:
Math
.
floor
(
Math
.
random
()
*
10
)
+
1
16
}
17
}
18
}
19
}
20
};
21
};
22
23
for
(
let
n
of
random1_10
(
5
))
{
24
console
.
log
(
n
);
// prints 5 random numbers
25
}
for-of loop
ES6 has a new loop — for-of. It works with iterables. Let’s look at his signature:
1
for
(
LET
of
ITERABLE
)
{
2
CODE
BLOCK
3
}
It’s similar to for-in loop, which can be used to iterate through object properties (plain old Objects).
Arrays in ES6 are iterable by default, so we finally can use for-of for looping over the elements.
1
const
arr
=
[
1
,
2
,
3
,
4
,
5
];
2
3
for
(
let
item
of
arr
)
{
4
console
.
log
(
item
);
// 1
5
// 2
6
// 3
7
// 4
8
// 5
9
}
Inside the for-of loop, we can even use a break, continue and return.
1
const
arr
=
[
1
,
2
,
3
,
4
,
5
];
2
3
for
(
let
item
of
arr
)
{
4
if
(
item
>
4
)
{
5
break
;
6
}
7
if
(
0
!==
item
%
2
)
{
8
continue
;
9
}
10
console
.
log
(
item
);
// 2
11
// 4
12
}
generators
Generators are simply subtypes of Iterators which I wrote about previously. They are a special kind of function that can be suspended and resumed, which is making a difference to iterators. Generators use function* and yield operators to work their magic.
The yield operator returns a value from the function and when the generator is resumed, execution continues after the yield.
We also have to use function* (with star character) instead of a function to return a generator instance.
!!! Generators have been borrowed from Python language.
The most magical feature in ES6!
Why? Take a look:
1
function
*
generator
()
{
2
yield
1
;
3
// pause
4
yield
2
;
5
// pause
6
yield
3
;
7
// pause
8
yield
'done?'
;
9
// done
10
}
11
let
gen
=
generator
();
// [object Generator]
12
13
console
.
log
(
gen
.
next
());
// Object {value: 1, done: false}
14
console
.
log
(
gen
.
next
());
// Object {value: 2, done: false}
15
console
.
log
(
gen
.
next
());
// Object {value: 3, done: false}
16
console
.
log
(
gen
.
next
());
// Object {value: 'done?', done: false}
17
console
.
log
(
gen
.
next
());
// Object {value: undefined, done: true}
18
console
.
log
(
gen
.
next
());
// Object {value: undefined, done: true}
19
20
for
(
let
val
of
generator
())
{
21
console
.
log
(
val
);
// 1
22
// 2
23
// 3
24
// 'done?'
25
}
As you can see, the generator has four yield statements. Each returns a value, pauses execution and moves to the next yield when next() method is called. Calling a function produces an object for controlling generator execution, a so-called generator object.
Use is similar to iterators. We have next() method as I mentioned above and we can even use it with for-of loop.
Below is an example of a generator called random1_10, which returns random numbers from 1 to 10.
1
function
*
random1_10
()
{
2
while
(
true
)
{
3
yield
Math
.
floor
(
Math
.
random
()
*
10
)
+
1
;
4
}
5
}
6
7
let
rand
=
random1_10
();
8
console
.
log
(
rand
.
next
());
9
console
.
log
(
rand
.
next
());
10
// …
Generator has never ending while loop. It produces random numbers every time when you call next() method.
ES5 implementation:
1
function
random1_10
()
{
2
return
{
3
next
:
function
()
{
4
return
{
5
value
:
Math
.
floor
(
Math
.
random
()
*
10
)
+
1
,
6
done
:
false
7
};
8
}
9
};
10
}
11
12
let
rand
=
random1_10
();
13
console
.
log
(
rand
.
next
());
14
console
.
log
(
rand
.
next
());
15
// …
We can also mix generators together:
1
function
*
random
(
max
)
{
2
yield
Math
.
floor
(
Math
.
random
()
*
max
)
+
1
;
3
}
4
5
function
*
random1_20
()
{
6
while
(
true
)
{
7
yield
*
random
(
20
);
8
}
9
}
10
11
let
rand
=
random1_20
();
12
console
.
log
(
rand
.
next
());
13
console
.
log
(
rand
.
next
());
14
// …
random1_20 generator returns random values from 1 to 20. It uses random generator inside to create random number each time when yield statement is reached.
classes and inheritance
OO keywords is probably the most awaited features in ES6. Classes are something like another syntactic sugar over the prototype-based OO pattern. We now have one, concise way to make class patterns easier to use.
Over the prototype-based OO pattern to ensure backwards compatibility.
Overview — an example of ES6 class syntax and ES5 equivalent
1
class
Vehicle
{
2
3
constructor
(
name
,
type
)
{
4
this
.
name
=
name
;
5
this
.
type
=
type
;
6
}
7
8
getName
()
{
9
return
this
.
name
;
10
}
11
12
getType
()
{
13
return
this
.
type
;
14
}
15
16
}
17
18
let
car
=
new
Vehicle
(
'Tesla'
,
'car'
);
19
console
.
log
(
car
.
getName
());
// Tesla
20
console
.
log
(
car
.
getType
());
// car
It’s naive example, but we can see a new keywords as class and constructor.
ES5 equivalent could be something like this:
1
function
Vehicle
(
name
,
type
)
{
2
this
.
name
=
name
;
3
this
.
type
=
type
;
4
};
5
6
Vehicle
.
prototype
.
getName
=
function
getName
()
{
7
return
this
.
name
;
8
};
9
10
Vehicle
.
prototype
.
getType
=
function
getType
()
{
11
return
this
.
type
;
12
};
13
14
var
car
=
new
Vehicle
(
'Tesla'
,
'car'
);
15
console
.
log
(
car
.
getName
());
// Tesla
16
console
.
log
(
car
.
getType
());
// car
Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
It’s simple. We instantiate our classes the same way, but let’s add some..
inheritance
..to it and start from ES5 example:
1
function
Vehicle
(
name
,
type
)
{
2
this
.
name
=
name
;
3
this
.
type
=
type
;
4
};
5
6
Vehicle
.
prototype
.
getName
=
function
getName
()
{
7
return
this
.
name
;
8
};
9
10
Vehicle
.
prototype
.
getType
=
function
getType
()
{
11
return
this
.
type
;
12
};
13
14
function
Car
(
name
)
{
15
Vehicle
.
call
(
this
,
name
,
'car'
);
16
}
17
18
Car
.
prototype
=
Object
.
create
(
Vehicle
.
prototype
);
19
Car
.
prototype
.
constructor
=
Car
;
20
Car
.
parent
=
Vehicle
.
prototype
;
21
Car
.
prototype
.
getName
=
function
()
{
22
return
'It is a car: '
+
this
.
name
;
23
};
24
25
var
car
=
new
Car
(
'Tesla'
);
26
console
.
log
(
car
.
getName
());
// It is a car: Tesla
27
console
.
log
(
car
.
getType
());
// car
And now look at the ES6 version:
1
class
Vehicle
{
2
3
constructor
(
name
,
type
)
{
4
this
.
name
=
name
;
5
this
.
type
=
type
;
6
}
7
8
getName
()
{
9
return
this
.
name
;
10
}
11
12
getType
()
{
13
return
this
.
type
;
14
}
15
16
}
17
18
class
Car
extends
Vehicle
{
19
20
constructor
(
name
)
{
21
super
(
name
,
'car'
);
22
}
23
24
getName
()
{
25
return
'It is a car: '
+
super
.
getName
();
26
}
27
28
}
29
30
let
car
=
new
Car
(
'Tesla'
);
31
console
.
log
(
car
.
getName
());
// It is a car: Tesla
32
console
.
log
(
car
.
getType
());
// car
We see how easy is to implement inheritance with ES6. It’s finally looking like in other OO programming languages. We use extends to inherit from another class and the super keyword to call the parent class (function). Moreover, getName() method was overridden in subclass Car.
super — previously to achieve such functionality in Javascript required the use of call or apply
static
1
class
Vehicle
{
2
3
constructor
(
name
,
type
)
{
4
this
.
name
=
name
;
5
this
.
type
=
type
;
6
}
7
8
getName
()
{
9
return
this
.
name
;
10
}
11
12
getType
()
{
13
return
this
.
type
;
14
}
15
16
static
create
(
name
,
type
)
{
17
return
new
Vehicle
(
name
,
type
);
18
}
19
20
}
21
22
let
car
=
Vehicle
.
create
(
'Tesla'
,
'car'
);
23
console
.
log
(
car
.
getName
());
// Tesla
24
console
.
log
(
car
.
getType
());
// car
Classes give us an opportunity to create static members. We don’t have to use the new keyword later to instantiate a class.
static methods (properties) are also inherited and could be called by super
get / set
Other great things in upcoming ES6 are getters and setters for object properties. They allow us to run the code on the reading or writing of a property.
1
class
Car
{
2
3
constructor
(
name
)
{
4
this
.
_name
=
name
;
5
}
6
7
set
name
(
name
)
{
8
this
.
_name
=
name
;
9
}
10
11
get
name
()
{
12
return
this
.
_name
;
13
}
14
15
}
16
17
let
car
=
new
Car
(
'Tesla'
);
18
console
.
log
(
car
.
name
);
// Tesla
19
20
car
.
name
=
'BMW'
;
21
console
.
log
(
car
.
name
);
// BMW
I use ‘_’ prefix to create a (tmp) field to store name property.
Enhanced Object Properties
The last thing I have to mention is property shorthand, computed property names and method properties.
ES6 gives us shorter syntax for common object property definition:
1
// ES6
2
let
x
=
1
,
3
y
=
2
,
4
obj
=
{
x
,
y
};
5
6
console
.
log
(
obj
);
// Object { x: 1, y: 2 }
7
8
// ES5
9
var
x
=
1
,
10
y
=
2
,
11
obj
=
{
12
x
:
x
,
13
y
:
y
14
};
15
16
console
.
log
(
obj
);
// Object { x: 1, y: 2 }
As you can see, this works because the property value has the same name as the property identifier.
Another thing is ES6 support for computed names in object property definitions:
1
// ES6
2
let
getKey
=
()
=>
'123'
,
3
obj
=
{
4
foo
:
'bar'
,
5
[
'key_'
+
getKey
()]
:
123
6
};
7
8
console
.
log
(
obj
);
// Object { foo: 'bar', key_123: 123 }
9
10
// ES5
11
var
getKey
=
function
()
{
12
return
'123'
;
13
},
14
obj
=
{
15
foo
:
'bar'
16
};
17
obj
[
'key_'
+
getKey
()]
=
123
;
18
19
console
.
log
(
obj
);
// Object { foo: 'bar', key_123: 123 }
The one last thing is method properties seen in classes above. We can even use it in object definitions:
1
// ES6
2
let
obj
=
{
3
name
:
'object name'
,
4
toString
()
{
// 'function' keyword is omitted here
5
return
this
.
name
;
6
}
7
};
8
9
10
console
.
log
(
obj
.
toString
());
// object name
11
12
// ES5
13
var
obj
=
{
14
name
:
'object name'
,
15
toString
:
function
()
{
16
return
this
.
name
;
17
}
18
};
19
20
console
.
log
(
obj
.
toString
());
// object name
modules
Today we have a couple of ways to create modules, export & import them. JavaScript doesn’t have any built-in module loader yet. Upcoming ECMAScript 2015 standard gives us a reason to make people happy. Finally ;)
We have third party standards: CommonJS and AMD. The most popular, but, unfortunately, incompatible standards for module loaders.
CommonJS is known from Node.js. It’s mostly dedicated for servers and it supports synchronous loading. It also has a compact syntax focused on export and require keywords.
AMD and the most popular implementation - RequireJS are dedicated for browsers. AMD supports asynchronous loading, but has more complicated syntax than CommonJS.
The goal for ES6 is (was) to mix these two standards and make both user groups happy.
ES6 gives us an easy syntax and support for asynchronous and configurable module loading.
Async model — no code executes until requested modules are available and processed.
Named export
Modules can export multiple objects, which could be simple variables or functions.
1
export
function
multiply
(
x
,
y
)
{
2
return
x
*
y
;
3
};
We can also export a function stored in a variable, but we have to wrap the variable in a set of curly braces.
1
var
multiply
=
function
(
x
,
y
)
{
2
return
x
*
y
;
3
};
4
5
export
{
multiply
};
We can even export many objects and like in the above example — we have to wrap exported statements in a set of curly braces if we use one export keyword.
1
export
hello
=
'Hello World'
;
2
export
function
multiply
(
x
,
y
)
{
3
return
x
*
y
;
4
};
5
6
// === OR ===
7
8
var
hello
=
'Hello World'
,
9
multiply
=
function
(
x
,
y
)
{
10
return
x
*
y
;
11
};
12
13
export
{
hello
,
multiply
};
Let’s just imagine that we have modules.js file with all exported statements. To import them in another file (in the same directory) we use … import { .. } from .. syntax:
1
import
{
hello
}
from
'modules'
;
We can omit .js extension just like in CommonJS and AMD.
We can even import many statements:
1
import
{
hello
,
multiply
}
from
'modules'
;
Imports may also be aliased:
1
import
{
multiply
as
pow2
}
from
'modules'
;
..and use wildcard (*) to import all exported statemets:
1
import
*
from
'modules'
;
Default export
In our module, we can have many named exports, but we can also have a default export. It’s because our module could be a large library and with default export we can import then an entire module. It could be also useful when our module has single value or model (class / constructor).
One default export per module.
1
export
default
function
(
x
,
y
)
{
2
return
x
*
y
;
3
};
This time we don’t have to use curly braces for importing and we have a chance to name imported statement as we wish.
1
import
multiply
from
'modules'
;
2
3
// === OR ===
4
5
import
pow2
from
'modules'
;
6
7
// === OR ===
8
9
...
Module can have both named exports and a default export.
1
// modules.js
2
export
hello
=
'Hello World'
;
3
export
default
function
(
x
,
y
)
{
4
return
x
*
y
;
5
};
6
7
// app.js
8
import
pow2
,
{
hello
}
from
'modules'
;
The default export is just a named export with the special name default.
1
// modules.js
2
export
default
function
(
x
,
y
)
{
3
return
x
*
y
;
4
};
5
6
// app.js
7
import
{
default
}
from
'modules'
;
API
In addition, there is also a programmatic API and it allows to:
- Programmatically work with modules and scripts
- Configure module loading
SystemJS — universal dynamic module loader — loads ES6 modules, AMD, CommonJS and global scripts in the browser and NodeJS. Works with both Traceur and Babel.
Module loader should support: * Dynamic loading
- Global namespace isolation
- Nested virtualization
- Compilation hooks
promises
Promises aren’t a new and shiny idea. I use it every day in my AngularJS code. It’s based on kriskowal / q library:
A tool for creating and composing asynchronous promises in JavaScript.
It’s a library for asynchronous programming, to make our life easier. But, before I describe promises, I have to write something about callbacks.
Callbacks and callback hell
Until I remember, JavaScript coders use callbacks for all browser-based asynchronous functions (setTimeout, XMLHttpRequest, etc.).
Look at naive example:
1
console
.
log
(
'start!'
);
2
setTimeout
(
function
()
{
3
console
.
log
(
'ping'
);
4
setTimeout
(
function
()
{
5
console
.
log
(
'pong'
);
6
setTimeout
(
function
()
{
7
console
.
log
(
'end!'
);
8
},
1000
);
9
},
1000
);
10
},
1000
);
11
12
// start!
13
// after 1 sec: ping
14
// .. 1 sec later: pong
15
// .. and: end!
We have simple code which prints some statements to the console. I used a setTimeout function here, to show callback functions passed to invoke later (1 sec here). It looks terrible and we have only 3 steps here. Let’s imagine more steps. It will look like you build a pyramid, not nice, readable code. Awful, right? It’s called callback hell and we have it everywhere.
Promises
Support for promises is a very nice addition to the language. It’s finally native in the ES6.
Promises are a first class representation of a value that may be made available in the future.
A promise can be:
- fulfilled - promise succeeded
- rejected - promise failed
- pending - not fulfilled or not rejected yet
- settled - fulfilled or rejected
Every returned promise object also has a then method to execute code when a promise is settled.
Yep, promise object, because..
callbacks are functions, promises are objects.
Callbacks are blocks of code to execute in response to.. something (event). Promises are objects which store an information about the state.
How does it look like? Let’s see:
1
new
Promise
((
resolve
,
reject
)
=>
{
2
// when success, resolve
3
let
value
=
'success'
;
4
resolve
(
value
);
5
6
// when an error occurred, reject
7
reject
(
new
Error
(
'Something happened!'
));
8
});
Promise calls its resolve function when it’s fulfilled (success) and reject function otherwise (failure).
Promises are objects, so it’s not passed as arguments like callbacks, it’s returned. The return statement is an object which is a placeholder for the result, which will be available in the future.
Promises have just one responsibility-they represent only one event. Callbacks can handle multiple events, many times.
We can assign returned value (object) to the let statement:
1
let
promise
=
new
Promise
((
resolve
,
reject
)
=>
{
2
// when success, resolve
3
let
value
=
'success'
;
4
resolve
(
value
);
5
6
// when an error occurred, reject
7
reject
(
new
Error
(
'Something happened!'
));
8
});
As I mentioned above-promise object also has a then method to execute code when the promise is settled.
1
promise
.
then
(
onResolve
,
onReject
)
We can use this function to handle onResolve and onReject values returned by a promise. We can handle success, failure or both.
1
let
promise
=
new
Promise
((
resolve
,
reject
)
=>
{
2
// when success, resolve
3
let
value
=
'success'
;
4
resolve
(
value
);
5
6
// when an error occurred, reject
7
reject
(
new
Error
(
'Something happened!'
));
8
});
9
10
promise
.
then
(
response
=>
{
11
console
.
log
(
response
);
12
},
error
=>
{
13
console
.
log
(
error
);
14
});
15
// success
Our code above never executes reject function, so we can omit it for simplicity:
1
let
promise
=
new
Promise
(
resolve
=>
{
2
let
value
=
'success'
;
3
resolve
(
value
);
4
});
5
6
promise
.
then
(
response
=>
{
7
console
.
log
(
response
);
// success
8
});
Handlers passed to promise.then don’t just handle the result of the previous promise-they return is turned into a new promise.
1
let
promise
=
new
Promise
(
resolve
=>
{
2
let
value
=
'success'
;
3
resolve
(
value
);
4
});
5
6
promise
.
then
(
response
=>
{
7
console
.
log
(
response
);
// success
8
return
'another success'
;
9
}).
then
(
response
=>
{
10
console
.
log
(
response
);
// another success
11
});
You can see, that the code based on promises is always flat. No more callback hell.
If you are only interested in rejections, you can omit the first parameter.
1
let
promise
=
new
Promise
((
resolve
,
reject
)
=>
{
2
let
reason
=
'failure'
;
3
reject
(
reason
);
4
});
5
6
promise
.
then
(
7
null
,
8
error
=>
{
9
console
.
log
(
error
);
// failure
10
}
11
);
But is a more compact way of doing the same thing-catch() method.
1
let
promise
=
new
Promise
((
resolve
,
reject
)
=>
{
2
let
reason
=
'failure'
;
3
reject
(
reason
);
4
});
5
6
promise
.
catch
(
err
=>
{
7
console
.
log
(
err
);
// failure
8
});
If we have more than one then() call, the error is passed on until there is an error handler.
1
let
promise
=
new
Promise
(
resolve
=>
{
2
resolve
();
3
});
4
5
promise
6
.
then
(
response
=>
{
7
return
1
;
8
})
9
.
then
(
response
=>
{
10
throw
new
Error
(
'failure'
);
11
})
12
.
catch
(
error
=>
{
13
console
.
log
(
error
.
message
);
// failure
14
});
We can even combine one or more promises into new promises without having to take care of ordering of the underlying asynchronous operations yourself.
1
let
doSmth
=
new
Promise
(
resolve
=>
{
2
resolve
(
'doSmth'
);
3
}),
4
doSmthElse
=
new
Promise
(
resolve
=>
{
5
resolve
(
'doSmthElse'
);
6
}),
7
oneMore
=
new
Promise
(
resolve
=>
{
8
resolve
(
'oneMore'
);
9
});
10
11
Promise
.
all
([
12
doSmth
,
13
doSmthElse
,
14
oneMore
15
])
16
.
then
(
response
=>
{
17
let
[
one
,
two
,
three
]
=
response
;
18
console
.
log
(
one
,
two
,
three
);
// doSmth doSmthElse oneMore
19
});
Promise.all() takes an array of promises and when all of them are fulfilled, it put their values into the array.
There are two more functions which are useful:
- Promise.resolve(value) - it returns a promise which resolves to a value or returns value if value is already a promise
- Promise.reject(value) - returns rejected promise with value as value
Pitfall
Promises have its pitfall as well. Let’s image that when any exception is thrown within a then or the function passed to new Promise, will be silently disposed of unless manually handled.
set, map, weak
Sets and maps will be (are) finally available in ES6! No more spartan way to manipulate data structures. This chapter explains how we can deal with Map, Set, WeakMap and WeakSet.
Map
Maps are a store for key / value pairs. Key and value could be a primitives or object references.
Let’s create a map:
1
let
map
=
new
Map
(),
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
.
set
(
0
,
'val1'
);
8
map
.
set
(
'1'
,
val2
);
9
map
.
set
({
key
:
2
},
val3
);
10
11
console
.
log
(
map
);
// Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object \
12
{
key
:
'value'
}}
We can also use a constructor to create the sam map, based on array param passed to the constructor:
1
let
map
,
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
=
new
Map
([[
0
,
'val1'
],
[
'1'
,
val2
],
[{
key
:
2
},
val3
]]);
8
9
console
.
log
(
map
);
// Map {0 => 'val1', '1' => 'val2', Object {key: 2} => Object \
10
{
key
:
'value'
}}
To get a value by using a key, we have to use a get() method to do it (surprising):
1
let
map
=
new
Map
(),
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
.
set
(
0
,
'val1'
);
8
map
.
set
(
'1'
,
val2
);
9
map
.
set
({
key
:
2
},
val3
);
10
11
console
.
log
(
map
.
get
(
'1'
));
// val2
To iterate over the map collection, we can use built-in forEach method or use new for..of structure:
1
// forEach
2
let
map
=
new
Map
(),
3
val2
=
'val2'
,
4
val3
=
{
5
key
:
'value'
6
};
7
8
map
.
set
(
0
,
'val1'
);
9
map
.
set
(
'1'
,
val2
);
10
map
.
set
({
key
:
2
},
val3
);
11
12
map
.
forEach
(
function
(
value
,
key
)
{
13
console
.
log
(
`
Key
:
$
{
key
}
has
value
:
$
{
value
}
`
);
14
// Key: 0 has value: val1
15
// Key: 1 has value: val2
16
// Key: [object Object] has value: [object Object]
17
});
1
// for..of
2
let
map
=
new
Map
(),
3
val2
=
'val2'
,
4
val3
=
{
5
key
:
'value'
6
};
7
8
map
.
set
(
0
,
'val1'
);
9
map
.
set
(
'1'
,
val2
);
10
map
.
set
({
key
:
2
},
val3
);
11
12
for
(
let
entry
of
map
)
{
13
console
.
log
(
`
Key
:
$
{
entry
[
0
]}
has
value
:
$
{
entry
[
1
]}
`
);
14
// Key: 0 has value: val1
15
// Key: 1 has value: val2
16
// Key: [object Object] has value: [object Object]
17
};
We can also use a couple of methods to iterate:
- entries() — get all entries
- keys() — get only all keys
- values() — get only all values
To check if value is stored in our map, we can use has() method:
1
let
map
=
new
Map
(),
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
.
set
(
0
,
'val1'
);
8
map
.
set
(
'1'
,
val2
);
9
map
.
set
({
key
:
2
},
val3
);
10
11
console
.
log
(
map
.
has
(
0
));
// true
12
console
.
log
(
map
.
has
(
'key'
));
// false
To delete entry, we have delete() method:
1
let
map
=
new
Map
(),
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
.
set
(
0
,
'val1'
);
8
map
.
set
(
'1'
,
val2
);
9
map
.
set
({
key
:
2
},
val3
);
10
11
console
.
log
(
map
.
size
);
// 3
12
13
map
.
delete
(
'1'
);
14
15
console
.
log
(
map
.
size
);
// 2
..and to clear all collection, we use clear() method:
1
let
map
=
new
Map
(),
2
val2
=
'val2'
,
3
val3
=
{
4
key
:
'value'
5
};
6
7
map
.
set
(
0
,
'val1'
);
8
map
.
set
(
'1'
,
val2
);
9
map
.
set
({
key
:
2
},
val3
);
10
11
console
.
log
(
map
.
size
);
// 3
12
13
map
.
clear
();
14
15
console
.
log
(
map
.
size
);
// 0
Set
It’s a collection for unique values. The values could be also a primitives or object references.
1
let
set
=
new
Set
();
2
3
set
.
add
(
1
);
4
set
.
add
(
'1'
);
5
set
.
add
({
key
:
'value'
});
6
7
console
.
log
(
set
);
// Set {1, '1', Object {key: 'value'}}
Like a map, set allows to create collection by passing an array to its constructor:
1
let
set
=
new
Set
([
1
,
'1'
,
{
key
:
'value'
}]);
2
3
console
.
log
(
set
);
// Set {1, '1', Object {key: 'value'}}
To iterate over sets we have the same two options — built-in forEach function or for..of structure:
1
// forEach
2
let
set
=
new
Set
([
1
,
'1'
,
{
key
:
'value'
}]);
3
4
set
.
forEach
(
function
(
value
)
{
5
console
.
log
(
value
);
6
// 1
7
// '1'
8
// Object {key: 'value'}
9
});
1
// for..of
2
let
set
=
new
Set
([
1
,
'1'
,
{
key
:
'value'
}]);
3
4
for
(
let
value
of
set
)
{
5
console
.
log
(
value
);
6
// 1
7
// '1'
8
// Object {key: 'value'}
9
};
Set doesn’t allow to add duplicates.
1
let
set
=
new
Set
([
1
,
1
,
1
,
2
,
5
,
5
,
6
,
9
]);
2
console
.
log
(
set
.
size
);
// 5!
We can also use has(), delete(), clear() methods, which are similar to the Map versions.
WeakMap
WeakMaps provides leak-free object keyed side tables. It’s a Map that doesn’t prevent its keys from being garbage-collected. We don’t have to worry about memory leaks.
If the object is destroyed, the garbage collector removes an entry from the WeakMap and frees memory.
Keys must be objects.
It has almost the same API like a Map, but we can’t iterate over the WeakMap collection. We can’t even determine the length of the collection because we don’t have size attribute here.
The API looks like this:
1
new
WeakMap
([
iterable
])
2
3
WeakMap
.
prototype
.
get
(
key
)
:
any
4
WeakMap
.
prototype
.
set
(
key
,
value
)
:
this
5
WeakMap
.
prototype
.
has
(
key
)
:
boolean
6
WeakMap
.
prototype
.
delete
(
key
)
:
boolean
1
let
wm
=
new
WeakMap
(),
2
obj
=
{
3
key1
:
{
4
k
:
'v1'
5
},
6
key2
:
{
7
k
:
'v2'
8
}
9
};
10
11
wm
.
set
(
obj
.
key1
,
'val1'
);
12
wm
.
set
(
obj
.
key2
,
'val2'
);
13
14
console
.
log
(
wm
);
// WeakMap {Object {k: 'v1'} => 'val1', Object {k: 'v2'} => 'va\
15
l2
'
}
16
console
.
log
(
wm
.
has
(
obj
.
key1
));
// true
17
18
delete
obj
.
key1
;
19
20
console
.
log
(
wm
.
has
(
obj
.
key1
));
// false
WeakSet
Like a WeakMap, WeakSet is a Seat that doesn’t prevent its values from being garbage-collected. It has simpler API than WeakMap, because has only three methods:
1
new
WeakSet
([
iterable
])
2
3
WeakSet
.
prototype
.
add
(
value
)
:
any
4
WeakSet
.
prototype
.
has
(
value
)
:
boolean
5
WeakSet
.
prototype
.
delete
(
value
)
:
boolean
WeakSets are collections of unique objects only.
WeakSet collection can’t be iterated and we cannot determine its size.
1
let
ws
=
new
WeakSet
(),
2
obj
=
{
3
key1
:
{
4
k
:
'v1'
5
},
6
key2
:
{
7
k
:
'v2'
8
}
9
};
10
11
ws
.
add
(
obj
.
key1
);
12
ws
.
add
(
obj
.
key2
);
13
14
console
.
log
(
ws
);
// WeakSet {Object {k: 'v1'}, Object {k: 'v2'}}
15
console
.
log
(
ws
.
has
(
obj
.
key1
));
// true
16
17
delete
obj
.
key1
;
18
19
console
.
log
(
ws
.
has
(
obj
.
key1
));
// false