ES6讀書筆記(一)

NO IMAGE

前言

前段時間整理了ES5的讀書筆記:《你可能遺漏的JS知識點(一)》《你可能遺漏的JS知識點(二)》,現在輪到ES6了,總共分為四篇,以便於知識點的梳理和查看,本篇內容包括:

  • 一、let和const
  • 二、解構賦值
  • 三、字符串擴展
  • 四、數值擴展
  • 五、正則擴展
  • 六、Symbol

本文筆記也主要是根據阮一峰老師的《ECMAScript 6 入門》和平時的理解進行整理的,希望對你有所幫助,喜歡的就點個贊吧!

一、let和const

1.1 let

1.聲明變量的方法有6種:var、 function、 let、 const、 import 、class。

2.函數內let聲明的變量不能提前使用,沒有變量提升。

3.暫時性死區:在代碼塊內,使用let命令聲明變量之前,該變量都是不可用的,只要塊級作用域內存在let命令,它所聲明的變量就“綁定”(binding)這個區域,不再受外部的影響:

// 例一:
var tmp = 123;
if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
// 例二:
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 報錯,會出現暫時性死區,從左到右,x=y時y還未聲明
// 例三:
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]  反過來就可以,此時x已聲明
// 例四:
var x = x;  // 不報錯
let x = x;  // 報錯  ReferenceError: x is not defined  變量x的聲明語句還沒有執行完成前,就去取x的值,導致報錯
// 例五:
function func(arg) {
let arg; // 報錯 不能在同一作用域聲明同一個變量
}
function func(arg) {
{
let arg; // 不報錯 因為不在同一作用域
}
}

4.for循環裡面是父級作用域,塊裡面是單獨的子作用域:

for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc

5.頂層對象:在瀏覽器環境指的是window對象,在Node指的是global對象。

在ES5之中,頂層對象的屬性與全局變量是等價的:

var a = 1;
window.a  // 1
// 如果在Node的REPL環境,可以寫成global.a
// 或者採用通用方法,寫成 this.a

在ES6中,let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性:

let b = 1;
window.b  // undefined
// 這說明從ES6開始,全局變量將逐步與頂層對象的屬性脫鉤

6.塊作用域函數:不建議這樣聲明函數,應該使用函數表達式,避免函數提升的兼容性:

// 在塊內聲明函數
{ 
foo();  
function foo() {} 
} 
foo();  // ReferenceError

1.2 const

1.const實際上保證的,並不是變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,因此等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即總是指向另一個固定的地址),至於它指向的數據結構是不是可變的,就完全不能控制了。

二、解構賦值

從數組和對象中提取值,對變量進行賦值,稱為解構。

2.1 數組的解構

1.數組解構例子:

// 例一:
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
// 例二:
let [a, b] = [1, [2]];
a // 1
b // [2]
// 例三:
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
// 例四:
let [x, , y] = [1, 2, 3];
x // 1
y // 3
// 例五:
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
// 例六:
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

2.允許指定默認值:

let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a'];  // x='a', y='b'
let [x, y = 'b'] = ['a', undefined];  // x='a', y='b'

ES6內部使用嚴格相等運算符(===)來判斷一個位置是否有值,所以只有當一個數組成員嚴格等於undefined,默認值才會生效:

let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null

3.應用:

①交換變量的值:

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

②從函數返回多個值:

function example() {
return [1, 2, 3];
}
let [a, b, c] = example();

③解構結合展開/收集:

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

④默認值可以引用解構賦值的其他變量,但該變量必須已經聲明:

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

2.2 對象的解構

1.對象的解構與數組有一個重要的不同:數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值:

let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
// 如果變量名與屬性名不一致,必須寫成下面這樣:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

2.對象的解構賦值的內部機制,是先找到同名屬性,然後再賦給對應的變量;真正被賦值的是後者,而不是前者:
①匹配模式:

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

以上的foo是匹配的模式,baz才是變量,真正被賦值的是變量baz,而不是模式foo。

②對象簡寫的本質:

var [ a, b, c ] = [1, 2, 3];
var { x: x, y: y, z: z } = { x: 1, y: 2, z: 3};

對象簡寫後就是{x, y, z} = {x: 1, y: 2, z: 3}; 其實就是省略了"x:"這個部分。

③如果要不同名的對象屬性:

var { x: a, y: b, z: b } = { x: 1, y: 2, z: 3};
console.log(a, b, c);   // 1, 2, 3
console.log(x, y, z);   // referenceError

④解構變量的聲明:

// 先聲明變量再解構
var a, b, c, x, y, z;   
[a,b,c] = [1, 2, 3]; 
// 同時聲明和解構
var [a,b,c] = [1, 2, 3];   
// 如果不聲明,則對象需要用括號括起來,否則會被當成一個塊,而不是對象
var a, b, c, x, y, z; 
( { x, y, z } = { x: 1, y: 2, z: 3} ) 

⑤易錯:

let {foo: {bar}} = {baz: 'baz'};  // 報錯,因為首先foo這時等於undefined,再取子屬性bar就會報錯

3.應用:

①返回一個對象:

function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();

②重複賦值:

var { a: X, a: Y } = { a: 1 }; 
X;  // 1 
Y;  // 1
var { a: { x: X, x: Y }, a } = { a: { x: 1 } }; 
X;  // 1 
Y;  // 1 
a;  // { x: 1 } 
( { a: X, a: Y, a: [ Z ] } = { a: [ 1 ] } ); 
X.push( 2 ); 
Y[0] = 10; 
X;  // [10,2]   // 因為都是同一個引用,所以會同時改變為10
Y;  // [10,2] 
Z;  // 1

③鏈式賦值:

var o = { a:1, b:2, c:3 }, 
p = [4,5,6], 
a, b, c, x, y, z; 
( {a} = {b,c} = o ); 
[x,y] = [z] = p; 
console.log( a, b, c );         // 1 2 3 
console.log( x, y, z );         // 4 5 4  z也是4

2.3 函數參數的解構

1.例子:

// 例一:
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
// 例二:
[[1, 2], [3, 4]].map(([a, b]) => a + b);  // [ 3, 7 ]
// 例三:參數默認值
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3});      // [3, 0]
move({});          // [0, 0]
move();            // [0, 0]

2.應用:

①函數參數的定義:

//參數是一組有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
//參數是一組無次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});

②解構默認值+參數默認值:

function f6({ x = 10 } = {}, { y } = { y: 10 }) { 
console.log( x, y ); 
} 
f6();             // 10 10
f6( {}, {} );     // 10 undefined

③提取JSON數據:

let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);   // 42, "OK", [867, 5309]

④遍歷 Map 結構:

// 獲取鍵名
for (let [key] of map) {
// ...
}
// 獲取鍵值
for (let [,value] of map) {
// ...
}

2.4 字符串解構

1.字符串也可以解構賦值,這是因為此時字符串被轉換成了一個類似數組的對象:

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

2.類似數組的對象都有一個length屬性,因此還可以對這個屬性解構賦值:

let {length : len} = 'hello';   // 相當於 let {length: len} = {length: 5}
len // 5

2.5 數值和布爾值的解構

1.數值:

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

2.布爾值:

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

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

let { prop: x } = undefined;  // TypeError
let { prop: y } = null;       // TypeError

三、字符串擴展

1.包含:includes()、startsWith()、endsWith()

  • includes():返回布爾值,表示是否找到了參數字符串。
  • startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。
  • endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

這三個方法都支持第二個參數,表示開始搜索的位置:

let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5)   // true   第二參數表示針對的是前5個字符
s.includes('Hello', 6)   // false

2.重複:repeat()

返回一個新字符串,表示將原字符串重複n次:

'x'.repeat(3)         // "xxx"
'hello'.repeat(2)     // "hellohello"   
'na'.repeat(0)        // ""
'na'.repeat(2.9)      // "nana"  如果是小數,會被取整
'na'.repeat(Infinity) // RangeError   
'na'.repeat(-1)       // RangeError   負數報錯
'na'.repeat(-0.9)     // ""    0~-1會被取整為0
'na'.repeat(NaN)      // ""    NaN等同為0
'na'.repeat('na')     // ""      字符串會先轉換為數字,轉化了NaN,然後為0
'na'.repeat('3')      // "nanana"

3.補全長度:padStart(),padEnd()

返回新的字符串,不會改變原字符串:

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab')  // 'xabab'
'x'.padEnd(4, 'ab')  // 'xaba'

①如果原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串:

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab')   // 'xxx'

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

'abc'.padStart(10, '0123456789')  // '0123456abc'

③如果省略第二個參數,默認使用空格補全長度:

'x'.padStart(4) // '   x'
'x'.padEnd(4)   // 'x   '

用途:

①補全指定位數:

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"

②提示字符串格式:

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

3.模板字符串:“

①大括號ES6讀書筆記(一){‘hello’},還可以嵌套使用,以及引用對象屬性:

let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}` // "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`      // "3"

②模板字符串之中還能調用函數:

function fn() {
return "Hello World";
}
`foo ${fn()} bar`  // foo Hello World bar

