Esnext

https://github.com/lukehoban/es6features https://medium.freecodecamp.org/es5-to-esnext-heres-every-feature-added-to-javascript-since-2015-d0c255e13c6e https://blog.bitsrc.io/6-tricks-with-resting-and-spreading-javascript-objects-68d585bdc83

ES6 is ES2015, ES7 is published on 2016 and so on (latest is ES#{curent_year%2010+(month > june) ? 1 : 0 }). ES6 features:

  • defining variable was using var a (in strict mode, which is default in ES6, error is raised for using undefined variables). If it was defined inside function, it was visible only inside it, otherwise global scope. It should be declarad at the beggining of a function, because it can be still referecenced before declaration and used like a function parameter (hoisting). ES6 introduced let and const with block scope. For example inside if () { let myVar=1; } (myVar is not accesible outside of if block). Also you can NOT use variable before we let variable. Also you can NOT redeclare same variable name (or if name is same as param declaration function f(myName) { let myName; }. Notice difference in

    for (var i = 1; i< 5; i++) {
      console.log(i)
      setTimeout(function() {
        console.log(i) # here we have i = 5,5,5,5 since same variable is used
        # if we used let i=1;i<5;i++ than it will be different variable: 1,2,3,4
      }, 1000)
    }
    

    const MY_CONST = document.querySelect(".my-class"); have block scope, and it should be used for all non volatile variables. If you use object than nested values can change const k = { a: 3 }; k.a = 4

  • interpolation of template literals with backticks and curly braces
    var name = "Duke";
    console.log(`Hello ${name}`);
    
    const multiLine = `this
    is
    multi
    line`
    
    // use trim() so you can go to new line at beggining
    let n = `
    hi
    bye`.trim()
    
  • arrow functions are abbreviated syntax for anonymouse functions. If there is only one parameter than you can omit parenthesis, if there are no params than you can replace parenthesis with _. One liner syntax (without curly braces) use implicit return, for other you need explicit return if you need. If returning object, remember to wrap inside parethenses

    var sayHello = (name) => ({name: `Hello ${name}!`})
    var sayCiao = name => {
      return `Ciao ${name}!`
    }
    
  • arrow functions automatically bind this
    class MyClass {
      regular() {
        return function() {
          console.log('regular this: ', this) // undefined
        }
      }
      arrow() {
        return () => {
          console.log('arrow this: ', this) // MyClass
        }
      }
    }
    

    Note that this inside arrow functions does not bind to current object for object methods. It binds to outer this (it is inherited from the execution context). So this is usefull only if you have function inside method that is defined in clasical way, ie do not use arrow functions for methods, use old functions (when dynamic context is not needed). Also for jQuery each $.each(functions() {$(this).select2()}) use normal function instead of arrow function.

    let o = {
      name: 'name',
      myMethod: function() {
        setTimeout( () => {
          console.log(this.name) // o.name
        }, 3000)
      }
    }
    
  • destructuring with default parameters values and renaming https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

    # destructuring arrays
    let [, month, date = 1] = '2010-10-11'.split('-')
    # month = '10' and date = '11'
    let [f, s, ...rest] = [1, 2, 3, 4]
    # f=1, s=2, rest = [3,4]
    
    # destructuring objects
    let { name, age, gender:sex = 'male' } = { name: 'Duke', age: '33', gender: 'male' }
    # is the same as
    let name = o.name
    let age = o.age
    let sex = o.gender # here we also renaming and have default value
    

    You can also use destructuring when defining methods

    function call({
      name: 'My Name',
      phone: '123123',
    } = {}) {
      console.log(name + ' ' + phone)
    }
    

    You can also destructure nested objects in function params

    var car = {
      model: 'bmw 2018',
      engine: {
        turbo: true,
        vin: 12345
      }
    }
    
    // es6 shorthand instead of
    // { vin: vin }
    // you can use just
    // { vin }
    const modelAndVIN = ({model, engine: {vin}}) => {
      console.log(`model: ${model} vin: ${vin}`);
    }
    
    modelAndVIN(car); // => model: bmw 2018  vin: 12345
    

    There exists rest ... parameter which convers to array

    const sum = (...a) => a.reduce((sum, current) => sum + current, 0)
    

    and same operator ... inside method call is called spread and it expands explode array to params (coffee calls this splats f(items...))

    let a = [1, 2, 3]
    f(...a)
    # is the same as
    f(1, 2, 3)
    

    you can also spread objects let d = { a: 3}; let db = { ...d, b: 3}. But if object has methods, it wont be expanded. If you spread two objects, later will override: let object1 = { a:1, b:2 }; let object2 = { b:30, c:40}; let merged = {…object1, …object2} // {a:1, b:30, c:40}. Merging nested properties you need to anotate each property when spreading untill you have one level object

    let a = { p: { x: 1 } }
    let b = { p: { y: 2 } }
    let m = { p: { ...a.p, ...b.p } }
    

    for default values you can use something like this so passed.nested_parameter does not override whole object o.nested but only properties of o.nested

    o = {
      parameter: 1,
      ...passed,
      nested: {
        nested_parameter: 2,
        ...passed.nested_parameter
      },
    }
    

    Note that is you have only { parameter: 1, ...passed } than it will override whole o.nested with passed.nested. For array you can clone (duplicate) with const cloned = [...array] You can de-duplicate array with let arr = [1, 1, 2, 2, 3, 3]; let deduped = [...new Set(arr)] // [1, 2, 3].

  • default parameter values function g(a=2){}. It can be combined with default destructing params function g({a=1, b}={b:2}){} and g({a: 2}) (ok b=2), g() (ok a=1, b=2), but g({}) error, b is required.
  • if you need some param to be required you can use this trick

    const required = () => {throw new Error('Missing parameter')};
    
    //The below function will trow an error if either "a" or "b" is missing.
    const add = (a = required(), b = required()) => a + b;
    
    add(1, 2) //3
    add(1) // Error: Missing parameter.
    
  • for(i=0;i<cars.length;i++) {} is not concise cars.forEach(myFunction) is concise but can not break out of the loop. ES6 gives for(let ... of ..) {} (for values in array or string) and for..in (for object property names) is concise and can break. for in iterates over enumerable properties of an object in arbitrary order, for of iterates over values of iterable object. Use .entries() to get index
    for(let [i, car] of cars.entries()) { car }`
    
    for(let id in data) {
      let value = data[id]
    }
    

    To convert HtmlCollection to array you can use let array = Array.from(htmlCollection) and array.forEach(item) => { console.log(item) } To create new array you can use Array(9).fill(null).

  • array.reduce(function, init_value) can be used as filtering if init_value is array, than function takes two arguments (init_value, array_item)
    const numbers = [10, 20, 30, 40];
    
    const doubledOver50 = numbers.reduce((finalList, num) => {
      num = num * 2; //double each number (i.e. map)
      //filter number > 50
      if (num > 50) {
        finalList.push(num);
      }
      return finalList;
    }, []);
    doubledOver50; // [60, 80]
    
  • class Person can be used to instantiate objects new Person(). For initialization you can use constructor(name) { this.name = name } Instead of hash method full_name: function() {} we can write full_name () {} This is shorthand for object method definitions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions I like to put space since it does not look like we are calling a function.

    Extend classes class Employee extends Person {} so we don’t need to write Employee.prototype = new Person (or var e = { __proto__: person }. Inside class we can define instance and static methods. Static methods are called without instantiating their class (used for utility functions). Multiple inheritance can be achieved with mix-ins https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes#Mix-ins You can use getter (or setter) which binds an object property to a function that will be called when that property is looked up (or attempt to set that property e.full_name =)

    class Empoyee extends Person {
      constructor(name) {
        super(name);
    
        this.proffessional_name = name;
      }
      get full_name() {
        return this.name;
      }
      set full_name(name) {
        this.name = name;
      }
    
      static myUtilityFunction(uppercase) {
        return uppercase;
      }
    
      hello() {
        return `Hi, I'm ${name}`
      }
    }
    
  • new data structure called Map and WeakMap (keys are objects, not plain values like number, string or symbol)

    var map = new Map();
    map.set('year', '123');
    map.get('year'); // '123'
    
    # or use initialization
    map = new Map([['color', 'red'],['owner', 'me']])
    # or
    map = new Map(Object.entries(object))
    # convert back to object
    object = Object.fromEntries(map)
    
    # some methods
    map.size
    map.keys()
    map.values()
    map.delete('color')
    map.clear()
    
  • new data structure called Set and WeakSet
  • new string functions 'asd'.startsWith('a');'asd'.endsWith('a');'asd'.includes('as');'a'.repeat(3);
  • new Array functions
  • Array.from([1,2,3], x => x + x)
  • Array.find( user => user.age > 15) same as Array.findIndex but returns object instead of index
  • [1,2,3].filter( (n) => n > 2 ). This can be used as .compact in ruby for example sum of digits
      sum = value
          .toString()
          .split('')
          .filter(Number)
          .map(Number)
          .reduce(function (a, b) {
              return a + b;
          }, 0);
    
  • [1,2].includes(3) returns false

  • property value shorthands, when you define object and you already have variable with same name as key, you can use

    const name = 'dule'
    const anPerson = {
      name
    }
    // is the same as
    const aPerson = {
      name: name
    }
    

    it also exists for methods

    const aPerson = {
      speak (word) {},
      // same as
      speak: function (word) {},
      // do not use arrow because you can not access this
      // speak: () => {},
    }
    
  • computed object property names (you can use variable as key)

    const name = 'dule'
    
    const aPerson = {
      'mile': 'value',
      [name]: 'dule is key and value', // same as 'dule': 'dule is key and value'
      [name+'car']: 'value', // { 'dulecar': 'value' }
    }
    
  • generators
    function *calculator(input) {
      var doubleThat = 2 * (yield (input / 2))
      var another = yield (doubleThat)
      return (input * doubleThat * another)
    }
    
    const calc = calculator(10)
    // start
    > c.next()
    { value: 5, done: false }
    > c.next(7)
    { value: 14, done: false }
    > c.next(100)
    { value: 14000, done: true }
    

