ES6讀書筆記(二)

NO IMAGE
目錄

前言

前段時間整理了ES6的讀書筆記:《ES6讀書筆記(一)》,現在為第二篇,本篇內容包括:

  • 一、數組擴展
  • 二、對象擴展
  • 三、函數擴展
  • 四、Set和Map數據結構
  • 五、Reflect

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

一、數組擴展

1. 擴展運算符

①複製數組:

const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

②求最大值:

Math.max(...[14, 3, 77])
Math.max.apply(null, [14, 3, 77])

③合併數組:

let arr1 = ['a', 'b'];
let arr2 = ['c'];
let arr3 = ['d', 'e'];
// ES5:
arr1.concat(arr2, arr3);   // [ 'a', 'b', 'c', 'd', 'e' ] 為淺拷貝,slice、Object.assign()也為淺拷貝
// ES6:
[...arr1, ...arr2, ...arr3]    // [ 'a', 'b', 'c', 'd', 'e' ] 淺拷貝
// push:
Array.prototype.push.apply(arr1, arr2);   // 因為push參數不能為數組
arr1.push(...arr2);

④數組克隆:

// ES5:
const a1 = [1, 2];
const a2 = a1.concat();
// ES6:
const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

⑤與解構賦值結合:

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list
const [first, ...rest] = [1, 2, 3, 4, 5];  
first  // 1
rest  // [2, 3, 4, 5]
// 賦值時即在左邊時只能放在參數最後一位
const [...butLast, last] = [1, 2, 3, 4, 5];  // 報錯
const [first, ...middle, last] = [1, 2, 3, 4, 5];  // 報錯
var arr1 = ['a', 'b'];
var a2 = [...arr1,2];   // 這樣是沒問題的,因為是擴展運算
console.log(a2)   // ['a', 'b', 2] 

⑥將字符串轉為真正的數組:

[...'hello']   // [ "h", "e", "l", "l", "o" ]   
Array.from('hello')   // [ "h", "e", "l", "l", "o" ] 
//擴展運算符內部調用的是數據結構的Iterator接口,因此只有Iterator接口的對象才可以用擴展運算符轉為真正的數組,Array.from也是如此,同時Array.from還支持轉化類似數組的對象,即帶有length屬性的對象,可不含遍歷器接口(Symbol.iterator):
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr = [...arrayLike];  // TypeError: Cannot spread non-iterable object.
// 這個類似數組的對象沒有Iterator接口,所以不能轉化,可以使用Array.from()
Array.from({ length: 3 });  // [ undefined, undefined, undefined ]
Array.from({ length: 2 }, () => 'jack')   // ['jack', 'jack']

⑦擴展運算符後面是一個空數組,則不產生任何效果:

[...[], 1]  // [1]

2. Array上的方法:

①Array.from(數組,回調函數(val,index),this綁定)
第二個參數,類似於map方法,第三參數,this指向:

Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]

應用:
(1)可用於轉化/拷貝數組:

//原方式:
Array.prototype.slice.call(arr);
let arr1 = ['a', 'b', 'c'];
let arr2 = Array.from(arr1); // ['a', 'b', 'c']

(2)處理空位:

var arrLike = { 
length: 4, 
2: "foo" 
}; 
Array.from( arrLike ); 
// [ undefined, undefined, "foo", undefined ]  // 和Array(3)產生空槽位不一樣,這裡是有undefined值的,會將空位轉為undefined

②Array.of():用於將一組值,轉換為數組:
與Array()不同,Array()的單參數會變為長度:

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
//模擬:
function ArrayOf(){
return [].slice.call(arguments);
}

3. Array實例的方法:

①find((val, index, arr)=>{}, this) :用於找出【第一個】符合條件的【數組成員】,它的參數是一個回調函數,所有數組成員依次執行該回調函數,直到找出第一個返回值為true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined,第二參數為this綁定。

[1, 4, -5, 10].find((n) => n < 0);
// -5  不是返回數組
[1, 5, 10, 15].find((value, index, arr) => value > 9); // 10
var a = [1,2,3,4,5];
(a.indexOf("2") != -1);   // false  indexOf是嚴格匹配===的,所以不會轉化字符串
a.find(v => v == "2");    // 2  返回這個匹配的值,而不是返回布爾值
a.find(v => v == 7);      // undefined 
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find((v) => { v > this.age;}, person);    // 26

