Taiyi dev

Notes

ES6-ES12

Content

ES6 (ECMAScript 2015)

1. let/const

let 以前在宣告變數都使用全域的var在 ES6 新增了let可以更明確的規範變數作用的範圍。

function f() {
  let x = 1;
  {
    console.log(x); // 1
    let y = 2;
  }
  console.log(y); // Uncaught ReferenceError: y is not defined
}
f();

const 簡化了定義常數的宣告,常數必須初始化且不能變更。

const PI = 3.14;

2. arrow functions

var a = [1, 2, 3, 4, 5];
//ES5
a.map(function (v) {
  return v + 1;
});
//ES6
a.map((v) => v + 1);

3. classes

加入 classes,定義 class 更直觀。在 ES5 以前定義物件很複雜,沒有 class 關鍵字,所以會看到很多XXX.prototype.YYY的語法,在 ES6 幾乎看不見,精簡許多。

class Stack {
  constructor() {
    this.stack = [];
  }
  push(num) {
    this.stack.push(num);
  }
  pop() {
    return this.stack.pop();
  }
  size() {
    return this.stack.length;
  }
}

s = new Stack();
s.push(1);
s.push(2);
s.push(3);
s.pop(); // 3
s.size(); // 2
const Stack = function () {
  this.stack = [];
};
Stack.prototype.push = function (num) {
  this.stack.push(num);
};
Stack.prototype.pop = function () {
  return this.stack.pop();
};
Stack.prototype.size = function () {
  return this.stack.length;
};
class Animal {
  constructor(name, leg) {
    this.name = name;
    this.leg = leg;
  }
  bark() {
    console.log("animal bark");
  }
}
class Cat extends Animal {
  constructor({ name, leg }) {
    super(name, leg);
    this.run = true;
    this.fly = false;
  }
  bark() {
    console.log("meow");
  }
}

let cat = new Cat({ name: "Jack", leg: 4 });
cat; // Cat {name: "Jack", leg: 4, run: true, fly: false}
cat.bark(); // meow

4. template string

允許在字串中插入變數,不用像以前一樣做字串串接。

let name = "Tom";
let template = `Hello, ${name}.`; // "Hello, Tom."

5. destructuring

在拆解陣列或是物件時更方便

let num = [1, 2, 3];
let [a, b, c] = num; // a = 1, b = 2, c = 3
let obj = { a: 1, b: 2, c: 3 };
let { a, b, c } = obj;
console.log(a, b, c); // 1, 2, 3

6. default + rest + speard

let f = (a = 1, b = 2, c = 3) => {
  console.log(a, b, c);
};
f(); // 1 2 3
f(4, 5); // 4 5 3
f(4, 5, 6); // 4 5 6
let f = (a, ...rest) => {
  console.log(a, rest);
};
f(1, 2, 3, 4, 5); // 1 [2, 3, 4, 5]
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

7. iterators + for..of

let nums = [1, 2, 3, 4];
for (let n of nums) {
  console.log(n);
}
/*
	1
	2
	3
	4
*/

8. Generators

function f() {
  console.log("1.");
  console.log("2.");

  return "Hello World";
}
function* f() {
  yield "yield 1.";
  console.log("1.");

  yield "yield 2.";
  console.log("2.");

  return "Hello World";
}

const iter = f();
iter.next(); // {value: 'yield 1.', done: false}
iter.next(); // {value: 'yield 2.', done: false}
iter.next(); // {value: 'Hello World', done: true}
function* f() {
  yield "a";
  yield "b";
  yield "c";
  yield "d";
}

const iter = f();
console.log([...iter]); // [a, b, c, d]

費式數列例子

