ES6 知識整理一(es6快速入門)

歐西里斯的天秤發表於2019-03-12

ES6 簡介

ES6, 全稱 ECMAScript 6.0 ,是 JavaScript 的下一個版本標準,2015.06 發版。

let 和 const

let 命令

let 命令,用來宣告變數。它的用法類似於 var,區別在於 var 宣告的變數全域性有效,let 宣告的變數只在它所在的程式碼塊內有效。

// 變數i儲存的值是10,所以執行a[2]()後輸出10
var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[2](); // 10


// 修正方法
// 閉包會使得函式中的變數都被儲存在記憶體中,所以執行a[2]()後輸出2
var a = [];
for (var i = 0; i < 10; i++) {
    (function (i) {
        a[i] = function () {
            console.log(i)
        }
    })(i);
}
a[2](); // 2

// es6
// let宣告的i只在當前的程式碼塊有效,所以每次for迴圈相當於用let重新宣告一次i
var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[2](); // 2

// 注:JavaScript 引擎內部會記住上一輪迴圈的值,初始化本輪的變數i時,就在上一輪迴圈的基礎上進行計算。

let 不存在變數提升,必須先宣告後使用,否則報錯;var 存在變數提升,未宣告前使用輸出 undefined。

let 存在暫時性死區,在程式碼塊內,使用 let 命令宣告變數之前,該變數都是不可用的。

let 不允許重複宣告。

const 命令

const 宣告一個只讀的常量。一旦宣告,常量的值就不能改變。不能只宣告不賦值。

const a = 10;
a = 20; // 報錯

const b; // 報錯

const 的作用域與 let 相同。

if(true) {
  const num = 5;
}
console.log(num); // 報錯

const 宣告物件,常量物件記憶體地址,因此物件本身可改,但是給常量重新賦值就會報錯。

const obj = {};
obj.a = 'a';

obj = {}; // 報錯

塊級作用域和函式作用域

ES5 規定,函式只能在頂層作用域和函式作用域之中宣告,不能在塊級作用域宣告。但是在 ES6 中,函式可以在塊級作用域中宣告。但是,市面上很多瀏覽器都不支援 ES6,所以應該避免在塊級作用與中宣告函式。

ES6 宣告變數的方法

  • var
  • function
  • let
  • const
  • import
  • class

變數的解構賦值

ES6 允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構(Destructuring)。

陣列的解構賦值

模式匹配賦值,如果解構不成功,變數的值就等於 undefined。

let [a, [[b], c]] = [1, [[2], 3]];
console.log(a,b,c); // 1, 2, 3

let [x, , y, z] = [1, 2, 3];
console.log(x); // 1
console.log(y); // 3
console.log(z); // undefined

不完全解構賦值,等號左邊的模式,只匹配一部分的等號右邊的陣列。

let [x, [y], z] = [1, [2, 3], 4];
console.log(x); // 1
console.log(y); // 2
console.log(z); // 4

陣列結構賦值右邊必須是陣列,模式不匹配則報錯。

let [a]  = {}; // 報錯

解構賦值可以新增預設值,並且可以引用解構賦值的其他變數。

let [a = 1, b = 2] = [, 3];
console.log(a); // 1
console.log(b); // 3

let [x = 1, y = x] = [];  // x = 1; y = 1
let [x = 1, y = x] = [2]; // x = 1; y = 2

陣列解構賦值可用於交換變數的值。

let [a, b] = [1, 2];
console.log(a, b); // 1, 2
[b, a] = [a, b];
console.log(a, b); // 2, 1

物件的解構賦值

變數必須與屬性同名

let { a, b, c } = { a: 'aaa', b: 'bbb' };
console.log(a); // 'aaa'
console.log(b); // 'bbb'
console.log(c); // undefined

變數名與屬性名不一致

let { a: x, b: y } = { a: 'aaa', b: 'bbb' };
console.log(x); // 'aaa'
console.log(y); // 'bbb'

巢狀賦值,如果子物件所在的父屬性不存在,會報錯,慎用。

let { a, a: {x}, b: y } = { a: {x: 'xxx',y: 'yyy'}, b: "bbb" };
console.log(a); // { x: 'xxx', y: 'yyy' }
console.log(x); // 'xxx'

let {c: {d: {e}}} = {c: 'ccc'}; // 報錯
console.log(e)

變數解構賦值也和陣列的解構賦值一樣,可以賦預設值,變數解構賦值時,不能將大括號寫在行首,否者 JavaScript 引擎將會按程式碼塊執行。

let x;
{x} = {x: 1}; // 報錯

// 正確寫法
let x;
({x} = {x: 1});

字串解構賦值

字串解構賦值,將字串轉化成陣列物件