②findIndex((val, index, arr)=>{}, this) :返回第一個符合條件的數組成員的位置,如果所有成員都不符合條件,則返回-1,第二參數為this綁定。

find()和findIndex()都彌補了indexOf方法對NaN的不足:

[NaN].indexOf(NaN)   // -1
[NaN].findIndex(y => Object.is(NaN, y))   // 0

③copyWithin(target, start = 0, end = this.length)
target(必需):從該位置開始替換數據。如果為負值,表示倒數。
start(可選):從該位置開始讀取數據,默認為 0。如果為負值,表示倒數。
end(可選):到該位置 前 (這個位置前,所以不包含這個位置)停止讀取數據,默認等於數組長度。如果為負值,表示倒數。

[1, 2, 3, 4, 5].copyWithin(0, 3)   // [4, 5, 3, 4, 5]  4、5替換1、2
[1, 2, 3, 4, 5].copyWithin(0, 2)   // [3, 4, 5, 4, 5]  3、4、5替換1、2、3
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)  // [4, 2, 3, 4, 5]  4替換1

④entries(),keys()和values()——用於遍歷數組:返回的是遍歷器對象,可使用for of或者擴展運算符遍歷出來:

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"
var a = [1,2,3]; 
[...a.values()];              // [1,2,3] 
[...a.keys()];                // [0,1,2] 
[...a.entries()];             // [ [0,1], [1,2], [2,3] ] 
[...a[Symbol.iterator]()];    // [1,2,3]
//也可手動next調用:
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']

⑤includes(包含項,起始位置): Array.prototype.includes方法返回一個布爾值(而find返回的是值),表示某個數組是否包含給定的值,與字符串的includes方法類似:

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true  彌補了indexOf(使用===)對NaN的不足

//該方法的第二個參數表示搜索的起始位置,默認為0。如果第二個參數為負數,則表示倒數的位置,如果這時它大於數組長度(比如第二個參數為-4,但數組長度為3),則會重置為從0開始:

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

⑥fill(填充項,起始位置,結束位置):使用給定值填充數組

var a = Array(4).fill(undefined); 
a;   // [undefined,undefined,undefined,undefined]
var a = [null, null, null, null].fill(42, 1, 3); 
a;   // [null,42,42,null]  不包含結束位置
//如果被賦值的是引用類型,是淺拷貝:
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr    // [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

⑦flat():用於將嵌套的數組“拉平”,變成一維的數組。該方法返回一個新數組,對原數據沒有影響:

[1, 2, [3, 4]].flat()   // [1, 2, 3, 4]

參數可設置拉平幾層:

[1, 2, [3, [4, 5]]].flat(2)   // [1, 2, 3, 4, 5]

不管幾層使用Infinity:

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

會跳過空位:

[1, 2, , 4, 5].flat()   // [1, 2, 4, 5]

flatMap():只能展開一層,相當於map方法,第二參為this

[2, 3, 4].flatMap((x) => [x, x * 2])     // [2, 4, 3, 6, 4, 8]
//相當於:
[2, 3, 4].map((x) => [x, x * 2 ]).flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]]) // 只能展開一層 [[2], [4], [6], [8]]

4. 數組空位:空位不是undefined,是指沒有值

var a = Array(3);
console.log(a)     // [empty × 3]
console.log(a[0])  // undefined 為空,找不到值,所以返回undefined
0 in [undefined, undefined, undefined] // true  說明0號位是有值的
0 in [, , ,]   // false

二、對象擴展

1. 對象屬性

①簡寫:{x, y}

②屬性名錶達式:

let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word']   // "hello"
a[lastWord]    // "world"
a['last word']   // "world"
//表達式還可以用於定義方法名:
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
//屬性名錶達式如果是一個對象,默認情況下會自動將對象轉為字符串[object Object]:
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}  'valueA'被覆蓋了

③屬性的可枚舉性:設置為false,規避了for in操作,防止遍歷到不可枚舉屬性。

Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false

2. 對象的解構賦值+擴展運算符:只能用在最後。

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

