ES2015

在這篇文章中,會紀錄各種 ES2015 的標準!

const

var 宣告的變數,可重新賦值:

var myName = 'Jay';
myName = 'DogPeng'; // no problem here!

而由 const 宣告的變數,不能被重新賦值,也不能被重新宣告:

const myName = 'Jay';
myName = 'DogPeng'; // TypeError
const myName = 'DogPeng'; // SyntaxError

但是可以新增元素:

const numbers = [1, 2, 3, 4];
numbers.push(10); // 5
numbers; // [1, 2, 3, 4, 10]
numbers = 'no!'; // TypeError

let

let myName = 'Jay';
myName = 'DogPeng'; // no problems here!
let myName = 'DogPeng'; // SyntaxError

letconst 不同的是,可以重新賦與新值,但一樣無法重新宣告。

method let const
reassign
redeclare

let 有 scope 限制:

const myName = 'Jay';
if (myName === 'Jay') {
  let funFact = 'Plays the piano';
}

funFact; // ReferenceError

Hoisting with let:

function helloJay() {
  return jay;
  var jay = 'ME!';
}

helloJay(); // undefined
function helloJay() {
  return jay;
  let jay = 'ME!';
}

helloJay(); // ReferenceError
for (var i = 0; i < 5; ++i) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

// 5 (five times)
for (let i = 0; i < 5; ++i) {
  setTimeout(function () {
    console.log(i);
  }, 1000);
}

// 0
// 1
// 2
// 3
// 4

Template Strings

ES2015 對於字串的處理也有較優的方式,舊的寫法易出錯,新的標準清楚明了

const firstName = 'Jay';
const lastName = 'Chen';
console.log('Hello ' + firstName + ' ' + lastName); // error prone!
console.log(`Hello ${firstName} ${lastName}`); // much nicer!

對於多行字串也沒問題:

`
Hello
How
Nice
Is
This!
`; // works well!

Arrow Functions

接下來就要介紹 ES2015 中,我認為最讚的箭頭函數!它能大副的縮短程式碼行數,但一開始要花點時間去習慣。

// ES5
const add = function (a, b) {
  return a + b;
};

// 將 'function' keyword 取代成 '=>'
// ES2015
const add = (a, b) => {
  return a + b;
};

// One-line arrow functions
const add = (a, b) => a + b;

讓我們來試試用箭頭函數,重新改寫一些程式碼:

// ES5
[1, 2, 3].map(function (val) {
  return val * 2;
}); // [2, 4, 6]

// ES2015
[1, 2, 3].map((val) => val * 2); // [2, 4, 6]
// ES5
function doubleAndFilter(arr) {
  return arr
    .map(function (val) {
      return val * 2;
    })
    .filter(function (val) {
      return val % 3 === 0;
    });
}

// ES2015
const doubleAndFilter = (arr) =>
  arr.map((val) => val * 2).filter((num) => num % 3 === 0);

doubleAndFilter([5, 10, 15, 20]); // [30]

箭頭函數(arrow functions)和一般 functions 的差別在於他們沒有自己的 this 關鍵字。

const me = {
  firstName: 'Jay',
  sayHi: function () {
    setTimeout(function () {
      console.log(`Hello ${this.firstName}`);
    }, 1000);
  },
};

me.sayHi(); // 'Hello undefined'
const me = {
  firstName: 'Jay',
  sayHi: function () {
    setTimeout(
      function () {
        console.log(`Hello ${this.firstName}`);
      }.bind(this),
      1000
    );
  },
};

me.sayHi(); // 'Hello Jay'

箭頭函式因為沒有自己的 this,所以 this 會直接指向離他最近的物件(me)

const me = {
  firstName: 'Jay',
  // why can't we use an arrow function here?
  sayHi: function () {
    setTimeout(() => {
      console.log(`Hello ${this.firstName}`);
    }, 1000);
  },
};

me.sayHi(); // 'Hello Jay'

若我們將 function() 換成 () => 會導致 sayHi 沒有自己的 this,如此一來 this 就不在是指到 me

接下來就來看看更多 ES2015 比 ES5 更棒的地方!

Default Parameters