const [a,b,c] = '123456789';
const {length} = '123456789';
console.log(a, b, c, length); // 1, 2, 3, 9

函式解構賦值

const arr = [[1, 2], [3, 4]].map(([a, b]) => a + b);
console.log(arr); // [ 3, 7 ]

解構賦值規則

解構賦值的規則是,只要等號右邊的值不是物件或陣列,就先將其轉為物件。由於 undefined 和 null 無法轉為物件,所以對它們進行解構賦值,都會報錯。

let {toString: n} = 123;
n === Number.prototype.toString // true

let {toString: b} = true;
b === Boolean.prototype.toString // true

let { prop: u } = undefined; // 報錯
let { prop: n } = null; // 報錯

解構賦值的用途

  • 交換變數的值
  • 從物件、陣列中取值(提取 JSON 資料),或從函式中返回多個值
  • 函式解構賦值傳參,給定函式引數的預設值
  • 輸入模組的指定方法
  • 遍歷 Map 結構
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + ' is ' + value);
}
// first is hello
// second is world

字串擴充套件(不含編碼)

for...of 遍歷字串

for(let codePoint of 'string'){
  console.log(codePoint)
}
// 's'
// 't'
// 'r'
// 'i'
// 'n'
// 'g'

includes(),startsWith(),endsWith()

三個方法都接收兩個引數,第一個引數為檢索的值,第二個引數為檢索的起始位置,返回布林值

let s = 'Hello world!';

const [a, b, c] = [
    s.startsWith('Hello', 2),
    s.endsWith('!'),
    s.includes('o w')
];

console.log(a, b, c); // false true true

repeat()

repeat 方法返回一個新字串,表示將原字串重複 n 次。

  • 引數為[-Infinity,-1]或者 Infinity,會報錯;
  • 引數為(-1,1)時,相當於引數為 0;
  • 引數為小數時向下取整;
  • 引數 NaN 等同於 0;
  • 引數是字串,則會先轉換成數字。
'str'.repeat('3') // 'strstrstr'

padStart(), padEnd()

padStart(),padEnd()有兩個引數,第一個引數為字串補全生效的最大長度,第二個引數為補全的字串。

第二個引數預設為空格,省略第二個引數時預設用空格補全。

第一個引數小於字串原長度時,返回原字串。

如果用來補全的字串與原字串,兩者的長度之和超過了最大長度,則會截去超出位數的補全字串。

常見用途:補全指定位數,提示字串格式。

'123456'.padStart(10, '0') // "0000123456"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

模版字串(``)

const str = 'world';
const template = `Hello ${str}`;
console.log(template); // Hello world

正則擴充套件(略)

數值擴充套件

二進位制、八進位制表示法

使用二進位制表示法,字首為 0b,使用八進位制表示法,字首為 0o,ES6 不支援使用 00 字首表示八進位制。

進位制轉換使用 toString 方法,使用 Number 方法直接轉十進位制。

0b1100100 === 100; // true
0o144 === 100; // true

(0b1100100).toString(8); // 144
(0b1100100).toString(10); // 100
Number('0b1100100'); // 100

Number.isFinite(),Number.isNaN()

Number.isFinite()用來檢查一個數值是否為有限的(finite),即不是 Infinity。引數型別不是數值,Number.isFinite 一律返回 false。

Number.isNaN()用來檢查一個值是否為 NaN。引數型別不是 NaN,Number.isNaN 一律返回 false。

Number.isFinite(15); // true
Number.isFinite(-Infinity); // false

Number.isNaN(15) // false
Number.isNaN(9/0) // true

Number.parseInt(), Number.parseFloat()

ES6 將全域性方法 parseInt()和 parseFloat(),移植到 Number 物件上面,行為完全保持不變。

Number.isInteger()

Number.isInteger()用來判斷一個數值是否為整數。

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false

ES6 新增 Number 常量

  • Number.EPSILON 極小常量,浮點數誤差小於這個值可以認為不存在誤差;
  • Number.MAX_SAFE_INTEGER 安全整數的最大範圍;
  • Number.MIN_SAFE_INTEGER 安全整數的最小範圍;

Number.isSafeInteger() 用來判斷一個整數是否落在安全整數範圍之內。

Number.isSafeInteger(9007199254740993) // false
Number.isSafeInteger(990) // true
Number.isSafeInteger(9007199254740993 - 990) // true

Math 物件的擴充套件

Math.trunc() 除去一個數的小數部分,返回整數部分。引數不是數值,內部會先呼叫 Nunber()專為數值,對於空值和無法擷取整數的值,返回 NaN。(Math 物件的擴充套件的方法對於非數值的處理方法都一樣)

Math.trunc(5.9) // 5
Math.trunc(-4.9) // -4
Math.trunc(null) // 0
Math.trunc('foo'); // NaN