為淺拷貝:

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

擴展運算符:用於取出參數對象的所有可遍歷屬性(應該就只是自身可枚舉屬性),拷貝到當前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n  // { a: 3, b: 4 }

數組是特殊的對象,所以對象的擴展運算符也可以用於數組:

{...['a', 'b', 'c']}  // {0: "a", 1: "b", 2: "c"}

如果擴展運算符後面是字符串,它會自動轉成一個類似數組的對象:

{...'hello'}   // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

如果擴展運算符後面是一個空對象,則沒有任何效果:

{...{}, a: 1}  // { a: 1 }

如果擴展運算符後面不是對象,則會自動將其轉為對象:

{...1}  // {} 等同於 {...Object(1)}

對象的擴展運算符等同於使用Object.assign()方法:

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

上面的例子只是拷貝了對象實例的屬性,如果想完整克隆一個對象,還拷貝對象原型的屬性,可以採用下面的寫法:

// 寫法一
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 寫法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);

擴展運算符可以用於合併兩個對象:

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

3. super對象:指向當前對象的原型對象,super.foo等同於Object.getPrototypeOf(this).foo,只能用在對象的簡寫形式的方法之中

var o1 = { 
foo() { 
console.log("o1:foo"); 
} 
}; 
var o2 = { 
foo() { 
super.foo();   // super相當於Object.getPrototypeOf(o2),指向o1,所以得到"o1:foo"的結果
console.log("o2:foo"); 
} 
}; 
Object.setPrototypeOf(o2, o1); 
o2.foo();      // o1:foo 
// o2:foo

4. Object上新增的方法:

(1)Object.is():比較兩個值是否相等,與嚴格比較運算符(===)的行為基本一致,不同之處只有兩個:一是+0不等於-0,二是NaN等於自身:

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

(2)Object.assign():用於對象的合併,將源對象(source)的所有可枚舉屬性,複製到目標對象(target)。

// 如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性:

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}
// 如果只有一個參數,Object.assign會直接返回該參數:
const obj = {a: 1};
Object.assign(obj) === obj // true
// 如果該參數不是對象,則會先轉成對象,然後返回:
typeof Object.assign(2) // "object"
// undefined和null無法轉成對象,所以如果它們作為參數,就會報錯:
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
// 如果非對象不是首參數,則會跳過(字符串會轉為數字鍵對象),不會報錯:
let obj = {a: 1};
Object.assign(obj, undefined) === obj   // true
Object.assign(obj, null) === obj   // true
// 其他類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。但是,除了字符串會以數組形式,拷貝入目標對象,其他值都不會產生效果:
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
// 只拷貝源對象的自身可枚舉屬性,包括屬性名為 Symbol 值的屬性:
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
// 且是淺拷貝:
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
// 可以用來處理數組,但是會把數組視為對象:
Object.assign([1, 2, 3], [4, 5])   // [4, 5, 3]

上面代碼中,Object.assign把數組視為屬性名為 0、1、2 的對象,因此源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。

應用:
(1)為對象添加屬性和方法:

class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}}

上面方法通過Object.assign方法,將x屬性和y屬性添加到Point類的對象實例。

(2)克隆或合併多個對象:

function clone(origin) {
return Object.assign({}, origin);
}

上面代碼將原始對象拷貝到一個空對象,就得到了原始對象的克隆。
不過,採用這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以採用下面的代碼。

function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}

(3)為屬性指定默認值:

const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
console.log(options);
// ...
}

上面代碼中,DEFAULTS對象是默認值,options對象是用戶提供的參數。Object.assign方法將DEFAULTS和options合併成一個新對象,如果兩者有同名屬性,則option的屬性值會覆蓋DEFAULTS的屬性值。

(3)Object.getOwnPropertyDescriptor():返回某個對象屬性的描述對象。

const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj);
/*{ foo:
{ value: 123,
writable: true,
enumerable: true,
configurable: true 
},
bar:
{ get: [Function: get bar],
set: undefined,
enumerable: true,
configurable: true 
} 
}
*/

繼承:

const obj = Object.create(prot);
obj.foo = 123;
// 或者
const obj = Object.assign(
Object.create(prot),
{
foo: 123,
});