// Generates an infinite stream of Fibonacci numbers.
// The generator doesn't keep the array of all numbers.
function* fibonacci(): IterableIterator<number> {
  let [a, b] = [0, 1];

  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

function print(n: number) {
  let i = 0;
  for (const fib of fibonacci()) {
    if (i++ === n) break;
    console.log(fib);
  }
}

// Print first 10 Fibonacci numbers.
print(10);

在遇到 generator.return()時會直接返回{done: true}

function* print(i){
   let index = 0
   while(true){
      yield index++
   }
}

g = print(0)
g.next()   // {value: 0, done: false}
g.next()   // {value: 1, done: false}
g.return() // {value: undefined, done: true}
g.next()   // {value: undefined, done: true}

9. unicode

s = "\u{1F602}"; //"😂"

10. modules

// a.js
const a = () => {
  console.log("a.js");
};
module.exports = a;
// b.js
import a from "./a";
a(); // a.js

11. map/set/weakmap/weakset

let map = new Map();
map.set(1, "1");
map.set("a", "a");
map.set(["arr1"], ["arr2"]);
map; // {1 => "1", "a" => "a", ["arr1"] => ["arr2"]}

let weakmap = new WeakMap();
weakmap.set(1, "1");
// Uncaught TypeError: Invalid value used as weak map key
weakmap.set("a", "a");
// Uncaught TypeError: Invalid value used as weak map key
weakmap.set(["arr1"], ["arr2"]); // {["arr1"] => ["arr2"]}

Set 與 WeakSet 差異: Set 能夠儲存隨意型態(int, string, array), WeakSet 只能夠儲存物件。

let set = new Set();
set.add(1).add(2).add(5); // {1, 2, 5}
set.add(2); // {1, 2, 5}
set.add("2"); // // {1, 2, 5, "2"}
set.add([1, 2, 3]); // {1, 2, 5, "2", [1,2,3]}
set.size; // 5

let weakset = new WeakSet();
weakset.add(1);
// Uncaught TypeError: Invalid value used in weak set
weakset.add([1, 2, 3]); // {[1,2,3]}
weakset.add([1, 2, 3]); // {[1,2,3], [1,2,3]}

12. proxying/refection

13. symbols

symbol 是 ES6 新增的一種基本數據型態(primitive data type)。
基本型態有七種: string,number,bigint,boolean,null,undefined,symbol。
特性: 唯一且不可改變(immutable)的資料型態,可用來識別物件的屬性。

let a = Symbol("123");
let b = Symbol("123");
a === b; // false
let a = Symbol("aaa");
let b = Symbol("bbb");
let obj = { [a]: 123, [b]: 456 };
obj; // {Symbol(aaa): 123, Symbol(bbb): 456}

14. promises

程式常常需要做 request 到後端來取得資料,但 server 並不會馬上回傳資料,要等待 server 處理的時間。這時程式就必須停下來等待,造成時間浪費。

ES6 提出了 promise 來解決這個非同步(asynchronously)的狀況。當發送 request 到 server 後,並不會停下來等 server 處理完,而是留下 callback 後,繼續執行後面任務。

當 server 成功處理好拿到資料準備回傳時,再去執行剛剛留下的 callback function。留下的 callback function 有分為 request 呼叫成功(resolve)與 request 呼叫失敗(reject)。

const callServer = (time) => {
  return new Promise((resolve, reject) => {
    if (time <= 300) {
      setTimeout(() => resolve({ msg: "request success!" }), time);
    } else {
      setTimeout(() => reject({ msg: "request failed!" }), time);
    }
  });
};
callServer(200).then((res) => console.log(res));
// {msg: "request success!"}
callServer(400)
  .then((res) => console.log(res))
  .catch((e) => console.log(e)); // <--
// {msg: "request failed!"}

15. binary and octal literals

支援二進位與八進位表示

0b111; //7
0o123; //83
let num = "1010";
let binNum = `0b${num}`;
let decNum = BigInt(binNum); // 10n
decNum.toString(2); // "1010"

ES7 (ECMAScript 2016)

1. Exponentiation Operator

2 ** 3; // 8

2. Array.includes()

let arr = ["Tom", "Jack", "David"];
arr.includes("Tom"); // true

ES8 (ECMAScript 2017)

1. Async functions (MDN)

可以用來簡化 ES6 Promise 寫法,await 表達式只能在 async 函式內部使用,await 會等待 Promise 解析完畢才會繼續往下執行。

之前 ES6 Promise 範例,在 callServer()之後要處理回傳的 Promise 需要使用.then()來接。若拿回來的資料還要當做其他 function 的 input 會遇到.then().then().then()的狀況。

const callServer = (time) => {
  return new Promise((resolve, reject) => {
    if (time <= 300) {
      setTimeout(() => resolve({ msg: "request success!" }), time);
    } else {
      setTimeout(() => reject({ msg: "request failed!" }), time);
    }
  });
};
callServer(200).then((res) => console.log(res));
// {msg: "request success!"}
callServer(400)
  .then((res) => console.log(res))
  .catch((e) => console.log(e)); // <--
// {msg: "request failed!"}

ES8 async function 可以改善處理 promise 的寫法。

const callServer = (time) => {
  return new Promise((resolve, reject) => {
    if (time <= 300) {
      setTimeout(() => resolve({ msg: "request success!" }), time);
    } else {
      setTimeout(() => reject({ msg: "request failed!" }), time);
    }
  });
};
const run = async () => {
  let success = await callServer(200);
  let error = {};
  try {
    error = await callServer(400);
  } catch (err) {
    error = err;
  }

  console.log(success); // {msg: "request success!"}
  console.log(error); // {msg: "request failed!"}
};
run();

2. Object.entries

let obj = { a: 1, b: 2, c: 3 };

for (let [key, value] of Object.entries(obj)) {
  console.log(key, value);
}
/*
  a 1
  b 2
  c 3
*/

3. Object.values

let obj = { a: 1, b: 2, c: 3 };

for (let value of Object.values(obj)) {
  console.log(value);
}
/*
  1
  2
  3
*/

4. Object.getOwnPropertyDescriptors

let obj = { a: 1, b: 2, c: 3 };

let descriptors = Object.getOwnPropertyDescriptors(obj);

descriptors;

/*
{
  a: {value: 1, writable: true, enumerable: true, configurable: true}
  b: {value: 2, writable: true, enumerable: true, configurable: true}
  c: {value: 3, writable: true, enumerable: true, configurable: true}
}
*/

5. String.padStart() (MDN), String.padEnd() (MDN)

"101".padStart(8); // "     101"
"101".padStart(8, "0"); // "00000101"
"101".padStart(8, "xyz"); // "xyzxy101"
"101".padEnd(8); // "101     "
"101".padEnd(8, "0"); // "10100000"
"101".padEnd(8, "xyz"); // "101xyzxy"

6. Shared memory and Atomic

shared-memory

Atomic 是類似鎖,當 CPU1 在讀寫 shared memory 時,會把 shared memory 中的值複製一份到 cache 中。在讀寫時把 shared memory 鎖住,讓其他 CPU 不能夠讀寫,避免複製到舊的值,確保資料一致性。

ES9 (ECMAScript 2018)

1. Object rest and spread

在 ES6 加入的 rest 與 spread 只能針對陣列做展開,ES8 擴展到也能夠針對物件展開。

let obj = { a: 1, b: 2, c: 3 };
let { a, ...rest } = obj;
a; // 1
rest; // {b: 2, c: 3}

2. Promise.finally

不管 Promise 是 resolve 還是 reject 都會執行 finally(),可以用來關閉 loading 狀態。

const fetchData = (time) => {
  return new Promise((resolve, reject) => {
    if (time <= 300) {
      setTimeout(() => resolve({ data: "request success" }), time);
    } else {
      setTimeout(() => reject({ data: "request failed!" }), time);
    }
  });
};

const run = async () => {
  fetchData(400)
    .then(({ data }) => console.log(data))
    .catch((err) => console.log(err))
    .finally(() => console.log("finally"));
};
run();

// {data: "request failed!"}
// finally

3. Asynchronous Iteration

支援在 for 迴圈內呼叫非同步 function。

const fetchData = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(`success: ${time}ms`), time);
  });
};