function add(a = 10, b = 20) {
  return a + b;
}

add(); // 30
add(20); // 40

forof

const arr = [1, 2, 3, 4, 5];

for (const val of arr) {
  console.log(val);
}

// 1
// 2
// 3
// 4
// 5

Rest

rest 總是回傳一個陣列

function printRest(a, b, ...c) {
  console.log(a);
  console.log(b);
  console.log(c);
}

printRest(1, 2, 3, 4, 5);

// 1
// 2
// [3, 4, 5]
const sumArguments = (...args) => args.reduce((acc, next) => acc + next);

Spread

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];

const combined = [...arr1, ...arr2, ...arr3];

Spread instead of apply

const arr = [3, 2, 4, 1, 5];
Math.max(arr); // NaN

// ES5
Math.max.apply(this, arr); // 5

// ES2015
Math.max(...arr); // 5

Object Shorthand Notation

const firstName = 'Jay';
const lastName = 'Chen';

// ES5
const me = {
  firstName: firstName,
  lastName: lastName,
};

// ES2015
const me = {
  firstName,
  lastName,
};

Object Methods

// ES5
const me = {
  sayHello: function () {
    return 'Hello!';
  },
};

// ES2015 - do NOT use arrow functions here!
const me = {
  sayHello() {
    return 'Hello!';
  },
};

Computed Property Names

const firstName = 'Jay';

// ES5
const me = {};
me[firstName] = "That's me!";

// ES2015
const boy = {
  [firstName]: "That's me!",
};

me.Jay; // "That's me!"
boy.Jay; // "That's me!"

Object Destructuring

const me = {
  firstName: 'Jay',
  lastName: 'Chen',
};

// ES5
const firstName = me.firstName;
const lastName = me.lastName;

// ES2015
const { firstName, lastName } = me;

firstName; // 'Jay'
lastName; // 'Chen'

Rename destructured variables

const { firstName: first, lastName: last } = me;
first; // 'Jay'
last; // 'Chen'

Default values with an object

在 ES2015 中,若沒有任何參數傳入,那麼 destructured object 就是預設參數:

// ES5
function createMyself(options) {
  var options = options || {};
  const name = options.name || { first: 'Jay', last: 'Chen' };
  const isHilarious = options.isHilarious || false;
  return [name.first, name.last, isHilarious];
}

// ES2015
function createMyself({
  name = { first: 'Jay', last: 'Chen' },
  isHilarious = false,
} = {}) {
  return [name.first, name.last, isHilarious];
}

createMyself();
// ['Jay', 'Chen', false]

createMyself({ isHilarious: true });
// ['Jay', 'Chen', true]

createMyself({ name: { first: 'Dog', last: 'Peng' } });
// ['Dog', 'Peng', false]

Object fields as parameters

// ES5
function displayInfo(obj) {
  return [obj.name, obj.favColor];
}

// ES2015 - Very common in React!
function displayInfo({ name, favColor }) {
  return [name, favColor];
}

const me = {
  name: 'Jay',
  favColor: 'White',
};

displayInfo(me); // ['Jay', 'White']

Array Destructuring

const arr = [1, 2, 3];

// ES5
const a = arr[0];
const b = arr[1];
const c = arr[2];

// ES2015
const [a, b, c] = arr;

a; // 1
b; // 2
c; // 3
function returnNumbers(a, b) {
  return [a, b];
}

// ES5
const first = returnNumbers(5, 10)[0];
const second = returnNumbers(5, 10)[1];

// ES2015
[first, second] = returnNumbers(5, 10);

first; // 5
second; // 10

Swapping Values

// ES5
function swap(a, b) {
  const temp = a;
  a = b;
  b = temp;
  return [a, b];
}

// ES2015
function swap(a, b) {
  return ([a, b] = [b, a]);
}

swap(10, 5); // [5,10]

class

  • 在 ES2015 後,為新的保留字
  • 不能被重新宣告
  • 沒有 hoist
  • 仍使用 new 關鍵字
