前言
前段时间整理了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)]
以上静态方法,如果传入的不是对象,基本都会报错
最后
好了,本篇就到这里,主要都是摘抄常用的知识点和备注自己的理解,希望对你有所帮助,后面会持续更新,也感谢你能看到这里!