const run = async () => {
  let times = [500, 1000, 2000];
  for await (let ms of times) {
    fetchData(ms).then((res) => console.log(res));
  }
};
run();
// success: 500ms
// success: 1000ms
// success: 2000ms

4. Regular Expression Features

const re = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
const match = re.exec("2021-09-05");
match.groups; // {year: "2021", month: "09", day: "05"}

5. Escape sequences allowed in tagged template literals

const tagged = (s, version, year) => {
  console.log(s);
  console.log(version);
  console.log(year);
};

let version = "ES9";
let year = 2018;

console.log(tagged`This is ${version} (ECMAScript${year}).`);
/* 
  ["This is ", " (ECMAScript", ").", raw: Array(3)]
  ES9
  2018
*/

ES10 (ECMAScript 2019)

1. Array.flat() (MDN) / Array.flatMap() (MDN)

let arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]

let arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat(); // [1, 2, 3, 4, [5, 6]]
arr2.flat(2); // [1, 2, 3, 4, 5, 6]

let arr3 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr3.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

2. Object.fromEntries()(MDN)

把 Map、Array 傳換成物件。

const map = new Map([
  ["a", 1],
  ["b", 2],
]);
map; // Map(2) {'a' => 1, 'b' => 2}
Object.fromEntries(map); // {a: 1, b: 2}
let arr = [
  ["a", 1],
  ["b", 2],
  ["c", 3],
];
Object.fromEntries(arr); // {a: 1, b: 2, c: 3}

3. String.trimStart() / String.trimEnd()