ES2017

  • in function declarations and function calls you can use trailing commas doSomething(p1, p2,)

ES2019 ESNext

  • [1,[2, [3]]].flat(Infinity) returns [1, 2, 3]
  • instead of map and flat you can use flatMap
    ['My dog', 'is awesome'].flatMap(words => words.split(' ')) 
    // [ 'My', 'dog', 'is', 'awesome' ]
    
  • create object from entries Object.fromEntries(Object.entries(person)) and usage is in transforming the objects
    const object = { x: 42, y: 50, abc: 9001 }
    const result = Object.fromEntries(
      Object.entries(object)
        .filter(([key, value]) => key.length === 1)
        .map(([key, value]) => [key, value * 2])
    )
    // { x: 84, y: 100 }
    
  • class property can be marked as private with hash
    class Counter {
      #count = 0
      get value() {
        return this.#count
      }
      increment() {
        this.#count++
      }
    }
    
    const counter = new Counter()
    counter.#count // SyntaxError
    
  • underscore in numbers: 1_000.

Promise

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises

Promise is an object representing eventual completion or failure of an asynchronous operation. We can use chaining to combine two callback function, so promise.then(callback1).then(callback2) means than callback1 will be executed and than callback2. promise1 = promise.then(successCallback, failureCallback) returns promise which is completion of promise and successCallback or failureCallback which also can be asynchronous functions returning a promise (in which case any callbacks added to promise1 will be queued behind those promises returned by successCallback or failureCallback).