有了Object.getOwnPropertyDescriptors(),我們就有了另一種寫法:

const obj = Object.create(
prot,
Object.getOwnPropertyDescriptors({
foo: 123,
}));

(4)Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替__proto__

①Object.setPrototypeOf(obj, prototype)

②Object.getPrototypeOf(obj)
如果參數不是對象,會被自動轉為對象

(5)Object.keys()、Object.values()、Object.entries(),返回的都是數組,數組也有這些方法,但是返回的是遍歷器對象。

①Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名:

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj);
// ["foo", "baz"]

②Object.values方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值:

const obj = {100: 'a', 2: 'b', 7: 'c'};
Object.values(obj);
// ["b", "c", "a"]
// 屬性名為數值的屬性,是按照數值大小,從小到大遍歷的,因此返回的順序是b、c、a

③Object.entries方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對數組:

const obj = {foo: 'bar', baz: 42};
Object.entries(obj);
// [ ["foo", "bar"], ["baz", 42] ]

(6)Object.fromEntries():是Object.entries()的逆操作,用於將一個鍵值對數組轉為對象。

Object.fromEntries([
['foo', 'bar'],
['baz', 42]])
// { foo: "bar", baz: 42 }

特別適合將Map結構轉為對象:

const entries = new Map([
['foo', 'bar'],
['baz', 42]]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

三、函數擴展

1. 函數參數

(1)默認參數:

①不能重複聲明:

function foo(x = 5) {
let x = 1;   // error
const x = 2; // error
}

②默認參數的位置應該是寫在參數的末尾,這樣才是自適應省略傳參,否則如果默認參數放前面,如果不給默認傳參,想給其它參數傳參,則還是得向默認參數傳遞一個undefined,否則所傳參數會覆蓋默認參數,造成混淆。

③指定了默認值以後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真:

(function (a) {}).length         // 1
(function (a = 5) {}).length     // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length     // 0

如果設置了默認值的參數不是尾參數,那麼length屬性也不再計入後面的參數了:

(function (a = 0, b, c) {}).length   // 0
(function (a, b = 1, c) {}).length   // 1

④產生作用域:
一旦設置了參數的默認值,函數進行聲明初始化時,參數會形成一個單獨的作用域(context)。等到初始化結束,這個作用域就會消失。這種語法行為,在不設置參數默認值時,是不會出現的:

var x = 1;
function f(x, y = x) {   // 不會找到外部的x,因為參數作用域中第一個參數已經聲明有了x,所以會y=x的x會指向它,而不會指向外部的x
console.log(y);
}
f(2); // 2
f();  // undefined
//----------------------------------
let x = 1;
function f(y = x) {  // 會找到外部的x,因為參數中未聲明x,所以會往上層作用域查找x
let x = 2;
console.log(y);
}
f(3); // 3
f();  // 1

⑤默認值表達式是惰性求值的,這意味著它們只在需要的時候運行——即在參數的值省略或者為undefined的時候:

function bar(val) { 
console.log("bar called!"); 
return y + val; 
} 
function foo(x = y + 3, z = bar(x)) {  // 形參作用域是在函數聲明包裹的作用域,而不是在函數體
console.log(x, z); 
} 
var y = 5; 
foo();                  // "bar called"
// 8 13
foo(10);                // "bar called"
// 10 15
y = 6; 
foo(undefined, 10);     // 9 10
//--------------------------------------------
var w = 1, z = 2; 
function foo(x = w + 1, y = x + 1, z = z + 1) { 
console.log(x, y, z); 
} 
foo();    // ReferenceError 問題出在z+1中的z發現z是一個未初始化的參數變量,所以不會往上層作用域查找z

⑥undefined會觸發默認值,null不會。

(2)參數的解構賦值:

function foo({x, y = 5}) {
console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo()   // TypeError: Cannot read property 'x' of undefined 如果函數foo調用時沒提供參數,變量x和y就不會生成,從而報錯。通過提供函數參數的默認值,就可以避免這種情況:
function foo({x, y = 5} = {}) {
console.log(x, y);
}
foo() // undefined 5

區別以下:

// 寫法一
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 寫法二
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
// 函數沒有參數的情況
m1() // [0, 0]
m2() // [0, 0]
// x 和 y 都有值的情況
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 無值的情況
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]
// x 和 y 都無值的情況
m1({}) // [0, 0];
m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

(3)參數使用...運算符:

①擴展運算符...之後不能再有其他參數:

// 報錯
function f(a, ...b, c) {
// ...
}

②函數的length屬性,不包括rest參數:

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1

2. 箭頭函數

①函數體內的this對象,就是定義時所在的對象(找離它最近的一個執行環境作為執行上下文,如對象方法中的箭頭函數的this是指向window的,所以對象方法不要使用箭頭函數),而不是使用時所在的對象。由於箭頭函數沒有自己的this,所以當然也就不能用call()、apply()、bind()這些方法去改變this的指向:

function foo() {
// setTimeout中的函數默認由全局環境執行,所以此時this指向window
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo()  // id: 21
foo.call({id: 42});  // id: 42  call改變了this指向

②不可以當作構造函數,也就是說,不可以使用new命令,否則會拋出一個錯誤。

③不可以使用arguments對象,該對象在函數體內不存在。如果要用,可以用rest參數代替:

function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]   得到的是外部函數的參數

④不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。

不適用場合:

const cat = {
lives: 9,
jumps: () => {
this.lives--;
console.log(this.lives);
}
}
cat.jumps();  // NaN

this指向:普通函數的 this 是動態的,所以要在運行時找擁有當前上下文的對象。

而箭頭函數的 this 是靜態的,也就是說,只需要看箭頭函數在什麼函數作用域下聲明的,那麼這個 this 就會綁定到這個函數的上下文中。即“穿透”箭頭函數。

例子裡的箭頭函數並沒有在哪個函數裡聲明,所以 thisfallback 到全局,全局的lives未聲明,為undefined,運算後得到NaN。

(3)函數的name屬性:

var f = function() {};
// ES5
f.name    // ""
// ES6
f.name    // "f"
const bar = function baz() {};
// ES5
bar.name   // "baz"
// ES6
bar.name   // "baz"

(4)雙冒號運算符:

foo::bar;
// 等同於
bar.bind(foo);
foo::bar(...arguments);
// 等同於
bar.apply(foo, arguments);

(5)尾調用:指某個函數的最後一步是調用另一個函數。

function f(x){
return g(x);
}

四、Set和Map數據結構

4.1 Set

1. Set類似於數組,但是成員的值都是唯一的,沒有重複的值,它是構造函數,用於構造Set數據結構:
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4  不會添加重複值
2. Set函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作為參數,用來初始化:
const set = new Set([1, 2, 3, 4, 4]);  // 可為數組去重
set       // Set結構: {1, 2, 3, 4}
[...set]  // [1, 2, 3, 4]

數組或類數組去重:

Array.from(new Set(array))

也可去除字符串裡的重複字符:

[...new Set('ababbc')].join('')
// "abc"
3. Set內部認為NaN是相等的,所以只能添加一個;兩個對象也總是不相等的。
4. Array.from方法可以將 Set 結構轉為數組:
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
5. Set實例屬性:
// 構造函數,默認就是Set函數
Set.prototype.constructor
// 返回Set實例的成員總數
Set.prototype.size
6. Set實例方法:

四個操作方法:

  • ①add(value):添加某個值,返回 Set 結構本身。
  • ②delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
  • ③has(value):返回一個布爾值,表示該值是否為Set的成員。
  • ④clear():清除所有成員,沒有返回值。
let s = new Set();
s.add(1).add(2).add(2);   // 注意2被加入了兩次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false

四個遍歷方法:

  • ①keys():返回鍵名的遍歷器
  • ②values():返回鍵值的遍歷器
  • ③entries():返回鍵值對的遍歷器
  • ④forEach():使用回調函數遍歷每個成員

由於 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以keys方法和values方法的行為完全一致:

let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

Set 結構的實例默認可遍歷,它的默認遍歷器生成函數就是它的values方法:

Set.prototype[Symbol.iterator] === Set.prototype.values    // true
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue
//方便實現並集、交集、差集:
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 並集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

4.2 WeakSet

WeakSet 結構與 Set 類似,也是不重複的值的集合。但是,它與 Set 有兩個區別:

①WeakSet 的成員只能是對象,而不能是其他類型的值:

const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

②WeakSet 中的對象都是弱引用,即垃圾回收機制不考慮 WeakSet 對該對象的引用,且不可遍歷。

作為構造函數,WeakSet 可以接受一個數組或類似數組的對象作為參數。(實際上,任何具有 Iterable 接口的對象,都可以作為 WeakSet 的參數。)

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]} 是數組的成員成為WeakSet 實例對象的成員,而不是a,所以成員必須是對象
const b = [3, 4];
const ws = new WeakSet(b);   // 成員不是對象,所以會報錯
// Uncaught TypeError: Invalid value used in weak set(…)