去除左邊/右邊空白並返回一個新的字串,不會直接修改原來的值。

let s = "   hello   ";
s.trimStart(); // 'hello   '
s.trimEnd(); // '   hello'

ES11 (ECMAScript 2020)

1. string.matchAll() (MDN)

let s = "abcdacd";
console.log([...s.matchAll(/a/g)]);
/*
[
  ['a', index: 0, input: 'abcdacd', groups: undefined],
  ['a', index: 4, input: 'abcdacd', groups: undefined]
]
*/

2. BigInt

遇到類似Leetcode 1985這類問題, Number 型態已經不足夠表示。Number 型態表示上限為25312^{53} - 1

Math.pow(2, 53); // 9007199254740992
Number.MAX_SAFE_INTEGER; // 9007199254740991

Number.MAX_SAFE_INTEGER + 1; // 9007199254740992
Number.MAX_SAFE_INTEGER + 2; // 9007199254740992

BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1); // 9007199254740992n
BigInt(Number.MAX_SAFE_INTEGER) + BigInt(2); // 9007199254740993n

3. Promise.allSettled

Promise.all() 在遇到 reject 時會停止後續動作(short-circuit),直接噴錯。
Promise.allSettled() 其中有遇到 reject 還是會將其他 promise 執行完畢。

let promise1 = Promise.resolve(3);
let promise2 = new Promise((resolve, reject) => {
  setTimeout(reject("error"));
});
let promise3 = 42;

Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values);
  })
  .catch(() => console.log("error"));
// error

Promise.allSettled([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
/*
[
  { status: 'fulfilled', value: 3 },
  { status: 'rejected', reason: 'error' },
  { status: 'fulfilled', value: 42 }
]
*/

4. globalThis

var a = 10 // Window{..., a: 10, ...}

(function () {
  let a = 20
  console.log(a) // 20
  console.log(this.a) // 10
  console.log(window.a) // 10
  console.log(frames.a) // 10
  console.log(globalThis.a) // 10
})()

5. Nullish coalescing Operator (??) (MDN)

??左側為否定只判斷nullundefined

console.log(null ?? "hello"); // hello
console.log(undefined ?? "hello"); // hello
console.log(false ?? "hello"); // false
console.log(0 ?? "hello"); // 0
console.log("" ?? "hello"); // ""

console.log(null || "hello"); // hello
console.log(undefined || "hello"); // hello
console.log(false || "hello"); // hello
console.log(0 || "hello"); // hello
console.log("" || "hello"); // hello

6. Optional Chaining (?.)

let a = {
  b: {
    c: 1,
  },
};
a.b.c; // 1
a.d.c; // TypeError: Cannot read properties of undefined
a?.d?.c; // undefined

7. module

ES12 (ECMAScript 2021)

1. replaceAll

let str = "ABCDFABCGHIABC";
str.replaceAll("ABC", "xxx"); // 'xxxDFxxxGHIxxx'
str.replace(/ABC/g, "xxx"); // 'xxxDFxxxGHIxxx'

2. Promise.any

a Promise combinator that short-circuits when an input value is fulfilled (遇到的第一個 Promise 是 fullilled 就回傳,後面的都不做了)

3. AggregateError

a new Error type to represent multiple errors at once (一個新的 Error type, 把多個 Errors 包在一起)

Promise.any([Promise.reject("error1"), Promise.reject("error2"), Promise.reject("error3")]).catch((err) => err);

/*
AggregateError{
  errors: (3) ['error1', 'error2', 'error3']
  message: "All promises were rejected"
  stack: "AggregateError: All promises were rejected"
}
*/

4. ??=, &&=, ||=

let a = undefined;
let b = 1;

a ??= b; // 如果a是nullish(null, undefined), 將b賦值到a
let a = 1;
a &&= 10; // 如果a是truthy,則賦值到a (a = 10)

let b = 0;
b &&= 10; // 如果b是falsy,則不賦值 (b = 0)
let a = 1;
a ||= 10; // 如果a是truthy,則不賦值 (a = 0)

let b = 0;
b ||= 10; // 如果b是falsy,則賦值到b (b = 10)

5. WeakRef

for referring to a target object without preserving it from garbage collection

6. FinalizationRegistry

to manage registration and unregistration of cleanup operations performed when target objects are garbage collected (在註冊的物件被回收時,執行 callback)

const r = new FinalizationRegistry(
  () => console.log("callback")
);

(() => {
  let obj = { a: 1, b: 2 }
  r.register(obj)
})();