If we attach handler to already settled promise, handler will be performed at the first asynchronous opportunity (stack has cleared and clock tick has passed, like setTimeout(handler, 10)).

.catch(failureCallback) is short-hand for then(null, failureCallback). Callback param is result of previous promise so using arrow functions

doSomething()
  .then(result => doSomethingElse(result))
  .then(elseResult => doThirdThing(newResult))

In .catch().then() if catch does not throw an error, .then will be performed

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise a Promise is a proxy for a value not necessarily known when the promise is created. It can be in one of three states: pending (initial), fulfilled or rejected. When it is either fulfilled or rejected (also known as settled) than all associated handlers queued up using then are called. A promise is fulfilled if promise.then(f) will call f as soon as possible A promise is rejected if promise.then(null, r) will call r as soon as possible. A promise is pending if it is neither fulfilled nor rejected.

https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md Promises have two exclusive fates: resolved (when trying to resolve or reject it has no effect on promise) and unresolved.

Signature of handler functions are simple, they accept a single parameter of any type and termination condition determines the settled state of next promise in the chain. Termination with throw nextReason creates a rejected state, other termination, for example return nextValue creates resolved state. If nextValue is another promise object, it is dynamically inserted into the chain. In this case it is resolved but still is not settled.

Creating a new promise https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise new Promise(executor) constructor is used to wrap functions that do not already support promises (like setTimeout, XMLHttpRequest). Argument executor is function that accepts two functions function(resolutionFunc, rejectionFunc) {} which we use in callbacks (in setTimeout or XMLHttpRequest.onload) which are tethered to the promiseObj. Return value is not important, side effect is that promiseObj is resolved and output is communicated by calling resolutionFunc or rejectionFunc.