三個方法:

  • WeakSet.prototype.add(value):向 WeakSet 實例添加一個新成員。
  • WeakSet.prototype.delete(value):清除 WeakSet 實例的指定成員。
  • WeakSet.prototype.has(value):返回一個布爾值,表示某個值是否在 WeakSet 實例之中。

沒有size屬性,無法遍歷

4.3 Map

Map類似於對象,也是鍵值對的集合,但是“鍵”的範圍不限於字符串,各種類型的值(包括對象)都可以當作鍵:

const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')  // 將對象o作為鍵
m           // {{…} => "content"}
m.get(o)    // "content"
m.has(o)    // true
m.delete(o) // true
m.has(o)    // false

可接受數組作為參數,數組成員是一個個表示鍵值對的數組:

const map = new Map([
['name', '張三'],
['title', 'Author']
]);
map       // {"name" => "張三", "title" => "Author"}
map.size  // 2
map.has('name')  // true
map.get('name')  // "張三"
map.has('title') // true
map.get('title') // "Author"

不僅僅是數組,任何具有 Iterator 接口、且每個成員都是一個雙元素的數組的數據結構都可以當作Map構造函數的參數。這就是說,Set和Map都可以用來生成新的 Map:

const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1
const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3

只有相同的簡單類型的鍵才會被視為同一個鍵,否則如數組、對象等作為鍵時,不是同一個引用時就算是同名,也是不同的鍵:

const map = new Map();
map.set(['a'], 555);   // 數組對象鍵
map.get(['a']) // undefined 不是同一個地址
// ----------------------------------------------------
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1)  // 111 引用地址不同,所以是不同的鍵
map.get(k2)  // 222

屬性方法:

  • ①size 屬性
  • ②set(key, value)
  • ③get(key)
  • ④has(key)
  • ⑤delete(key)
  • ⑥clear()

三個遍歷器生成函數和一個遍歷方法:

  • keys():返回鍵名的遍歷器。
  • values():返回鍵值的遍歷器。
  • entries():返回所有成員的遍歷器。
  • forEach():遍歷 Map 的所有成員。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同於使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
map[Symbol.iterator] === map.entries   // true

可用擴展運算符轉為數組:

const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

數組轉為Map:

new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
//   true => 7,
//   Object {foo: 3} => ['abc']
// }

4.4 WeakMap

①WeakMap只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名:

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

②WeakMap的鍵名所指向的對象,不計入垃圾回收機制。
不能遍歷且無size屬性

五、Reflect

類似於proxy,reflect也是為了操作對象,reflect對象上可以拿到語言內部的方法:

// 舊寫法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新寫法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
// 舊寫法
'assign' in Object // true
// 新寫法
Reflect.has(Object, 'assign') // true

1. Reflect.get(target, name, receiver)

Reflect.get方法查找並返回target對象的name屬性,如果沒有該屬性,則返回undefined:

var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
};
var myReceiverObject = {
foo: 4,
bar: 4,
};
Reflect.get(myObject, 'baz', myReceiverObject)  // 8

2. Reflect.set(target, name, value, receiver)

Reflect.set方法設置target對象的name屬性等於value:

var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3

3. Reflect.has(obj, name)

Reflect.has方法對應name in obj裡面的in運算符:

var myObject = {
foo: 1,
};
// 舊寫法
'foo' in myObject // true
// 新寫法
Reflect.has(myObject, 'foo') // true
// 如果第一個參數不是對象,Reflect.has和in運算符都會報錯。

4. Reflect.deleteProperty(obj, name)

