ES6
Reflect
Reflect
是 ES6 中新增的一个内置对象,它提供了一组与对象操作相关的方法,这些方法与 Object
上的方法类似,但有一些不同之处。它的作用是让一些 Object
上的操作变成函数式的操作,比如 delete
操作符变成了 Reflect.deleteProperty()
方法。Reflect
的方法都是静态方法,也就是说,不需要通过实例来调用。
Reflect
提供了一些常见的操作方法,如:Reflect.get()
,Reflect.set()
,Reflect.has()
,Reflect.deleteProperty()
,Reflect.construct()
等。这些方法与对应的 Object
方法的作用类似,但是它们具有一些不同的特性,比如返回值和执行方式等。
Reflect
还提供了一些其他方法,如:Reflect.apply()
,Reflect.defineProperty()
,Reflect.getOwnPropertyDescriptor()
,Reflect.getPrototypeOf()
等,它们也都与 Object
上的方法类似,但是具有一些不同的特性。
示例:
- 使用
Reflect.get()
获取对象的属性值:
const person = { name: 'Alice', age: 25 };
const age = Reflect.get(person, 'age');
console.log(age); // 25
- 使用
Reflect.set()
设置对象的属性值:
const person = { name: 'Alice', age: 25 };
Reflect.set(person, 'age', 26);
console.log(person); // { name: 'Alice', age: 26 }
- 使用
Reflect.has()
检查对象是否包含某个属性:
const person = { name: 'Alice', age: 25 };
const hasAge = Reflect.has(person, 'age');
const hasAg = Reflect.has(person, 'ag');
console.log(hasAge); // true
console.log(hasAg); // false
- 使用
Reflect.deleteProperty()
删除对象的属性:
const person = { name: 'Alice', age: 25 };
Reflect.deleteProperty(person, 'age');
console.log(person); // { name: 'Alice' }
- 使用
Reflect.construct()
创建一个对象实例:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const person = Reflect.construct(Person, ['Alice', 25]);
console.log(person); // Person { name: 'Alice', age: 25 }
- 使用
Reflect.apply()
调用一个函数并传递参数:
const sayHi = (name) => {
console.log(`Hi, ${name}`);
}
Reflect.apply(sayHi, null, ['Alice']); // Hi, Alice
// 带 this
function sayHi(){
console.log(`Hi, ${this.name}`);
}
Reflect.apply(sayHi, {name: 'Alice'}, []); // Hi, Alice
Reflect
的出现让一些操作变得更加简单和易于理解,并且它的方法可以更好地支持“函数式编程”和“元编程”,使得开发者能够更加灵活地编写 JavaScript 代码。
Symbol
ES6 引入了一种新的原始数据类型 Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol
特点:
Symbol
的值是唯一的,用来解决命名冲突的问题;Symbol
值不能与其他数据进行运算;Symbol
定义的对象属性不能使用for...in
循环遍历,但是可以使用Reflect.ownKeys
来获取对象的所有键名。
基本使用:
const q = Symbol();
console.log(q); // Symbol()
// 带标识
const q1 = Symbol('qiuxc');
const q2 = Symbol('qiuxc');
console.log(q1, q2); // Symbol(qiuxc) Symbol(qiuxc)
// 一样的标识进行比较也不相等
console.log(q1 === q2); // false
// Symbol.for() 创建,具有唯一性
const q3 = Symbol.for('qiuxc');
const q4 = Symbol.for('qiuxc');
console.log(q3, q4); // Symbol(qiuxc) Symbol(qiuxc)
// Symbol.for() 创建的一样的标识进行比较则相等
console.log(q3 === q4); // true
// Symbol.keyFor() 获取 Symbol 类型值对应的字符串键名
const sym1 = Symbol.for('foo');
// 使用 Symbol.for 创建的具有唯一性,可以获取到键名
console.log(Symbol.keyFor(sym1)); // 输出"foo"
// 使用 Symbol 创建的不具有唯一性,无法获取到键名
const sym2 = Symbol('bar');
console.log(Symbol.keyFor(sym2)); // 输出undefined
// 不能与其他数据进行运算
q1 + 1 // Error
q1 > 1 // Error
q1 + q1 // Error
q1 + 'abc' // Error
对象添加 Symbol
类型的属性:
// 安全地向对象中添加属性或方法,无需担心键冲突
const game = {
up(){
console.log('👆');
},
down(){
console.log('👇');
}
};
const methods = {
up: Symbol('up'),
down: Symbol('down')
};
game[methods.up] = function(){
console.log('向上');
}
game[methods.down] = function(){
console.log('向下');
}
console.log(game); // {up: ƒ, down: ƒ, Symbol(up): ƒ, Symbol(down): ƒ}
Symbol 内置方法
Symbol
自带了 11 个静态方法,如下表:
方法名 | 作用 |
---|---|
Symbol.hasInstance | 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。 |
Symbol.isConcatSpreÏadable | 对象的 Symbol.isConcatSpreadable 属性是一个布尔值,表示该对象用于 Array.prototype.concat() 时,是否可以展开。 |
Symbol.match | 当执行 str.match(myObject) 时,如果该方法存在,会调用它,返回该方法的返回值。 |
Symbol.replace | 当对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.search | 当对象被 str.search(myObject) 方法调用时,会返回该方法的返回值。 |
Symbol.split | 当对象被 str.split(myObject) 方法调用时,会返回该方法的返回值 |
Symbol.species | 用于指定创建派生对象的构造函数。 |
Symbol.toPrimitive | 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 |
Symbol.toStringTag | 在该对象上面调用 toString 方法时,返回该方法的返回值。 |
Symbol.iterator | 对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器。 |
Symbol.unscopables | 用于指示某些属性是否应该被 with 语句排除在外。 |
以下是一些使用 Symbol
内置方法的示例:
- 使用
Symbol.hasInstance
方法自定义对象的instanceof
行为:
/**
* 在下面的示例中,Person 类定义了 Symbol.hasInstance 静态方法,该方法用于自定义对象的 instanceof 行为。
* 在该示例中,如果对象是一个包含 name 和 age 属性的普通对象,则认为它是 Person 类的实例。
* 因此,person 对象使用 instanceof 运算符检查是否为 Person 类的实例时,返回 true。
*/
class Person {
static [Symbol.hasInstance](obj) {
if (typeof obj !== 'object') {
return false;
}
return 'name' in obj && 'age' in obj;
}
}
const person = { name: 'Alice', age: 25 };
console.log(person instanceof Person); // 输出 true
console.log(1 instanceof Person); // 输出 false
- 使用
Symbol.toPrimitive
方法将对象转换为基本类型值:
Symbol.toPrimitive方法的第一个参数hint指示将对象转换为哪种基本类型值。hint的值可以是以下三种之一:
- "default":在字符串和数值上下文中都可以使用。
- "number":在数值上下文中使用,如运算符、Math函数等。
- "string":在字符串上下文中使用,如字符串连接运算符(+)等。
- 当 hint 为 "default" 时,JavaScript 引擎会根据上下文自动选择将对象转换为字符串或数值类型。
- 当 hint 为 "number" 时,JavaScript 引擎会优先将对象转换为数值类型,如果无法转换,则尝试将其转换为字符串类型。
- 当 hint 为 "string" 时,JavaScript 引擎会优先将对象转换为字符串类型,如果无法转换,则尝试将其转换为数值类型。
const obj = {
valueOf() {
return 42;
},
toString() {
return 'forty-two';
},
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 42;
}
if (hint === 'string') {
return 'forty-two';
}
return 'default';
}
};
console.log(obj + 1); // 输出"default1"(因为hint为"default"时,JavaScript引擎会将对象转换为字符串类型)
console.log(obj * 2); // 输出84(因为hint为"number"时,JavaScript引擎会将对象转换为数值类型)
console.log(String(obj)); // 输出forty-two(因为hint为"string"时,JavaScript引擎会将对象转换为字符串类型)
- 使用
Symbol.iterator
方法迭代一个自定义对象:
/**
* 在下面的示例中,myObj 对象定义了一个自定义的迭代器,该迭代器使用 Symbol.iterator 方法返回一个迭代器对象,
* 该迭代器对象实现了 next() 方法,用于迭代 myObj 对象的 data 属性中的元素。
* 通过使用 for...of 语句,可以使用自定义的迭代器来迭代 myObj 对象中的元素。
*/
const myObj = {
data: [1, 2, 3],
[Symbol.iterator]: function() {
let index = 0;
const data = this.data;
return {
next: function() {
if (index >= data.length) {
return { done: true };
}
return { value: data[index++], done: false };
}
};
}
};
for (const item of myObj) {
console.log(item);
}
迭代器 Iterator
迭代器(Iterator)是对象的一个属性(Symbol.Iterator
),为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历语句
for...of
循环,Iterator 接口主要供for...of
消费。 - 原生具备 Iterator 接口的数据结构如下:
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
- 工作原理
- 创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上,就是一个指针对象;
- 第一次调用指针对象的
next
方法,可以将指针指向数据结构的第一个成员; - 接下来不断调用指针对象的
next
方法,指针一直向后移动,直到它指向最后一个成员; - 每次调用
next
方法返回一个包含value
和done
两个属性的对象。其中,value
属性是当前成员的值,done
属性是一个布尔值,表示遍历是否结束。
注:需要自定义遍历数据的时候,可以使用
Symbol.iterator
方法。
自定义迭代器:
const obj = {
data: [1, 2, 3],
[Symbol.iterator]: function() {
let index = 0;
const data = this.data;
return {
next: function() {
if (index >= data.length) {
return { done: true };
}
return { value: data[index++], done: false };
}
};
}
};
for (const v of obj) {
console.log(v);
};
// 1
// 2
// 3
手动调用对象的 Symbol.iterator
方法,可以返回一个迭代器对象,该迭代器对象实现了 next()
方法,用于迭代对象中的元素:
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
生成器 Generator
生成器(Generator)是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
生成器(Generator)是一个特殊的函数,可以用来控制函数的执行过程,手动暂停和恢复代码执行。生成器函数和普通函数不同,普通函数一旦开始执行,无法暂停,而生成器函数可以执行一段暂停后再继续执行的代码。
语法:
// `*` 号在 `function` 关键字与函数名之间,可以偏左也可以偏右,表示该函数是一个生成器函数。
function* generator() {
yield xxx;
// ...
}
生成器函数的执行结果是一个生成器对象,该对象符合迭代器协议:
function * gen(){
console.log('hello generator');
}
const g = gen();
console.log(g); // Object [Generator] {}
console.log(g.next());
// hello generator
// { value: undefined, done: true }
可以使用 next
或者 for...of
语句迭代生成器对象中的元素:
function* generator() {
yield 1;
yield 2;
yield 3;
}
const iterator = generator();
// 使用 next() 方法迭代生成器对象中的元素
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// 使用 for...of 语句迭代生成器对象中的元素
for (const v of generator()) {
console.log(v);
}
// 1
// 2
// 3
生成器函数的参数传递:
next
方法可以带一个参数,该参数会被当作上一个yield
语句的返回值。
function* gen(arg){
const r1 = yield 111;
console.log(r1);
const r2 = yield 222;
console.log(r2);
const r3 = yield 333;
console.log(r3);
}
const iterator = gen('AAA');
console.log(iterator.next()); // { value: 111, done: false }
console.log(iterator.next('BBB')); // BBB { value: 222, done: false }
console.log(iterator.next('CCC')); // CCC { value: 333, done: false }
console.log(iterator.next('DDD')); // DDD { value: undefined, done: true }
解决异步编程问题:
function* gen() {
const r1 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('AAA');
}, 1000);
});
console.log(r1);
const r2 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('BBB');
}, 1000);
});
console.log(r2);
const r3 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('CCC');
}, 1000);
});
console.log(r3);
}
const iterator = gen();
iterator.next().value.then((data) => {
iterator.next(data).value.then((data) => {
iterator.next(data).value.then((data) => {
iterator.next(data);
});
});
});
// AAA (1s)
// BBB (2s)
// CCC (3s)
解决回调地狱问题:
function* gen() {
const r1 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('AAA');
}, 1000);
});
console.log(r1);
const r2 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('BBB');
}, 1000);
});
console.log(r2);
const r3 = yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve('CCC');
}, 1000);
});
console.log(r3);
}
const iterator = gen();
function next(data) {
const { value, done } = iterator.next(data);
if (done) {
return;
}
value.then(next);
}
next();
// AAA (1s)
// BBB (2s)
// CCC (3s)
异步接口调用示例:
const getUser = () => setTimeout(() => {
const data = '用户数据';
iterator.next(data);
}, 1000);
const getOrder = () => setTimeout(() => {
const data = '订单数据';
iterator.next(data);
}, 1000);
const getGoods = () => setTimeout(() => {
const data = '商品数据';
iterator.next(data);
}, 1000);
function * gen() {
const userData = yield getUser();
console.log(userData);
const orderData = yield getOrder();
console.log(orderData);
const goodsData = yield getGoods();
console.log(goodsData);
}
const iterator = gen();
iterator.next();
// 用户数据 (1s)
// 订单数据 (2s)
// 商品数据 (3s)
集合 Set
ES6 提供了一种新的数据结构 Set(集合),它类似于数组,但是成员的值都是唯一的,没有重复的值。
集合实现了 iterator
接口,可以使用 for...of
语句迭代集合中的元素。
集合的属性和方法:
size
属性:返回集合中元素的个数。add
方法:向集合中添加一个元素,返回当前集合。delete
方法:从集合中删除一个元素,返回boolean
值,表示是否删除成功。has
方法:判断集合中是否包含某个元素,返回boolean
值。clear
方法:清空集合中的所有元素。
// 创建一个空集合
const set = new Set();
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
set.add(5);
console.log(set); // Set { 1, 2, 3, 4, 5 }
// 打印集合的元素个数
console.log(set.size); // 5
// 判断集合中是否包含某个元素
console.log(set.has(3)); // true
console.log(set.has(6)); // false
// 删除集合中的某个元素
set.delete(5);
console.log(set); // Set { 1, 2, 3, 4 }
// 遍历集合
for (const v of set) {
console.log(v);
}
// 1
// 2
// 3
// 4
// 清空集合
set.clear();
console.log(set); // Set {}
示例:
- 数组去重
const arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1];
const result = [...new Set(arr)];
console.log(result); // [ 1, 2, 3, 4, 5 ]
- 判断数组中是否包含重复元素
const arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1];
const result = arr.length === [...new Set(arr)].length;
console.log(result); // false
- 求两个数组的交集
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];
const result = [...new Set(arr1)].filter((item) => new Set(arr2).has(item));
console.log(result); // [ 3, 4, 5 ]
- 求两个数组的并集
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];
const result = [...new Set([...arr1, ...arr2])];
console.log(result); // [ 1, 2, 3, 4, 5, 6, 7 ]
- 求两个数组的差集
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [3, 4, 5, 6, 7];
const result = [...new Set(arr1)].filter((item) => !new Set(arr2).has(item));
console.log(result); // [ 1, 2 ]
字典 Map
ES6 提供了一种新的数据结构 Map(字典),它类似于对象,但是键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Map 也提供了 iterator
接口,可以使用 for...of
语句迭代 Map 中的元素。
Map 的属性和方法:
size
属性:返回 Map 中键值对的个数。set
方法:向 Map 中添加一个键值对,返回当前 Map。get
方法:获取 Map 中指定键的值,如果不存在则返回undefined
。has
方法:判断 Map 中是否包含指定的键,返回boolean
值。delete
方法:从 Map 中删除指定的键值对,返回boolean
值,表示是否删除成功。clear
方法:清空 Map 中的所有键值对。
// 创建一个空 Map
const map = new Map()
map.set('name', 'Qiuxc')
map.set('sayHi', function(){
console.log("hi, I'm Qiuxc");
})
map.set({a: 1}, {b: 2})
console.log(map); // Map { 'name' => 'Qiuxc', 'sayHi' => [Function (anonymous)], { a: 1 } => { b: 2 } }
// 获取 Map 的大小
console.log(map.size); // 3
// 遍历 Map
for (const v of map) {
console.log(v);
}
// [ 'name', 'Qiuxc' ]
// [ 'sayHi', [Function (anonymous)] ]
// [ { a: 1 }, { b: 2 } ]
// 判断 Map 中是否包含指定的键
console.log(map.has('name')); // true
console.log(map.has('nam')); // false
// 删除 Map 中的指定键值对
map.delete('name')
console.log(map); // Map { 'sayHi' => [Function (anonymous)], { a: 1 } => { b: 2 } }
// 清空 Map
map.clear()
console.log(map); // Map {}