const promiseObj = new Promise((resolutionFunc, rejectionFunc) => {
  try {
    setTimeout(() => {
      resolutionFunc('foo')
    }, 300)
  }
  catch(err) {
    rejectionFunc('bar')
  }
})

Here is example of function which returns a promise

function myAsyncFunction(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open("GET", url)
    xhr.onload = () => resolve(xhr.responseText)
    xhr.onerror = () => reject(xhr.statusText)
    xhr.send()
  });
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then When return value of handler is some value, the promise returned by .then() gets resolved with returned values as its value When handler throes an error, promise returned by .then() gets rejected with the thrown error as its value When it returns another promise (fulfilled, rejected or pending) than promise returned by .then() gets fulfilled, rejected or is in pending state.

Promise.all([p1, p2]).then will be executed when all are resolved Promise.race([p1, p2]).then will be executed when first (any) is resolved Both of this methods will be rejected if some of p1 is rejected - reject early.

await Promise.allSettled([p1, p2]) all are finished (some failed or succeeded). Promise.any([p1, p2]) is similar to race but does not reject early when one of the promises rejects. We end up in catch block when all were rejected.

Async/Await

const doSomethingAsync = () => {
  return new Promise(resolve => {
    setTimeout( () => resolve('Here is thing I built after some delay'), 2000);
  })
}

const doSomething = async () => {
  console.log(await doSomethingAsync())
}

Using async to a function means that it will return promise, so those two functions are the same:

const aFunction = async () => {
  return 'test'
}

const aFunction = () => {
  return Promise.resolve('test')
}

aFunction().then(console.log)

ES2018 there is for await (const line of readLines(filePath)) { log(line) }

.finnaly() is executed regardless of the successful or not promise.

Modules

Import bindings which are exported by another module (js file). Module is just js script (no need for special keyword) but everything defined inside module is local to the module, except when you use export. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

function myModule() {
  this.hello = function() {
    return 'hello
  }
}

module.exports = myModule

Note that for Webpack you can use CommonJS module and use with require

var myModule = require('myModule');

var myModuleInstance = new myModule();
myModuleInstance.hello(); // 'hello!'

Using Asynchronous Module Definition AMD can load modules async using define

define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
  console.log(myModule.hello());
});