③“類似於IIFE,會自行執行解析內部的變量/表達式,空格、換行、縮進都會被保存:

var text = 
`Now is the time for all good men 
to come to the aid of their 
country!`; 
console.log( text );    // 換行會被保存
// Now is the time for all good men 
// to come to the aid of their 
// country!
`A very ${upper( "warm" )} welcome`  // 可插入表達式

④運行函數:

alert`123`
// 等同於
alert(123)

4.可精確計算字符串長度:

var gclef = "一個長度用普通方法計算得不到2的特殊圖形符號"; 
[...gclef].length;               // 1  藉助擴展運算符
Array.from( gclef ).length;      // 1  藉助Array.from

5.擴展了碼點和字節的精確計算。

6.matchAll()返回一個正則表達式在當前字符串的所有匹配。

四、數值擴展

1.Number.isFinite()、Number.isNaN()、Number.isInteger():

①Number.isFinite()用來檢查一個數值是否為有限的(finite),即不是Infinity,如果參時不是數值,一律返回false:

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

②Number.isNaN()用來檢查一個值是否為NaN,注意與以前的isNaN()進行區別:

Number.isNaN(NaN)      // true
isNaN( 'NaN' );        // true
Number.isNaN( 'NaN' )  // false  已修正

與傳統的全局方法isFinite()和isNaN()的區別在於,傳統方法先調用Number()將非數值的值轉為數值,再進行判斷,而這兩個新方法只對數值有效,Number.isFinite()對於非數值一律返回false, Number.isNaN()只有對於NaN才返回true,非NaN一律返回false。

③Number.isInteger()用來判斷一個數值是否為整數(javaScript的數字值永遠都是浮點數,所以判斷整數,其實是判斷小數部分是否為0):

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

整數範圍:-2^53到2^53之間(不含兩個端點),超過這個範圍,無法精確表示這個值,Number.isSafeInteger()用來判斷一個整數是否落在這個範圍之內:

Math.pow(2, 53) === Math.pow(2, 53) + 1  // true

2.ES6將全局方法parseInt()和parseFloat()移植到Number對象上面,行為完全保持不變:

// ES5的寫法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6的寫法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
Number.parseInt === parseInt     // true
Number.parseFloat === parseFloat // true

3.Number.EPSILON

Number.EPSILON === Math.pow(2, -52)  // true

4.Math上的方法:

Math.trunc()用於去除一個數的小數部分,返回整數部分:

Math.trunc(4.1)     //  4
Math.trunc(4.9)     //  4
Math.trunc(-4.1)    // -4
Math.trunc(-4.9)    // -4
Math.trunc(-0.1234) // -0
Math.trunc(true)    //  1
Math.trunc(false)   //  0
Math.trunc(null)    //  0
Math.trunc(NaN);      // NaN
Math.trunc('foo');    // NaN
Math.trunc();         // NaN
Math.trunc(undefined) // NaN

Math.sign()用來判斷一個數到底是正數、負數、還是零。對於非數值,會先將其轉換為數值,它會返回五種值:

  • 參數為正數,返回+1;
  • 參數為負數,返回-1;
  • 參數為 0,返回0;
  • 參數為-0,返回-0;
  • 其他值,返回NaN。

5.前綴0b(或0B)表示二進制,0o(或0O)表示八進制:

0b111110111 === 503   // true
0o767 === 503         // true

6.指數運算符(**): 右結合

2 ** 2  // 4
2 ** 3  // 8
2 ** 3 ** 2  // 512  相當於 2 ** (3 ** 2)
let a = 1.5;
a **= 2;     // 等同於 a = a * a;

五、正則擴展

1.修飾符:

①unicode標識u:處理大於\uFFFF的 Unicode 字符。

②定點標識y(sticky)“粘連”:作用與g修飾符類似,也是全局匹配,後一次匹配都從上一次匹配成功的下一個位置開始,不同之處在於,g修飾符只要剩餘位置中存在匹配就可,而y修飾符確保匹配必須從剩餘的第一個位置開始:

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // ["aaa"]
r2.exec(s) // null

2.單單一個y修飾符對match方法,只能返回第一個匹配,必須與g修飾符聯用,才能返回所有匹配:

'a1a2a3'.match(/a\d/y)  // ["a1"]
'a1a2a3'.match(/a\d/gy) // ["a1", "a2", "a3"]

3.s修飾符,稱為dotAll模式,即點(dot)代表一切字符:

以前.點不能匹配以下:

  • U+000A 換行符(\n)
  • U+000D 回車符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)

但如果加上s修飾符,則可以匹配任意單個字符:

/foo.bar/.test('foo\nbar')  // false
/foo.bar/s.test('foo\nbar') // true