Reflect.deleteProperty方法等同於delete obj[name],用於刪除對象的屬性:

const myObj = { foo: 'bar' };
// 舊寫法
delete myObj.foo;
// 新寫法
Reflect.deleteProperty(myObj, 'foo');
//該方法返回一個布爾值。如果刪除成功,或者被刪除的屬性不存在,返回true;刪除失敗,被刪除的屬性依然存在,返回false。

5. Reflect.construct(target, args)

Reflect.construct方法等同於new target(…args),這提供了一種不使用new,來調用構造函數的方法:

function Greeting(name) {
this.name = name;
}
// new 的寫法
const instance = new Greeting('張三');
// Reflect.construct 的寫法
const instance = Reflect.construct(Greeting, ['張三']);

6. Reflect.getPrototypeOf(obj)

Reflect.getPrototypeOf方法用於讀取對象的__proto__屬性,對應Object.getPrototypeOf(obj):

const myObj = new FancyThing();
// 舊寫法
Object.getPrototypeOf(myObj) === FancyThing.prototype;
// 新寫法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

Reflect.getPrototypeOf和Object.getPrototypeOf的一個區別是,如果參數不是對象,Object.getPrototypeOf會將這個參數轉為對象,然後再運行,而Reflect.getPrototypeOf會報錯。

Object.getPrototypeOf(1)  // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 報錯

7. Reflect.setPrototypeOf(obj, newProto)

Reflect.setPrototypeOf方法用於設置目標對象的原型(prototype),對應Object.setPrototypeOf(obj, newProto)方法。它返回一個布爾值,表示是否設置成功:

const myObj = {};
// 舊寫法
Object.setPrototypeOf(myObj, Array.prototype);
// 新寫法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0

8. Reflect.apply(func, thisArg, args)

Reflect.apply方法等同於Function.prototype.apply.call(func, thisArg, args),用於綁定this對象後執行給定函數:

const ages = [11, 33, 12, 54, 18, 96];
// 舊寫法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);
// 新寫法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

9. Reflect.defineProperty(target, propertyKey, attributes)

Reflect.defineProperty方法基本等同於Object.defineProperty,用來為對象定義屬性。未來,後者會被逐漸廢除,所以最好現在就開始使用Reflect.defineProperty代替它:

function MyDate() {
/*…*/
}
// 舊寫法
Object.defineProperty(MyDate, 'now', {
value: () => Date.now()
});
// 新寫法
Reflect.defineProperty(MyDate, 'now', {
value: () => Date.now()
});

10. Reflect.getOwnPropertyDescriptor(target, propertyKey)

Reflect.getOwnPropertyDescriptor基本等同於Object.getOwnPropertyDescriptor,用於得到指定屬性的描述對象,將來會替代掉後者:

var myObject = {};
Object.defineProperty(myObject, 'hidden', {
value: true,
enumerable: false,
});
// 舊寫法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');
// 新寫法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');

####11. Reflect.isExtensible(target)

Reflect.isExtensible方法對應Object.isExtensible,返回一個布爾值,表示當前對象是否可擴展:

const myObject = {};
// 舊寫法
Object.isExtensible(myObject) // true
// 新寫法
Reflect.isExtensible(myObject) // true

12. Reflect.preventExtensions(target)

Reflect.preventExtensions對應Object.preventExtensions方法,用於讓一個對象變為不可擴展。它返回一個布爾值,表示是否操作成功:

var myObject = {};
// 舊寫法
Object.preventExtensions(myObject)  // Object {}
// 新寫法
Reflect.preventExtensions(myObject) // true

13. Reflect.ownKeys(target)

Reflect.ownKeys方法用於返回對象的所有屬性,基本等同於Object.getOwnPropertyNames與Object.getOwnPropertySymbols之和:

var myObject = {
foo: 1,
bar: 2,
[Symbol.for('baz')]: 3,
[Symbol.for('bing')]: 4,
};
// 舊寫法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']
Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]
// 新寫法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

以上靜態方法,如果傳入的不是對象,基本都會報錯

最後

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

GitHub傳送門
博客園傳送門

ES6讀書筆記(二)

相關文章

HTTP解析

Vue源碼該如何入手?

理理Vue細節

ES6讀書筆記(三)