Math.sign() 判斷一個數是正數、負數、還是零。

Math.sign(-5) // -1 負數
Math.sign(5) // +1 正數
Math.sign(0) // +0 零
Math.sign(-0) // -0 零
Math.sign(NaN) // NaN

Math.cbrt() 計算一個數的立方根。

Math.cbrt(2)  // 1.2599210498948734

// Math.sqrt(x) 計算平方根
Math.sqrt(2) // 1.4142135623730951

// 冪運算 Math.pow(x,y)
Math.pow(2, 3)

Math.hypot() 返回所有引數的平方和的平方根。

Math.hypot(3, 4);        // 5
Math.hypot(3, 4, 5);     // 7.0710678118654755

函式擴充套件

rest 引數

ES6 引入 rest 引數(形式為...變數名),用於獲取函式的多餘引數,rest 引數搭配的變數是一個陣列,該變數將多餘的引數放入陣列中。只能是最後一個引數,函式的 length 屬性,不包括 rest 引數。

function sum1(x, y, ...args) {
    let sum = 0;

    for (let arg of args) {
        sum += arg;
    }

    return sum;
}

console.log(sum1(1, 2, 3, 4)) // 7

function sum2(...args) {
    return args.reduce((prev, curr) => {
        return prev + curr
    }, 0)
}

console.log(sum2(1, 2, 3)); // 6

name 屬性

函式的 name 屬性,返回該函式的函式名。對於匿名函式,ES5 返回'',ES6 返回變數名;
Function 建構函式返回的函式例項,name 屬性的值為 anonymous;bind 返回的函式,name 屬性值會加上 bound 字首。

function fn() {}
fn.name // 'fn'

function foo() {};
foo.bind({}).name // 'bound foo'

(function(){}).bind({}).name // 'bound '

箭頭函式

const fn = v => v;

// 等同於
const fn = function (v) {
  return v;
};

注意要點

  • 函式體內的 this 物件,就是定義時所在的物件,而不是使用時所在的物件;
  • 不可以當作建構函式,即不可以使用 new 命令,否則會丟擲一個錯誤;
  • 不可以使用 arguments 物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替;
  • 不可以使用 yield 命令,因此箭頭函式不能用作 Generator 函式。

尾呼叫優化

尾呼叫指函式的最後一步是呼叫另一個函式。

function f(x){
  'use strict';
  return g(x);
}

函式呼叫會在記憶體形成一個“呼叫記錄”,又稱“呼叫幀”(call frame),儲存呼叫位置和內部變數等資訊。如果在函式 A 的內部呼叫函式 B,那麼在 A 的呼叫幀上方,還會形成一個 B 的呼叫幀。等到 B 執行結束,將結果返回到 A,B 的呼叫幀才會消失。如果函式 B 內部還呼叫函式 C,那就還有一個 C 的呼叫幀,以此類推。所有的呼叫幀,就形成一個“呼叫棧”(call stack)。

尾呼叫由於是函式的最後一步操作,所以不需要保留外層函式的呼叫幀,因為呼叫位置、內部變數等資訊都不會再用到了,只要直接用內層函式的呼叫幀,取代外層函式的呼叫幀就可以了,這樣可以防止記憶體溢位,達成尾呼叫優化。

ES6 的尾呼叫優化只在嚴格模式下開啟,正常模式是無效的。

陣列擴充套件

擴充套件運算子

擴充套件運算子(spread)是三個點(...)。它好比 rest 引數的逆運算,將一個陣列轉為用逗號分隔的引數序列。

const arr = [1, 2, 3];
arr.push(...[4, 5, 6]);

擴充套件運算子的應用

  • 陣列展開
const arr = [1, 2, 3];
...arr // 1, 2, 3
  • 複製陣列
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

// 相當於
const a1 = [1, 2];
const a2 = a1.concat();
  • 合併陣列。淺拷貝只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。以下的兩種方法屬於淺拷貝,如果修改了原陣列的成員,會同步反映到新陣列。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合併陣列
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合併陣列
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
  • 解構賦值,字串轉陣列
const list = [1, 2, 3];
[a, ...b] = list;
console.log(a) // 1
console.log(b) // [2, 3]

[...'hello'] // ['h', 'e', 'l', 'l', 'o']

Array.from()

Array.from 方法用於將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件(包括 ES6 新增的資料結構 Set 和 Map)。

常見的類似陣列的物件有 DOM 操作返回的 NodeList 集合,以及函式內部的 arguments 物件。

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

Array.from('hello');
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b']);
Array.from(namesSet); // ['a', 'b']

Array.from 還可以接受第二個引數,作用類似於陣列的 map 方法,用來對每個元素進行處理,將處理後的值放入返回的陣列。

