Skip to content
大纲

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 上的方法类似,但是具有一些不同的特性。

示例:

  1. 使用 Reflect.get() 获取对象的属性值:
js
const person = { name: 'Alice', age: 25 };
const age = Reflect.get(person, 'age');
console.log(age); // 25
  1. 使用 Reflect.set() 设置对象的属性值:
js
const person = { name: 'Alice', age: 25 };
Reflect.set(person, 'age', 26);
console.log(person); // { name: 'Alice', age: 26 }
  1. 使用 Reflect.has() 检查对象是否包含某个属性:
js
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
  1. 使用 Reflect.deleteProperty() 删除对象的属性:
js
const person = { name: 'Alice', age: 25 };
Reflect.deleteProperty(person, 'age');
console.log(person); // { name: 'Alice' }
  1. 使用 Reflect.construct() 创建一个对象实例:
js
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 }
  1. 使用 Reflect.apply() 调用一个函数并传递参数:
js
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 特点:

  1. Symbol 的值是唯一的,用来解决命名冲突的问题;
  2. Symbol 值不能与其他数据进行运算;
  3. Symbol 定义的对象属性不能使用 for...in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名。

基本使用:

js
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 类型的属性:

js
// 安全地向对象中添加属性或方法,无需担心键冲突
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 内置方法的示例:

  1. 使用 Symbol.hasInstance 方法自定义对象的 instanceof 行为:
js
/**
 * 在下面的示例中,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
  1. 使用 Symbol.toPrimitive 方法将对象转换为基本类型值:

Symbol.toPrimitive方法的第一个参数hint指示将对象转换为哪种基本类型值。hint的值可以是以下三种之一:

  1. "default":在字符串和数值上下文中都可以使用。
  2. "number":在数值上下文中使用,如运算符、Math函数等。
  3. "string":在字符串上下文中使用,如字符串连接运算符(+)等。
  • 当 hint 为 "default" 时,JavaScript 引擎会根据上下文自动选择将对象转换为字符串或数值类型。
  • 当 hint 为 "number" 时,JavaScript 引擎会优先将对象转换为数值类型,如果无法转换,则尝试将其转换为字符串类型。
  • 当 hint 为 "string" 时,JavaScript 引擎会优先将对象转换为字符串类型,如果无法转换,则尝试将其转换为数值类型。
js
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引擎会将对象转换为字符串类型)
  1. 使用 Symbol.iterator 方法迭代一个自定义对象:
js
/**
 * 在下面的示例中,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 方法返回一个包含 valuedone 两个属性的对象。其中,value 属性是当前成员的值,done 属性是一个布尔值,表示遍历是否结束。

注:需要自定义遍历数据的时候,可以使用 Symbol.iterator 方法。

自定义迭代器:

js
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() 方法,用于迭代对象中的元素:

js
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)是一个特殊的函数,可以用来控制函数的执行过程,手动暂停和恢复代码执行。生成器函数和普通函数不同,普通函数一旦开始执行,无法暂停,而生成器函数可以执行一段暂停后再继续执行的代码。

语法:

js
// `*` 号在 `function` 关键字与函数名之间,可以偏左也可以偏右,表示该函数是一个生成器函数。
function* generator() {
  yield xxx;
  // ...
}

生成器函数的执行结果是一个生成器对象,该对象符合迭代器协议:

js
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 语句迭代生成器对象中的元素:

js
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 语句的返回值。

js
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 }

解决异步编程问题:

js
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)

解决回调地狱问题:

js
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)

异步接口调用示例:

js
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 方法:清空集合中的所有元素。
js
// 创建一个空集合
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 {}

示例:

  1. 数组去重
js
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 ]
  1. 判断数组中是否包含重复元素
js
const arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1];
const result = arr.length === [...new Set(arr)].length;
console.log(result); // false
  1. 求两个数组的交集
js
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 ]
  1. 求两个数组的并集
js
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 ]
  1. 求两个数组的差集
js
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 中的所有键值对。
js
// 创建一个空 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 {}