// ES5
function Student(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Student.prototype.sayHello = function () {
  return `Hello ${this.firstName} ${this.lastName}`;
};

Student.isStudent = function (obj) {
  return obj.constructor === Student;
};

// ES2015
class Student {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  sayHello() {
    return `Hello ${this.firstName} ${this.lastName}`;
  }
  static isStudent(obj) {
    return obj.constructor === Student;
  }
}

const jay = new Student('Jay', 'Chen');

Inheritance

一個 class 能傳送 methods 和 properties 給其它 class

// ES5
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Person.prototype.sayHello() {
  return `Hello ${this.firstName} ${this.lastName}`;
}

function Student(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

// Use Apply
function Student() {
  Person.apply(this, arguments);
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

super

// ES2015
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  sayHello() {
    return `Hello ${this.firstName} ${this.lastName}`;
  }
}

class Student extends Person {
  constructor(firstName, lastName) {
    // you must use super here!
    super(firstName, lastName);
  }
}

Map

  • 在其它語言常被稱做 “hash map”
  • 和物件(object)很像,但 keys 可以是任何資料形別
  • 使用 new 關鍵字
const myMap = new Map();

myMap.set(1, 'Jay');
myMap.set(false, 'a boolean');
myMap.set('nice', 'a string');
myMap.delete('nice'); // true
myMap.size; // 2

Key 可以是任何資料形別!

const arrayKey = [];
myMap.set(arrayKey, [1, 2, 3, 4, 5]);

const objectKey = {};
myMap.set(objectKey, { a: 1 });

Extracting values

myMap.get(1); // 'Jay'
myMap.get(false); // 'a boolean'
myMap.get(arrayKey); // [1, 2, 3, 4, 5]
myMap.get(objectKey); // { a: 1 }

我們可以很輕鬆的遍歷整個 Map

myMap.forEach((v) => console.log(v));

// Elie
// a boolean
// [1, 2, 3, 4, 5]
// { a: 1 }

Iterating over a Map

myMap.values(); // MapIterator of values
myMap.keys(); // MapIterator of keys

Accessing keys and values in a Map

我們可以藉由 .entries() 和 destructuring 去取得任何值!

const m = new Map();
m.set(1, 'Jay');
m.set(2, 'BB');
m.set(3, 'Disney');

for (const [key, value] of m.entries()) {
  console.log(key, value);
}

// 1 'Jay'
// 2 'BB'
// 3 'Disney'

WeakMap

  • Map 很像,但所有 keys 必需是 objects
  • WeakMap 中的 values 會在他們不在被需要時從記憶體中清空
  • Map 更有效率,但是無法被遍歷

Set

  • 所有的 values 是獨一無二的
  • value 可以是任何資料形別
  • 使用 new 關鍵字
const s = new Set();
const s2 = new Set([3, 1, 4, 1, 2, 1, 5]); // { 3, 1, 4, 2, 5 }

s.add(10); // { 10 }
s.add(20); // { 20, 10 }
s.add(10); // { 20, 10 }

s.size; // 2

s.has(10); // true
s.delete(20); // true
s.size; // 1

s2[Symbol.iterator]; // function() {}...

WeakSet

  • Set 很像,但所有 values 必需是 objects
  • WeakSet 中的 values 會在他們不在被需要時從記憶體中清空
  • Set 更有效率,但是無法被遍歷

Promise

  • 只有一次機會的未來 return value
  • 當 value 被弄清楚時,Promise 會被
    • resolved/fulfilled
    • rejected
  • 可以友善地重新改寫 callback 程式碼

Create your own Promise

  • 使用 new 關鍵字
  • 每一個 Promise constructor 接受了一個擁有兩個參數(resolve 和 reject)的 callback 函式

每一個由 Promise 所回傳的值會由 .then (resolved) 和 .catch (rejected) 來執行。

function displayAtRandomTime() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (Math.random() > 0.5) {
        resolve('Yes!');
      } else {
        reject('No!');
      }
    }, 1000);
  });
}

displayAtRandomTime()
  .then(function (value) {
    console.log(value);
  })
  .catch(function (error) {
    console.log(error);
  });

我們可以將 Promises 串接在一起,即它們是 thenable

const years = [];

