JavaScript - ES2015

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

const

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

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

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

1
2
3
const myName = 'Jay';
myName = 'DogPeng'; // TypeError
const myName = 'DogPeng'; // SyntaxError

但是可以新增元素:

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

let

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

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

method let const
reassign
redeclare

let 有 scope 限制:

1
2
3
4
5
6
var myName = 'Jay';
if (myName === 'Jay') {
let funFact = 'Plays the piano';
}

funFact; // ReferenceError!

Hoisting with let:

1
2
3
4
5
6
function helloJay() {
return jay;
var jay = 'ME!';
}

helloJay(); // undefined
1
2
3
4
5
6
function helloJay() {
return jay;
let jay = 'ME!';
}

helloJay(); // ReferenceError
1
2
3
4
5
6
7
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

// 5 (five times)
1
2
3
4
5
6
7
8
9
10
11
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i);
}, 1000);
}

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

Template Strings

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

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

對於多行字串也沒問題:

1
2
3
4
5
6
7
`
Hello
How
Nice
Is
This!
` // works well!

Arrow Functions

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

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5
var add = function(a, b) {
return a + b;
}

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

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

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

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

// ES2015
[1, 2, 3].map(val => val * 2); // [2, 4, 6]
1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5
function doubleAndFilter(arr) {
return arr.map(function(val) {
return val * 2;
}).filter(function(val) {
return val % 3 === 0;
});
};

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

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

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

1
2
3
4
5
6
7
8
9
10
var me = {
firstName: 'Jay',
sayHi: function() {
setTimeout(function() {
console.log('Hello ' + this.firstName);
}, 1000);
}
}

me.sayHi(); // 'Hello undefined'
1
2
3
4
5
6
7
8
9
10
var me = {
firstName: 'Jay',
sayHi: function() {
setTimeout(function() {
console.log('Hello ' + this.firstName);
}.bind(this), 1000);
}
}

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

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

1
2
3
4
5
6
7
8
9
10
11
var 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

1
2
3
4
5
6
function add(a = 10, b = 20) {
return a + b;
}

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

For … of

1
2
3
4
5
6
7
8
9
10
11
var arr = [1, 2, 3, 4, 5];

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

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

Rest

rest 總是回傳一個陣列

1
2
3
4
5
6
7
8
9
10
11
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]
1
var sumArguments = (...args) => args.reduce((acc, next) => acc + next);

Spread

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

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

Spread instead of apply

1
2
3
4
5
6
7
8
var 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var firstName = 'Jay';
var lastName = 'Chen';

// ES5
var me = {
firstName: firstName,
lastName: lastName
}

// ES2015
var me = {
firstName,
lastName
}

Object Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5
var me = {
sayHello: function() {
return 'Hello!';
}
}

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

Computed Property Names

1
2
3
4
5
6
7
8
9
10
11
12
var firstName = 'Jay';

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

// ES2015
var instructor = {
[firstName]: "That's me!"
}

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

Object Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var me = {
firstName: 'Jay',
lastName: 'Chen'
}

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

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

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

Different Variables

1
2
3
var { firstName: first, lastName: last } = me;
first; // 'Jay'
last; // 'Chen'

Default Values with an object

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ES5
function createMyself(options) {
var options = options || {};
var name = options.name || { first: 'Jay', last: 'Chen' };
var 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ES5
function displayInfo(obj) {
return [obj.name, obj.favColor];
}

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

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

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

Array Destructuring

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = [1, 2, 3];

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

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

a; // 1
b; // 2
c; // 3
1
2
3
4
5
6
7
8
9
10
11
12
13
function returnNumbers(a, b) {
return [a, b];
}

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

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

first; // 5
second; // 10

Swapping Values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ES5
function swap(a, b) {
var 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 關鍵字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ES5
function Student(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

Student.propotype.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;
}
}

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

Inheritance

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 關鍵字
1
2
3
4
5
6
7
var firstMap = new Map;

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

Key 可以是任何資料形別!

1
2
3
4
5
var arrayKey = [];
firstMap.set(arrayKey, [1, 2, 3, 4, 5]);

var objectKey = {};
firstMap.set(objectKey, { a: 1 });

Extracting Values

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

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

1
2
3
4
5
6
firstMap.forEach(v => console.log(v));

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

Iterating over a map

1
2
firstMap.values();  // MapIterator of values
firstMap.keys(); // MapIterator of keys

Accessing keys and values in a map

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

1
2
3
4
5
6
7
8
9
10
11
12
var m = new Map;
m.set(1, 'Jay');
m.set(2, 'BB');
m.set(3, 'Disney');

for (let [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 關鍵字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s = new Set;
var 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) 來執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function displayAtRandomTime() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (Math.random() > .5) {
resolve('Yes!');
} else {
reject('No!');
}
},1000);
});
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var years = [];
$.getJSON('https://omdbapi.com?t=titanic&apikey=thewdb')
.then(function(movie) {
years.push(movie.Year);
return $.getJSON('https://omdbapi.com?t=shrek&apikey=thewdb');
})
.then(function(movie) {
years.push(movie.Year);
console.log(years);
});
console.log('ALL DONE!');

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

Promise.all

  • Promise 不是 sequential,但 Promise.all 會 waits 他們。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getMovie(title) {
return $.getJSON(`https://omdbapi.com?t=${title}&apikey=thewdb`);
}

var titanicPromise = getMovie('titanic');
var shrekPromise = getMovie('shrek');
var 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
1
2
3
4
5
6
7
8
9
10
11
12
13
function* pauseAndReturnValues(num) {
for (let i = 0; i < num; i++) {
yield i;
}
}

var 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

1
2
3
4
5
6
7
8
9
10
function* printValues() {
yield 'First';
yield 'Second';
yield 'Third';
}

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

Iterating over a generator

1
2
3
4
5
6
7
8
9
10
11
12
13
function* pauseAndReturnValues(num) {
for (let i = 0; i < num; i++) {
yield i;
}
}

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

// 0
// 1
// 2

Async Generators

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

1
2
3
4
5
6
7
8
function* getMovieData(movieName) {
console.log('starting');
yield $.getJSON(`https://omdbapi.com?t=${movieName}&apikey=thewdb`);
console.log('ending');
}

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

Object.assign

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

1
2
3
4
5
6
// ES5
var o = { name: 'Jay' };
var o2 = o;

o2.name = 'BB';
o.name; // "BB"
1
2
3
4
5
6
// ES2015
var o = { name: 'Jay' };
var o2 = Object.assign({}, o);

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

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

1
2
3
4
5
6
// ES2015
var o = { names: ['Jay', 'BB'] };
var o2 = Object.assign({}, o);

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

Array.from

將其它資料型別轉入 arrays

1
2
3
4
5
6
// ES2015
var divs = document.getElementsByTagName('div');
var converted = Array.from(divs);

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

find

1
2
3
var names = [{ name: 'Jay' }, { name: 'BB' }, { name: 'Disney' }, { name: 'Benjamin' }];

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

findIndex

1
2
3
var names = [{ name: 'Jay' }, { name: 'BB' }, { name: 'Disney' }, { name: 'Benjamin' }];

names.findIndex(val => val.name === 'BB'); // 1
1
2
3
4
5
// ES5
'awesome'.indexOf('some'); // true

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

Number.isFinite

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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!';
}
}