ES6讀書筆記(二)
前言
前段時間整理了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
就會綁定到這個函數的上下文中。即“穿透”箭頭函數。
例子裏的箭頭函數並沒有在哪個函數裏聲明,所以 this
會 fallback
到全局,全局的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讀書筆記(二)