fetch('https://omdbapi.com?t=titanic&apikey=thewdb')
  .then((res) => res.json())
  .then(function (movie) {
    years.push(movie.Year);
    return fetch('https://omdbapi.com?t=titanic&apikey=thewdb').then((res) =>
      res.json()
    );
  })
  .then(function (movie) {
    years.push(movie.Year);
    console.log(years);
  });
console.log('ALL DONE!');

// "ALL DONE!"
// ["1997", "1997"]

Promise.all

  • Promise 不是 sequential,但 Promise.all 會 waits 他們。
function getMovie(title) {
  return fetch(`https://omdbapi.com?t=${title}&apikey=thewdb`).then((res) =>
    res.json()
  );
}

const titanicPromise = getMovie('titanic');
const shrekPromise = getMovie('shrek');
const braveheartPromise = getMovie('braveheart');

Promise.all([titanicPromise, shrekPromise, braveheartPromise]).then(function (
  movies
) {
  return movies.forEach(function (movie) {
    console.log(movie.Year);
  });
});

// 1997
// 2001
// 1995

Generator

  • 可以在任何時間 pause 執行和 resume
  • 使用 *
  • 當被呼叫時,會回傳一個 generator object、keys of value 和 done
  • Value 透過 yield 從 paused 函式回傳
  • Done 則是一個布林值,當函式完成時回傳 true
function* pauseAndReturnValues(num) {
  for (let i = 0; i < num; ++i) {
    yield i;
  }
}

const gen = pauseAndReturnValues(5);
gen.next(); // { value: 0, done: false }
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: 4, done: false }
gen.next(); // { value: undefined, done: true }

Yield multiple values

function* printValues() {
  yield 'First';
  yield 'Second';
  yield 'Third';
}

const g = printValues();
g.next().value; // "First"
g.next().value; // "Second"
g.next().value; // "Third"

Iterating over a generator

function* pauseAndReturnValues(num) {
  for (let i = 0; i < num; ++i) {
    yield i;
  }
}

for (const val of pauseAndReturnValues(3)) {
  console.log(val);
}

// 0
// 1
// 2

Async Generators

我們可以利用 generators 去暫停 asynchronous 程式碼!

function* getMovieData(movieName) {
  console.log('starting');
  yield fetch(`https://omdbapi.com?t=${movieName}&apikey=thewdb`).then((res) =>
    res.json()
  );
  console.log('ending');
}

const movieGetter = getMovieData('titanic');
movieGetter.next().value.then((val) => console.log(val));

Object.assign

ES2015 是「真的」新增了一個 Object

// ES5
const o = { name: 'Jay' };
const o2 = o;

o2.name = 'BB';
o.name; // "BB"
// ES2015
const o = { name: 'Jay' };
const o2 = Object.assign({}, o);

o2.name = 'BB';
o.name; // "Jay"

但不是完整的複製一份新的,仍有 reference!

// ES2015
const o = { names: ['Jay', 'BB'] };
const o2 = Object.assign({}, o);

o2.names.push('Disney');
o.names; // ["Jay", "BB", "Disney"];

Array.from

將其它資料型別轉入 arrays

// ES2015
const divs = document.getElementsByTagName('div');
const converted = Array.from(divs);

const firstSet = new Set([1, 2, 3, 4, 3, 2, 1]); // { 1, 2, 3, 4 }
const arrayFromSet = Array.from(firstSet); // [1, 2, 3, 4]

find

const names = [
  { name: 'Jay' },
  { name: 'BB' },
  { name: 'Disney' },
  { name: 'Benjamin' },
];

names.find((val) => val.name === 'BB'); // { name: "BB" }

findIndex

const names = [
  { name: 'Jay' },
  { name: 'BB' },
  { name: 'Disney' },
  { name: 'Benjamin' },
];

names.findIndex((val) => val.name === 'BB'); // 1

includes

// ES5
'awesome'.indexOf('some'); // 3

// ES2015
'awesome'.includes('some'); // true

Number.isFinite

// ES5
function seeIfNumber(val) {
  if (typeof val === 'number' && !isNaN(val)) {
    return 'It is a number!';
  }
}

// ES2015
function seeIfNumber(val) {
  if (Number.isFinite(val)) {
    return 'It is a number!';
  }
}