Universal Module Definition UMD combines AMD and CommonJS. But ECMASCRIPT 6 introduce import and export methods which replace all those functions… So if you write libs for web you should create all variations so it can be used in webpack or directly https://medium.com/@kelin2025/so-you-wanna-use-es6-modules-714f48b3a953

You can export any top level function, class, var, let or const. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

// lim/math.js
export function sum(x, y) {
  return x + y;
}

// you could use export list
export { sum, pi };

// but the best is to use export multiple times
export CONST PI = 3.141593
export CONST MY_KEY = '3.141593'

You can name the export as default and in this case you can use anonymous functions (otherwise you have to name each export so it can be distinquished from other exports). In case export is default than you can import only one item.

let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};
// or better use shorthand
export default myObject

# do not export default and list
# export default { myobject }

For index files you can export each file

// interfaces/index.ts
export * from './person.interface'
export * from './sport.interface'

and import all in one line

import { Person, Sport } from './interfaces/index'

Note that with import you should use curly brackets if there is no default export, and check that you used correct variable name (same as in the export command). Note that when using webpack, when export default sum than you need to call const myModule = require('my_module'); myModule.default.sum() https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export Default import

// import what is default exported and bind bind to defaultExport
import defaultExport from "module-name";
// this is shorthand of
import { default as defaultExport } from 'module-name';

Namespace import

import * as name from "module-name";

Named import

import { export1 , export2 as alias2 , [...] } from 'module-name'

combination of default import and named or namespaces import

import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
// just run the code, not importing anything
import "module-name";

module-name is relative (./lib/math or lib/math) or absolute path name to the .js file

import * as math from "lib/math";
console.log("2pi = " + math.sum(math.pi, math.pi));

// or we could
import { pi, sum } from "lib/math";
console.log("2pi = " + sum(pi, pi));

In html you can include using script tag with type='module'

<html>
  <head>
    <meta charset="utf-8"/>
    <script type='module' src='./app.js'></script>
  </head>
  <body>
    asd up is
    <span id='up'></span>
  </body>
</html>

# app.js
import toUp from './t.js'
document.getElementById('up').innerHTML = toUp('asd')

# t.js
export default (str) => str.toUpperCase()

NPM package

-D is shorthand for --safe-dev.

npm i -D @babel/core babel-loader

DOM

  • instead of element.dataset('activateTarget') camelCase, we can use element.getAttribute('data-activate-target') snake case.
  • findElementById getElementById()
  • https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassNamefinding getElementsByClassName('class)') returns live HTMLCollections (similar to array, and it is updated when document is changed)
  • https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll const elements = document.querySelectorAll(".class"); returns static NodeList (which provides .entries() and .forEach()). To convert to array you can use Array.from(elements) or Array(...elements).
    elements.forEach(function(element) {
      element.remove()
    })
    

Tips

  • arguments object contains function arguments, but better is to use ...arguments as rest parameter
  • one liner if statement if (text.lenth == 0) return
  • jquery $(selector).addClass('b') can be written as element.classList.add('b') or elements.forEach((element) => element.classList.add('b') ). Also for toggle and remove. other examples http://youmightnotneedjquery.com/
  • string substring str.substring(1, str.length(-2)) can be replaced with slice str.slice(1, -2)
  • compare arrays with json, for example check if array is sorted
    let c = [...x]
    if (JSON.stringify(c.sort((a,b) => a - b)) == JSON.stringify(x)) {
    
  • insert html el.insertAdjacentHTML('beforeend', entries)

  • fetch makes js requests. If you need to pass header for cookie session than use param https://github.com/github/fetch#sending-cookies

    fetch('/users', {
      credentials: 'same-origin'
    })
    

    when you do POST whan you also need csrf token

    let data = { token: '123' }
    fetch('/profile/add-token', {
      method: 'POST',
      credentials: 'same-origin',
      headers: {
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        'X-CSRF-Token': csrf,
      },
      body: JSON.stringify(data)
    })
    

todo https://css-tricks.com/all-the-new-es2019-tips-and-tricks/