ES2015
在這篇文章中,會紀錄各種 ES2015 的標準!
const
由 var
宣告的變數,可重新賦值:
var myName = "Jay";
myName = "Peng-Yu"; // no problem here!
而由 const
宣告的變數,不能被重新賦值,也不能被重新宣告:
const myName = "Jay";
myName = "Peng-Yu"; // TypeError
const myName = "Peng-Yu"; // 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 = "Peng-Yu"; // no problems here!
let myName = "Peng-Yu"; // SyntaxError
let
與 const
不同的是,可以重新賦與新值,但一樣無法重新宣告。
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
for
… of
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!";
}
}