4.flags屬性:

var re = /foo/ig; 
re.flags;           // "gi" 默認順序為"gimuy"
new RegExp(/abc/ig, 'i').flags   // 'i' 原有正則對象的修飾符是ig,它會被第二個參數i覆蓋

5.字符串對象共有 4 個方法,可以使用正則表達式:match()、replace()、search()和split()。

ES6 將這 4 個方法,在語言內部全部調用RegExp的實例方法,從而做到所有與正則相關的方法,全都定義在RegExp對象上:

  • String.prototype.match 調用 RegExp.prototype[Symbol.match]
  • String.prototype.replace 調用 RegExp.prototype[Symbol.replace]
  • String.prototype.search 調用 RegExp.prototype[Symbol.search]
  • String.prototype.split 調用 RegExp.prototype[Symbol.split]

6.具名組匹配: ?<組名>

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year;   // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day;     // 31

六、Symbol

1.表示獨一無二的值,是一種類似於字符串的數據類型(不是引用類型),通過Symbol函數生成(不能使用new命令),可以保證不會與對象中的其他屬性名產生衝突。

let s = Symbol();
typeof s  // "symbol"

Symbol函數可以接受一個字符串作為參數,表示對 Symbol 實例的描述,主要是為了在控制檯顯示,或者轉為字符串時,比較容易區分:

let s1 = Symbol('foo');
let s2 = Symbol('bar');
s1  // Symbol(foo)
s2  // Symbol(bar)
s1.toString()  // "Symbol(foo)"
s2.toString()  // "Symbol(bar)"

如果 Symbol 的參數是一個對象,就會調用該對象的toString方法,將其轉為字符串,然後才生成一個 Symbol 值:

const obj = {
toString() {
return 'abc';
}};
const sym = Symbol(obj);
sym   // Symbol(abc)

Symbol函數的參數只是表示對當前 Symbol 值的描述,因此相同參數的Symbol函數的返回值是不相等的:

// 沒有參數的情況
let s1 = Symbol();
let s2 = Symbol();
s1 === s2  // false
// 有參數的情況
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2  // false

Symbol值不能與其他類型的值進行運算,會報錯,可顯式轉換類型後再運算:

let sym = Symbol('My symbol');
"your symbol is " + sym  // TypeError: can't convert symbol to string
//轉為字符串:
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString()+"123"    // 'Symbol(My symbol)123'
//轉為布爾值:
Boolean(sym)  // true
//但不能轉為數值:
Number(sym)  // TypeError

2.作為屬性名,是公開屬性:要使用方括號,使用屬性時也是,不會出現在for…in、for…of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,但也不是私有屬性,Object.getOwnPropertySymbols方法可以獲取指定對象的所有 Symbol 屬性名:

let mySymbol = Symbol();
// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';
// 第二種寫法
let a = {
[mySymbol]: 'Hello!'
};
// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"

不能用點運算符:

const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a['mySymbol']  // "Hello!"
a[mySymbol]    // undefined

3.也可作為屬性值:定義一組常量,保證這組常量的值都是不相等的。

const log = {};
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');

4.遍歷Symbol屬性:

①Object.getOwnPropertySymbols()返回一個數組,成員是當前對象的所有用作屬性名的 Symbol 值:

const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols  // [Symbol(a), Symbol(b)]

②Reflect.ownKeys()返回所有類型的鍵名,包括常規鍵名和 Symbol 鍵名:

let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)  //  ["enum", "nonEnum", Symbol(my_key)]

5.Symbol.for()接受一個字符串作為參數,然後搜索有沒有以該參數作為名稱的Symbol值。如果有,就返回這個Symbol值,否則就新建並返回一個以該字符串為名稱的Symbol值,它登記的名字,是全局環境的,可以在不同的iframe或service worker中取到同一個值:

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2   // true
Symbol.for("bar") === Symbol.for("bar")   // true
Symbol("bar") === Symbol("bar")   // false

6.Symbol.keyFor()返回一個已登記的 Symbol 類型值的key:

let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined  變量s2屬於未登記的 Symbol 值,所以返回undefined

6.內置的Symbol值:對象的Symbol.iterator屬性,指向該對象的默認遍歷器方法。

最後

好了,本篇就到這裡,主要都是摘抄常用的知識點和備註自己的理解,希望對你有所幫助,後面會持續更新,歡迎關注!也感謝你能看到這裡!

GitHub傳送門
博客園傳送門

ES6讀書筆記(一)

相關文章

Vue源碼該如何入手?

理理Vue細節

ES6讀書筆記(三)

ES6讀書筆記(二)