- Only work in Strict mode
- Optimize code so that Tail Position is a Tail Call
// How many times recursion can be called
function foo(num) {
try {
return foo( (num || 0) + 1 );
} catch(e) {
return num;
}
}
console.log(foo());
// Fibonacci with proper tails calls
function fibonacci(x, y, limit, index) {
if(arguments.length == 1) {
if(x) {
return fibonacci(0, 1, x, 1);
}
return 0;
} else {
if(index < limit) {
return fibonacci(y, (x+y), limit, ++index);
}
return y;
}
}
console.log(fibonacci(3));
console.log(fibonacci(10));
console.log(fibonacci(12495));
- Variable hoisting.
let
uses block scope, the curly braces are the new scope.
- Can't dountle declare a variable with
let
.
- Temporal Dead Zone, no hoisting - throws an error if used before defined.
const
variable cannot be reassigned, scoped like let
.
- Block functions declare the same lexical scope as an if-true
let foo = 2;
if (true) {
let bar = 2;
}
console.log(bar); // Throws error
for(var j = 0; j < 10; j++) {
console.log(j); // logs 0-9
}
console.log(j); // ReferenceError
const a = 0;
a = 1; // SyntaxError: Assignment to constant variable
function doTihng() {
// some code
{ // lay down a new scope
let a = 0;
}
}
- Same ideas as splats
- Catches all parameters in an array, a proper array
- Includes only non-specified arguments, unlike
arguments
- Rules of Rest Parameters
- One per function
- Must be the last parameter
- Can't use
arguments
- No Default values
function myFunction(first, last, ...other) {
// code
console.log(other.joint(' '));
}
function myFunction('I', 'can', 'haz', 'teh', 'arguments');
var nums = [1, 2, 3];
console.log(nums); // [1, 2, 3]
console.log(..nums); // 1, 2, 3
let nums = [1, 2, 3];
let abcs = ['a', 'b', 'c'];
let alphanum = [...nums, ...abcs];
- New syntax to deconstruct Objects and Arrays
- Pattern matching
- Braces on the lest side on an assignment expression
- Creates separate variables
- Can alias the names using a colon
- Can be as a parameter to a function
- Can receive default values
}
on the left mean destructure the right
- By default, properties that aren’t found will be undefined, just like when accessing properties on an object with the dot or bracket notation.
- If you’re trying to access a deeply nested property of a parent that doesn’t exist, then you’ll get an exception, though.
- Destructuring can nest
let {city, state, zip} = getAddress();
// Aliasing the properties names
let {city: c, state: s, zip: z} = getAddress();
var person = {name: 'Aaron', age: 35};
displayPerson(person);
function displayPerson({name = "No Name Provided", age = 0}) {
// destructures the parameters
// do something
}
let {a: x} = {}; // undefined
let {a: x = 1} = {} // 1
let {name, age, address: {city, state, zip}} = person;
- Similar to destructuring objects
var nums = [1,2,3,4,5,6,7,8,9,10];
var [first, second,,,,,,,, tenth] = nums;
console.log(first, second, tenth);
// swap variables
[b, a] = [a, b]:
// as part of the method signature
var nums = [1, 2, 3, 4];
doSomething(nums);
function doSomething([first, second, ...others]) {
console.log(first);
console.log(second);
console.log(others);
}
// nested arrays
var nums = [1, 2, [3, 4, [5, 6]]];
var [one,,[three,,[,six]]] = nums;
// pattern errors
let [x] = [2, 3]; // x = 2
let [x] = {'0': 4}; // throw
let [x, y, z] = [1, 2]; // z = undefined
- Like lambdas in Ruby or Fat arrow functions from CoffeeScript
- Parentheses for the parameters
- No braces for single-line arrow function bodies
- Single-line arrow, implicit return statement
- Lexical binding of
this
- Can't alter
this
on arrow function, even if used with call
.
- Still use functions
var fn1 = function() {return 2;};
var fn2 = () => 2;
// parameter rules
var x;
x = () => {}; // No parameters, MUST HAVE PARENS
x = (val) => {}; // One parameter w/ parens , OPTIONAL
x = val => {}; // One parameter w/o parens, OPTIONAL
x = (y, z) => {}; // Two or more parameters, MUST HAVE PARENS
x = y, z => {}; // Syntax Error, must wrap with parens
// body rules
var square;
square = x => x * x; // Body w/o braces
square = x => { return x * x}; // Body w/ braces
// use to replace anonymous functions
let nums = [1, 2, 3];
let res = nums.map( n => n * n );
let key_maker = val => ({key: val});
console.log(key_maker(100)); // Logs {key: 100}
var Widget = {
init: function() {
// The arrow function binds this to the object
document.addEventListener("click", (event) => {
this.doSomething(event.type);
}, false);
},
doSomething: function(type) {
console.log("Handling " + type + " event");
}
};
Widget.init();
var f = () => {};
typeof f; // function
Object.getPrototypeOf(f); // [Function:f]
new f(); // TypeError: f is not a constructor
function Widget() {
this.id = 123;
// log is defined using the arrow function, this is bound lexically
this.log = () => {
console.log('Widget Log', this.id);
}
}
var pseudoWidget = { id: 345 };
// call will still use Widget() for this
new Widget().log.call(pseudoWidget); // Widget Log 123
- Used to help in data-proofing tasks
undefined
will trigger the default assignment
- The default value can be assigned to a function call
- Assignment happens lexically
- Not all parameters need default values
- No default parameters with rest parameters
- Default values don't appear in
arguments
function sayHello(name = "World") {
console.log("Hello " + name + "!");
}
sayHello("Vadim"); // Hello Vadim!
sayHello(""); // Hello !
sayHello(); // Hello World!
sayHello(undefined); // Hello World!
function myFunction(id = getRand()) {
// code
}
function die() { throw "x" }
function test(a = die()) {
console.log("Didn't die");
}
text(); // throws an error
var x = "INIT";
// x is scoped lecially within the function
function test(a = x) {
var x; // x is undefined
return a;
}
test(); // undefined
function test(a = 1, b = 2, c = 3) {
console.log(arguments.length);
}
test(); // 0
test(1); // 1
test(1, 2, 3, 4, 5); // 5
- The class system is only syntactic sugar over things that are already in the language
- Allows for constructors
- Can have private properties via
Symbol()
- Have Setter and Getter properties
- Can have class properties
- Classes can be extended, using the
extend
and super
keywords
- Classes do not hoist
- If constructor is missing it will do the default behavior, call constructor of the super class
function Foo() {
// code
}
// same as
class Foo {
// code
}
// Symbol is used to generate an obscure property name
var monsterHealth = Symbol();
class Monster {
// classes have constructors
constructor(name, health) {
this.name = name;
this[monsterHealth] = health;
// Class Property
Monster.allMonsters.push(this);
}
// getter property
get isAlive() {
return this[monsterHealth] > 0;
}
// setter property
set isAlive(alive) {
if (!alive) {
this[monsterHealth] = 0;
}
}
}
var kevin = new Monster('Kevin', 100);
kevin.isAlive; // used getter -> true
kevin.isAlive = false; // used setter
kevin.isAlive; // false
Monster.allMonsters = [];
class Monster {
constructor(name, health) {
this.name = name;
this[monsterHealth] = health;
}
// code
}
class Godzilla extends Monster {
constructor() {
super('Godzilla', 10000);
}
}
// extends can use any function
class MySocket extends getClass() {
// code
}
function getClass() {
if(isIE()) {
return IEWebSocketImpl;
}
return WebSocket;
function isIE() {
return false;
}
}
- Three new collections:
set
, map
, weakmap
set
is like Array
- unique collection of things
- no typecasting on uniqueness
- items are enumerable
map
is like key-value Object
- no typecasting on key
- objects can acts as keys
- Keys: primitives / objects / functions
- Must use the same key, not equal key
- Map has
.entries()
method to interate over contents
- Map is aware of
.size
- Can cause memory leaks if used to store DOM elements
weakmap
is like map, but different
- Very similar to a
map
- Does not have a size
- Stores a weak element to the DOM element, can solve the memory leak problem
- Does not prevent garbage collection
var set = new Set();
set.add(1);
set.add(2);
set.add(3);
set.size; // 3
var set = new Set();
set.has(1); // false
set.add(1);
set.has(1); // true, check for inclusion
set.clear(); // to clear the set
set.has(1); // false
set.add(1);
set.add(2);
set.size; // 2
set.delete(2); // delete an item
set.size; // 1
var items = new Set([1,2,3,4,5]);
for (let num of items) {
console.log(num);
}
// map
var json = { name: "Aaron" };
var map = new Map();
// map.set method
map.set('name', 'Aaron');
// map.get method
map.get('name'); // Aaron
var user = { name: 'Aaron', id: 1234 },
var userHobbyMap = new Map();
// complex object as the key of a map
userHobbyMap.set(user, ['Fishing', 'Hiking']);
userHobbyMap.get(user);
var user = { name: 'Aaron', id: 1234 };
var weak = new WeakMap();
weak.has(user); // false
weak.set(user, {lastAction: newDate()});
weak.has(user); // true
weak.delete(user);
weak.has(user); // false
weak.has(user); // true
weak.set(user, {lastAction: newDate()});
weak.clear();
weak.has(user); // true
- Solves the problem of nested callback structure for asyc code
- Good for Ajax, WebSockets, Read / Write localStorage, Write lots to DOM, Show a spinner for loading etc.
- Promises constructors have 2 methods:
resolve
, reject
- Promise instances can be in 1 of 4 states:
- fulfilled - successfully resolved -
1
- rejected -
2
- pending - hasn't resolved or rejected yet -
undefined
- settled - fulfilled or rejected -
1
or 2
- Promise
then
function is not executed until the promise resolved
- Static Promise Methods:
Promise.all(iterable);
- wait until all settle
Promise.race(iterable);
- wait until 1 settles
Promise.reject(reason);
- create a promise that is already rejected
Promise.resolve(reason);
- create a promise that is already resolved
// Promise constructor
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then...
if (/* everything is ok */) {
resolve('Stuff worked!');
} else {
reject(Error('It broke'));
}
});
return promise; // Give this to someone
// Promise instance
promise.then(function(result){
console.log(result); // Stuff worked!
}, function(err) {
console.log(err); // Error: it broke
});
// ajax example, get method with promises
function get(url) {
return new Promise(function(resolve, reject){
$.get(url, function(data){
resolve(data);
})
.fail(function() {
reject();
});
});
}
// usage
get('users.all').then(function(users){
myController.users = users;
}, function(){
delete myController.users;
});
// or .catch instead of second handler in .then
get('users.all')
.then(function(users){
myController.users = users;
})
.catch(function(){
delete myController.users;
});
// combine promises
var usersPromise = get('users.all');
var postsPromise = get('posts.everyone');
// wait until both are settled
Promise.all([usersPromise, postsPromise])
.then(function(results){
myController.users = results[0];
myController.posts = results[1];
}, function() {
delete myController.users;
delete myController.posts;
});
// If the response in a string, can use additional .then
get('users.all').then(function(userString){
return JSON.parse(userString);
}).then(function(users){
myController.users = users;
});
// even more compact
get('users.all').then(JSON.parse).then(function(users){
myController.users = users;
});
- Enable JS to be more collaborative on long running processes
- Help to remove blocking operations
- Has a
*
either on the function name or at the end of the function*
keyword
- Uses the
yield
keyword, kinda like return
, can return or get information
for in
loops continue while done: false
// basic syntax, * before the name of the function
function *myGen() {
// ...
yield 1;
yield 1;
return 3;
}
// or after the function
function* myGen() {
// ...
yield 1;
yield 1;
return 3;
}
function *three() {
yield 1;
yield 1;
return 3;
}
// Calling `three()` does not execute the function
// It returns a generator iterator
var geni = three();
// Starts the excution
geni.next(); // Return { value: 1, done: false }
geni.next(); // Return { value: 2, done: false }
geni.next(); // Return { value: 3, done: true }
geni.next(); // Return { value: undefined, done: true }
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1, 2, 3, 4, 5
// 6 does not get logged, because done: true
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield(y / 3);
return (x + y + z);
}
var genit = foo(5);
genit.next(); // {value: 6, done: false}
genit.next(12); // {value: 8, done: false}
genit.next(13); // {value: 42, done: true}