let arrayLike = {
    '0': 1,
    '1': 2,
    '2': 3,
    length: 3
};
Array.from(arrayLike, x => x * x); // [ 1, 4, 9 ]

Array.of()

Array.of 方法用於將一組值,轉換為陣列。這個方法的主要目的,是彌補陣列建構函式 Array()的不足。因為引數個數的不同,會導致 Array()的行為有差異。

Array.of() // []
Array.of(undefined) // [undefined]
Array.of(1) // [1]
Array.of(1, 2) // [1, 2]

copyWithin()

引數:

  • target(必需):從該位置開始替換資料。如果為負值,表示倒數。
  • start(可選):從該位置開始讀取資料,預設為 0。如果為負值,表示倒數。
  • end(可選):到該位置前停止讀取資料,預設等於陣列長度。如果為負值,表示倒數。

這三個引數都應該是數值,如果不是,會自動轉為數值。

[1, 2, 3, 4, 5].copyWithin(0, 3)

find() 和 findIndex()

陣列例項的 find 方法,用於找出第一個符合條件的陣列成員,如果沒有符合條件的成員,則返回 undefined。

findIndex 方法返回第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則返回-1。

[1, 4, -5, 10].find(n => n < 0); // -5
[1, 4, -5, 10].findIndex(n => n < 0); // 2

兩個方法都可以接受第二個引數,用來繫結回撥函式的 this 物件。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);  // 26

這兩個方法都可以發現 NaN,彌補了陣列的 indexOf 方法的不足。

fill() 填充陣列

fill 方法使用給定值,填充一個陣列。fill 方法可以接受第二個和第三個引數,用於指定填充的起始位置和結束位置。如果填充的型別為物件,那麼被賦值的是同一個記憶體地址的物件,而不是深拷貝物件,改變陣列中的一項,則所有項都改變。

let arr = Array.of(1, 2, 3).fill({
    num: 20
});

console.log(arr); // [ { num: 20 }, { num: 20 }, { num: 20 } ]

arr[0].num = 10;
console.log(arr); // [ { num: 10 }, { num: 10 }, { num: 10 } ]

entries(),keys() 和 values() 遍歷陣列

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

includes()

includes 方法返回一個布林值,表示某個陣列是否包含給定的值,與字串的 includes 方法類似。該方法的第二個參數列示搜尋的起始位置,第二引數是負數,取它的倒數,第二引數大於陣列長度,取 0。

[1, 2, 3].includes(3, -1); // true

flat(),flatMap()

flat()預設只會“拉平”一層,如果想要“拉平”多層的巢狀陣列,可以將 flat()方法的引數寫成一個整數,表示想要拉平的層數,預設為 1。

flat()的引數為 2,表示要“拉平”兩層的巢狀陣列。如果不管有多少層巢狀,都要轉成一維陣列,可以用 Infinity 關鍵字作為引數。

[1, [2, [3]]].flat(Infinity);
// [1, 2, 3]

flatMap()先遍歷陣列,再“拉平”一層,也只能拉平一層。引數魚 map()方法類似。
ß

[2, 3, 4].flatMap(x => [x, x * 2]); // [2, 4, 3, 6, 4, 8]

// 相當於
[2, 3, 4].map(x => [x, x * 2]).flat(); // [2, 4, 3, 6, 4, 8]

物件擴充套件

屬性簡潔表示法

const a = 1;
const b = 2;

const c = {a, b};
// 等同於
const c = {a: a, b: b};

const o = {
  method() {
    return "Hello!";
  }
};
// 等同於
const o = {
  method: function() {
    return "Hello!";
  }
};

function f(x, y) {
  return {x, y};
}
// 等同於
function f(x, y) {
  return {x: x, y: y};
}

物件的擴充套件運算子

物件擴充套件符類似陣列擴充套件符,主要用於解構賦值。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);

Object.is()

Object.is就是部署這個演算法的新方法。它用來比較兩個值是否嚴格相等,與嚴格比較運算子(===)的行為基本一致。

Object.is('str', 'str'); // true
Object.is({}, {}); // false

不同之處只有兩個:一是+0不等於-0,二是NaN等於自身。

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Object.assign()

Object.assign方法用於物件的合併,將源物件(source)的所有可列舉屬性,複製到目標物件(target)。

Object.assign方法的第一個引數是目標物件,後面的引數都是源物件。如果目標物件與源物件有同名屬性,或多個源物件有同名屬性,則後面的屬性會覆蓋前面的屬性。

由於undefined和null無法轉成物件,所以如果它們作為首引數,就會報錯。

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

常見用途:

  • 為物件新增屬性和方法
  • 克隆或合併物件
  • 給屬性指定預設值

其他

本文參考《ECMAScript 6 入門》,瞭解更多請點選跳轉點選跳轉

相關文章