ECMAScript 6 has basically become an industry standard. It is spreading much faster than ES5, mainly because modern browsers support ES6 quite rapidly, especially Chrome and Firefox, which already support the vast majority of ES6 features.


1. let, const, and block scope

let allows the creation of block-level scope. ES6 recommends using let to define variables in functions instead of var:

var a = 2;
{
  let a = 3;
  console.log(a); // 3
}
console.log(a); // 2

Another way to declare variables that is also valid in block-level scope is const, which can declare a constant. In ES6, a constant declared with const is similar to a pointer; it points to a reference, meaning this "constant" is not immutable. For example:

{
  const ARR = [5,6];
  ARR.push(7);
  console.log(ARR); // [5,6,7]
  ARR = 10; // TypeError
}

There are several points to note:

  • Variables declared with the let keyword do not have hoisting characteristics.
  • Declarations with let and const are only valid within the nearest block (inside curly braces).
  • When declaring with the constant keyword const, use uppercase variable names, such as: CAPITAL_CASING
  • const must be assigned a value when declared.

2. Arrow Functions

In ES6, arrow functions are a shorthand form of functions. Parameters are wrapped in parentheses, followed by =>, and then the function body:

var getPrice = function() {
  return 4.55;
};
 
// Implementation with Arrow Function
var getPrice = () => 4.55;

Note that the getPrice arrow function in the example above uses a concise function body, which does not require a return statement. The example below uses a normal function body:

let arr = ['apple', 'banana', 'orange'];
 
let breakfast = arr.map(fruit => {
  return fruit + 's';
});
 
console.log(breakfast); // apples bananas oranges

Of course, arrow functions not only make code concise; the this in a function is always bound to the object itself. Here are a few examples:

function Person() {
  this.age = 0;
 
  setInterval(function growUp() {
    // 在非严格模式下,growUp() 函数的 this 指向 window 对象
    this.age++;
  }, 1000);
}
var person = new Person();

We often need to use a variable to save this, and then reference it in the growUp function:

function Person() {
  var self = this;
  self.age = 0;
 
  setInterval(function growUp() {
    self.age++;
  }, 1000);
}

Using arrow functions can save this trouble:

function Person(){
  this.age = 0;
 
  setInterval(() => {
    // |this| 指向 person 对象
    this.age++;
  }, 1000);
}
 
var person = new Person();

3. Default values for function parameters

ES6 allows you to set default values for function parameters:

let getFinalPrice = (price, tax=0.7) => price + price * tax;
getFinalPrice(500); // 850

4. Spread / Rest operator

The Spread / Rest operator refers to ..., and whether it is Spread or Rest depends on the context.

When used in an iterable, it is a Spread operator:

function foo(x,y,z) {
  console.log(x,y,z);
}
 
let arr = [1,2,3];
foo(...arr); // 1 2 3

When used for passing function arguments, it is a Rest operator:

function foo(...args) {
  console.log(args);
}
foo( 1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

5. Object literal extensions

ES6 allows shorthand syntax when declaring object literals to initialize property variables and define methods, and also allows computed operations in object properties:

function getCar(make, model, value) {
  return {
    // 简写变量
    make,  // 等同于 make: make
    model, // 等同于 model: model
    value, // 等同于 value: value
 
    // 属性可以使用表达式计算值
    ['make' + make]: true,
 
    // 忽略 `function` 关键词简写对象函数
    depreciate() {
      this.value -= 2500;
    }
  };
}
 
let car = getCar('Barret', 'Lee', 40000);
 
// output: {
//     make: 'Barret',
//     model:'Lee',
//     value: 40000,
//     makeBarret: true,
//     depreciate: [Function: depreciate]
// }

6. Binary and octal literals

ES6 supports binary and octal literals. By prefixing a number with 0o or 0O, it can be converted to an octal value:

let oValue = 0o10;
console.log(oValue); // 8
 
let bValue = 0b10; // 二进制使用 `0b` 或者 `0B`
console.log(bValue); // 2

7. Object and array destructuring

Destructuring can avoid intermediate variables when assigning object values:

function foo() {
  return [1,2,3];
}
let arr = foo(); // [1,2,3]
 
let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3
 
function bar() {
  return {
    x: 4,
    y: 5,
    z: 6
  };
}
let {x: x, y: y, z: z} = bar();
console.log(x, y, z); // 4 5 6

8. Object superclass

ES6 allows the use of the super method in objects:

var parent = {
  foo() {
    console.log("Hello from the Parent");
  }
}
 
var child = {
  foo() {
    super.foo();
    console.log("Hello from the Child");
  }
}
 
Object.setPrototypeOf(child, parent);
child.foo(); // Hello from the Parent
             // Hello from the Child

9. Template syntax and delimiters

ES6 has a very concise way to assemble a bunch of strings and variables.

  • ${ ... } is used to render a variable
  • ` is used as the delimiter
let user = 'Barret';
console.log(`Hi ${user}!`); // Hi Barret!

10. for...of VS for...in

for...of is used to iterate over an iterable, such as an array:

let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
  console.log(nickname);
}
// 结果: di, boo, punkeye

for...in is used to iterate over properties in an object:

let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
  console.log(nickname);
}
Result: 0, 1, 2, size

11. Map and WeakMap

ES6 has two new data structure collections: Map and WeakMap. In fact, every object can be regarded as a Map.

An object consists of multiple key-val pairs. In a Map, any type can be used as the key of an object, such as:

var myMap = new Map();
 
var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};
 
// 设置值
myMap.set(keyString, "value 与 'a string' 关联");
myMap.set(keyObj, "value 与 keyObj 关联");
myMap.set(keyFunc, "value 与 keyFunc 关联");
 
myMap.size; // 3
 
// 获取值
myMap.get(keyString);    // "value 与 'a string' 关联"
myMap.get(keyObj);       // "value 与 keyObj 关联"
myMap.get(keyFunc);      // "value 与 keyFunc 关联"

WeakMap

WeakMap is a Map, except that all its keys are weak references, meaning that things in WeakMap are not considered during garbage collection, so you don't need to worry about memory leak issues when using it.

Another point to note is that all keys of WeakMap must be objects. It only has four methods: delete(key), has(key), get(key), and set(key, val):

let w = new WeakMap();
w.set('a', 'b'); 
// Uncaught TypeError: Invalid value used as weak map key
 
var o1 = {},
    o2 = function(){},
    o3 = window;
 
w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);
 
w.get(o3); // undefined, because that is the set value
 
w.has(o1); // true
w.delete(o1);
w.has(o1); // false

12. Set and WeakSet

A Set object is a collection of unique values. Duplicate values are ignored, and the value types can be primitive types and reference types:

let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('strings');
mySet.add({ a: 1, b:2 });

You can traverse a Set object with forEach and for...of:

mySet.forEach((item) => {
  console.log(item);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
});
 
for (let value of mySet) {
  console.log(value);
    // 1
    // 2
    // 3
    // 'strings'
    // Object { a: 1, b: 2 }
}

Set also has delete() and clear() methods.

WeakSet

Similar to WeakMap, a WeakSet object allows you to store weak references to objects in a collection. Objects in WeakSet are only allowed to appear once:

var ws = new WeakSet();
var obj = {};
var foo = {};
 
ws.add(window);
ws.add(obj);
 
ws.has(window); // true
ws.has(foo);    // false, foo 没有添加成功
 
ws.delete(window); // 从结合中删除 window 对象
ws.has(window);    // false, window 对象已经被删除

13. Classes

ES6 has class syntax. It is worth noting that class here is not a new object inheritance model; it is just syntactic sugar for the prototype chain.

In functions, use the static keyword to define methods and properties of the constructor:

class Task {
  constructor() {
    console.log("task instantiated!");
  }
 
  showId() {
    console.log(23);
  }
 
  static loadAll() {
    console.log("Loading all tasks..");
  }
}
 
console.log(typeof Task); // function
let task = new Task(); // "task instantiated!"
task.showId(); // 23
Task.loadAll(); // "Loading all tasks.."

Inheritance and super in classes:

class Car {
  constructor() {
    console.log("Creating a new car");
  }
}
 
class Porsche extends Car {
  constructor() {
    super();
    console.log("Creating Porsche");
  }
}
 
let c = new Porsche();
// Creating a new car
// Creating Porsche

extends allows a subclass to inherit a parent class. Note that the subclass's constructor function needs to execute the super() function.

Of course, you can also call parent class methods in subclass methods, such as super.parentMethodName().

Read more about classes here.

A few points worth noting:

  • Class declarations are not hoisted. If you want to use a certain Class, you must define it before using it, otherwise a ReferenceError will be thrown.
  • You do not need to use the function keyword to define functions in a class.

14. Symbol

Symbol is a new data type whose value is unique and immutable. The purpose of introducing symbol in ES6 is to generate a unique identifier, but you cannot access this identifier:

var sym = Symbol( "some optional description" );
console.log(typeof sym); // symbol

Note that the new operator cannot be used before Symbol here.

If it is used as a property of an object, then this property will be non-enumerable:

var o = {
    val: 10,
    [ Symbol("random") ]: "I'm a symbol",
};
 
console.log(Object.getOwnPropertyNames(o)); // val

To get the symbol properties of an object, you need to use Object.getOwnPropertySymbols(o).

15. Iterators

Iterators allow access to one element of a data collection at a time. When the pointer points to the last element of the data collection, the iterator exits. It provides a next() function to traverse a sequence, and this method returns an object containing the done and value properties.

In ES6, you can set a default iterator for an object through Symbol.iterator. Whenever an object needs to be iterated, executing its @@iterator method can return an iterator for obtaining values.

An array is an iterator by default:

var arr = [11,12,13];
var itr = arr[Symbol.iterator]();
 
itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }
 
itr.next(); // { value: undefined, done: true }

You can customize an iterator for an object through [Symbol.iterator]().

16. Generators

Generator functions are a new feature of ES6. They allow a function to return an iterable object that generates multiple values.

In use, you will see the * syntax and a new keyword yield:

function *infiniteNumbers() {
  var n = 1;
  while (true){
    yield n++;
  }
}
 
var numbers = infiniteNumbers(); // returns an iterable object
 
numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }

Each time yield is executed, the returned value becomes the next value of the iterator.

17. Promises

ES6 has native support for Promise. A Promise is an object waiting to be executed asynchronously. When it completes execution, its state becomes resolved or rejected.

var p = new Promise(function(resolve, reject) {  
  if (/* condition */) {
    // fulfilled successfully
    resolve(/* value */);  
  } else {
    // error, rejected
    reject(/* reason */);  
  }
});

Every Promise has a .then method. This method accepts two parameters: the first is a callback for handling the resolved state, and the second is a callback for handling the rejected state:

p.then((val) => console.log("Promise Resolved", val),
       (err) => console.log("Promise Rejected", err));

Original: https://github.com/metagrover/ES6-for-humans

Translation: http://www.barretlee.com/blog/2016/07/09/a-kickstarter-guide-to-writing-es6/

Translator/Little Bearded Brother