谈谈你不懂的ES6(你以为你懂了其实你不懂)

9 阅读12分钟

ES6简述

ES6(ECMAScript 2015)是JavaScript语言的一个重要版本,它引入了许多新的特性和语法,旨在改善代码的可读性和可维护性。虽然用了多年了有一些知识点还是比较隐匿的,并且容易忽略掉。。但是又比较实用。。。es6 ruanyifengMDN web docs

Reflect

Reflect 是 ES6 引入的一个内置对象,提供了多种方法用于操作对象。它的设计目的是为了简化某些操作并提供更一致的行为。Reflect API 参考

  <script>
    // Reflect
    const obj = {name: 'bob', age: 25};
    // obj.name = 'alice';
    // 设置属性
    Reflect.set(obj, 'name', 'alice');
    console.log(obj); // {name: 'alice', age: 25} 
    // 获取属性
    console.log(Reflect.get(obj, 'name')); // alice
    // 遍历对象
    const keys = Reflect.ownKeys(obj);
    console.log(keys); // ['name', 'age']
    // 调用方法
    const result = Reflect.apply(Math.pow, null, [2, 3]);
    console.log(result); // 8
    // 实例属性
    const person = {
      name: 'alice',
      sayName() {
        console.log(this.name);
      }
    };
    const sayName = person.sayName;
    sayName(); // alice
    // 绑定方法
    const boundSayName = sayName.bind(person);
    boundSayName(); // alice
    // 实例属性
    const person2 = {
      name: 'alice',
      sayName() {
        console.log(this.name);
      }
    };
    const sayName2 = person2.sayName;
    sayName2(); // alice
    // 绑定方法
    const boundSayName2 = sayName2.bind(person2);
    boundSayName2(); // alice 
    // 删除属性
    Reflect.deleteProperty(obj, 'age');
    console.log(obj); // {name: 'alice'}
    // 判断对象中是否有属性
    console.log(Reflect.has(obj, 'name')); // true
    console.log(Reflect.has(obj, 'age')); // false
    // 构造函数
    const Person = function(name, age) {
      this.name = name;
      this.age = age;
    };
    const person3 = new Person('bob', 25);
    console.log(person3); // {name: 'bob', age: 25}
    // 继承
    const person4 = Reflect.construct(Person, ['bob', 25]);
    console.log(person4); // {name: 'bob', age: 25} 
    // 劫持属性
    const handler = {
      get(target, key) {
        if (key === 'name') {
          return 'alice';
        }
        return target[key];
      }
    };
    const proxy = new Proxy(obj, handler);
    console.log(proxy.name); // alice
    // Reflect 劫持属性
    const proxy2 = new Proxy(obj, {
      get(target, key) {
        if (key === 'name') {
          return 'alice';
        }
        return Reflect.get(target, key);
      }
    });
    console.log(proxy2.name); // alice
  </script>

class & Proxy

利用ProxyReflect特性,使得构造函数的使用更加灵活和简洁

  // 简化构造函数 使用 new isProxy
   class Person {
     constructor(name, age) {
       this.name = name;
       this.age = age;
     }
   }
   // 简化
   class Student {

   }
   // 函数简化构造函数的调用
   function createProxy(className, ...propsNames){
    return new Proxy(className, {
      construct(target, args) {
        const obj = Reflect.construct(target, args);
        propsNames.forEach((propName, index) => {
          obj[propName] = args[index];
        });
        return obj;
      }
    })
  }
  const PersonProxy = createProxy(Person, 'name', 'age');
   const student = new PersonProxy('bob', 24);
   console.log(student); // {name: "bob", age: 24}

迭代器(Iterator) 和 生成器(Generator)

1. 迭代器(Iterator)

定义: 迭代器是一种对象,它实现了迭代协议(iteration protocol),即具有一个next()方法,该方法返回一个包含valuedone两个属性的对象。

  • value:当前迭代的值。
  • done:一个布尔值,表示迭代是否结束。

实现迭代器: 要创建一个迭代器,你需要定义一个具有next()方法的对象。例如:

const myIterator = {
  current: 0,
  last: 5,
  next() {
    if (this.current < this.last) {
      return { value: this.current++, done: false };
    } else {
      return { done: true };
    }
  }
};

console.log(myIterator.next()); // { value: 0, done: false }
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: 4, done: false }
console.log(myIterator.next()); // { done: true }

使用的场景: 迭代器常用于需要惰性求值的场景,例如遍历数据结构(如数组、集合)等。通过迭代器,可以逐步读取数据,而不需要一次性加载全部数据。

2. 生成器(Generator)

定义: 生成器是一种特殊的函数,使用function*语法定义,可以通过yield语句来暂停和恢复函数的执行。调用生成器函数会返回一个迭代器。

特性

  • 生成器可以多次调用,内部状态会被保存,可以随时恢复执行。
  • 每次遇到yield时,函数会返回一个值,并且保留执行状态,下次调用next()时从yield处继续执行。

实现生成器: 可以通过如下方式定义并使用生成器:

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

3. 迭代器与生成器的联系与区别

  • 联系

    • 生成器本质上就是一个迭代器,它实现了迭代器协议。
    • 所有生成器返回的都是迭代器对象。
  • 区别

    • 迭代器是一个普通对象,有next()方法;而生成器函数则用function*定义,可以通过yield控制执行流。
    • 使用生成器可以更方便地创建复杂的迭代逻辑,而不必手动维护状态。

4. 异步生成器(Async Generator)

定义: 异步生成器是一种特殊的生成器,可以使用async function*语法定义,它允许使用await关键字来处理异步操作。

特点

  • 处理异步操作时,可以逐步返回值。
  • 使用await可以等待异步操作完成后再执行生成器的下一步。

示例: 下面是一个简单的异步生成器示例:

async function* asyncGen() {
  const values = [1, 2, 3];
  for (const value of values) {
    // 模拟异步操作
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield value;
  }
}

const ag = asyncGen();

5. 异步迭代器(Async Iterator)

定义: 异步迭代器是一种对象,它实现了异步迭代协议,包含一个next()方法。该方法返回一个Promise,解析后包含valuedone两个属性。

使用方法: 异步迭代器常用于遍历需要异步处理的数据源,如网络请求、数据库访问等。

示例: 下面是如何使用异步迭代器的示例:

let asyncArr = [1, 2, 3, 4, 5];

// 创建异步迭代器
let asyncIterator = asyncArr[Symbol.asyncIterator]();

// 使用 async/await 逐步获取值
(async () => {
  let result = await asyncIterator.next();
  while (!result.done) {
    console.log(result.value); // 输出当前值
    result = await asyncIterator.next(); // 获取下一个值
  }
})();

生成器(Generator)异步任务控制

生成器(Generator)在JavaScript中不仅可以用于同步任务的控制,还可以结合yieldPromise来实现异步任务的控制。通过这种方式,可以更优雅地管理复杂的异步流程,避免“回调地狱”或过多的Promise链式调用。

生成器与异步任务的基本原理

生成器通过yield关键字可以暂停函数的执行,并在需要时恢复执行。结合Promise,可以在yield处等待异步任务完成,然后再继续执行后续代码。

核心思想

  • 使用yield暂停生成器函数的执行。
  • 使用Promise处理异步任务。
  • 通过生成器的next()方法恢复执行,并传递异步任务的结果。

实现异步任务控制的步骤

步骤 1:定义一个生成器函数

生成器函数使用function*定义,并在需要等待异步任务的地方使用yield

步骤 2:编写一个任务运行器(Runner)

任务运行器的作用是自动执行生成器函数,处理yield返回的Promise,并将结果传递回生成器。

步骤 3:使用生成器控制异步任务

通过生成器函数和任务运行器,可以清晰地表达异步任务的执行顺序。

示例代码

以下是一个完整的示例,展示如何使用生成器控制异步任务:

// 模拟异步任务
function asyncTask(value, delay) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`Task ${value} completed after ${delay}ms`);
      resolve(value);
    }, delay);
  });
}

// 生成器函数,定义异步任务的执行顺序
function* asyncGenerator() {
  const result1 = yield asyncTask(1, 1000); // 等待第一个任务完成
  console.log(`Result of task 1: ${result1}`);

  const result2 = yield asyncTask(2, 500); // 等待第二个任务完成
  console.log(`Result of task 2: ${result2}`);

  const result3 = yield asyncTask(3, 800); // 等待第三个任务完成
  console.log(`Result of task 3: ${result3}`);

  return "All tasks completed!";
}

// 任务运行器
function runGenerator(generator) {
  const iterator = generator(); // 获取生成器迭代器

  function handle(iteration) {
    if (iteration.done) {
      return Promise.resolve(iteration.value); // 如果生成器结束,返回最终结果
    }

    return Promise.resolve(iteration.value) // 等待 yield 返回的 Promise 完成
      .then((result) => {
        return handle(iterator.next(result)); // 将结果传递回生成器,继续执行
      })
      .catch((error) => {
        return handle(iterator.throw(error)); // 处理错误
      });
  }

  return handle(iterator.next()); // 启动生成器
}

// 运行生成器
runGenerator(asyncGenerator)
  .then((finalResult) => {
    console.log(finalResult); // 输出最终结果
  })
  .catch((error) => {
    console.error("Error:", error);
  });

输出结果

运行上述代码后,输出如下:

Task 1 completed after 1000ms
Result of task 1: 1
Task 2 completed after 500ms
Result of task 2: 2
Task 3 completed after 800ms
Result of task 3: 3
All tasks completed!

实际应用场景

  • 分步加载数据:例如,先加载用户信息,再加载用户的订单列表,最后加载订单详情。
  • 任务队列:按顺序执行一系列异步任务。
  • 复杂流程控制:例如,游戏中的任务流程或动画序列。

SetMap

在ES6中,引入了SetMap这两种新的数据结构,它们在数据存储和操作上各具特点。

1. 定义

  • Set

    • Set是一种集合,表示一个不重复的值的无序集合。它允许存储任何类型的唯一值,包括原始值和对象引用。
  • Map

    • Map是一种键值对集合,允许使用对象作为键。与普通对象相比,Map的键可以是任何类型的值。

2. 特性对比

特性SetMap
存储结构无序集合,存储唯一值有序集合,存储键值对
键/值类型只有值,没有键键和值都可以是任何类型
允许重复不允许重复值键不允许重复,值可以重复
插入顺序保留插入的顺序保留插入顺序
大小可以通过 size 属性获取集合的大小可以通过 size 属性获取映射的大小
常用方法 add(value)has(value)delete(value)clear()  set(key, value)get(key)has(key)delete(key)clear() 
迭代方法 forEachfor...of  forEachfor...of 
性能对于某些操作,性能较好(如去重、查找)键的查找性能较优

3. 常用操作

3.1 Set 的操作示例

const mySet = new Set();

// 添加元素
mySet.add(1);
mySet.add(2);
mySet.add(2); // 重复的值不会被添加
mySet.add('hello');
mySet.add({ a: 1 }); // 可以添加对象

console.log(mySet.size); // 4

// 检查元素是否存在
console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false

// 删除元素
mySet.delete(2);
console.log(mySet.size); // 3

// 遍历
mySet.forEach(value => {
  console.log(value);
});

// 可以使用 for...of 进行遍历
for (let item of mySet) {
  console.log(item);
}

3.2 Map 的操作示例

const myMap = new Map();

// 添加键值对
myMap.set('name', 'Alice');
myMap.set('age', 30);
myMap.set(1, 'One'); // 可以使用数字作为键
myMap.set(true, 'Boolean'); // 可以使用布尔值作为键

console.log(myMap.size); // 4

// 获取值
console.log(myMap.get('name')); // Alice

// 检查键是否存在
console.log(myMap.has('age')); // true
console.log(myMap.has(2)); // false

// 删除键值对
myMap.delete(1);
console.log(myMap.size); // 3

// 遍历
myMap.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});

// 使用 for...of 遍历
for (let [key, value] of myMap) {
  console.log(`${key}: ${value}`);
}

4. 使用场景

  • Set

    • 用于存储唯一值的场景,例如去重操作。
    • 可用于跟踪某些元素是否存在(如用户ID、标签等)。
  • Map

    • 存储关联数据的场景,例如构建对象属性(对象的键值对)。
    • 应用于需要快速查找的情境,例如缓存或索引。

WeakSetWeakMap

WeakSetWeakMap是ES6中引入的两个新的数据结构,它们与SetMap的主要区别在于对存储的对象引用的处理。二者通过弱引用(weak reference)来避免对对象的强引用,从而在某些情况下帮助实现垃圾回收。

1. 定义

  • WeakSet:

    • WeakSet是一个只存储对象的集合,其中的对象是弱引用。换句话说,如果没有其他强引用指向WeakSet中的对象,垃圾回收机制可以回收这些对象。
  • WeakMap:

    • WeakMap是一个键值对集合,其中的键是弱引用。它的键只能是对象,并且如果没有其他强引用指向这些键,则它们会被垃圾回收。

2. 特性对比

特性WeakSetWeakMap
存储内容只能存储对象,不能存储原始值键是对象,值可以是任何类型
引用类型弱引用,只有对象引用弱引用,只能是对象作为键
垃圾回收如果集合中没有其他引用,存储的对象可以被回收如果没有其他引用,存储的键值对可以被回收
可用方法 add(value)has(value)delete(value)  set(key, value)get(key)has(key)delete(key) 
遍历不能被遍历(没有遍历方法)不能被遍历(没有遍历方法)
大小不支持获取大小(没有 size 属性)不支持获取大小(没有 size 属性)
性能通常在垃圾回收时更加高效通常在垃圾回收时更加高效

3. 常用操作示例

3.1 WeakSet 的操作示例

const weakSet = new WeakSet();

// 创建对象
let obj1 = { name: "Alice" };
let obj2 = { name: "Bob" };

// 添加对象
weakSet.add(obj1);
weakSet.add(obj2);

// 检查对象是否存在
console.log(weakSet.has(obj1)); // true
console.log(weakSet.has({ name: "Charlie" })); // false

// 删除对象
weakSet.delete(obj1);
console.log(weakSet.has(obj1)); // false

// 注意:不能获取 WeakSet 的大小或进行遍历

3.2 WeakMap 的操作示例

const weakMap = new WeakMap();

// 创建对象
let key1 = {};
let key2 = {};

// 将对象作为键,存储值
weakMap.set(key1, "Value for key1");
weakMap.set(key2, "Value for key2");

// 获取值
console.log(weakMap.get(key1)); // Value for key1
console.log(weakMap.get(key2)); // Value for key2

// 检查键是否存在
console.log(weakMap.has(key1)); // true
console.log(weakMap.has({})); // false

// 删除键值对
weakMap.delete(key1);
console.log(weakMap.has(key1)); // false

// 注意:不能获取 WeakMap 的大小或进行遍历

4. 使用场景

  • WeakSet

    • 可用于存储需要在垃圾回收时被自动清理的对象,使得在对象生命周期内的引用不会妨碍垃圾回收。
    • 常见场景包括跟踪对象的元数据,或动态创建某些对象的状态。
  • WeakMap

    • 适合用于将附加信息关联到对象,且不希望阻止这些对象的垃圾回收。
    • 常见场景包括实现私有属性、缓存数据、或以对象作为键的映射。

属性描述符 (js自带有利于理解es6关联内容)

在JavaScript中,属性描述符是用于描述对象属性的一组特性,它们包括了属性的行为及其特性。通过属性描述符,我们可以控制对象属性的可写性、可枚举性和可配置性。

1. 属性描述符的类型

JavaScript中有两种类型的属性描述符:

  • 数据描述符(Data Descriptor):包含值的属性。
  • 访问器描述符(Accessor Descriptor):使用 getter 和 setter 方法来控制属性的访问。

2. 数据描述符

数据描述符是具有以下特性的对象:

  • value: 属性的值。
  • writable: 一个布尔值,表示该属性是否可以被修改。
  • enumerable: 一个布尔值,表示是否可以通过for...inObject.keys()等方法枚举该属性。
  • configurable: 一个布尔值,表示该属性是否可以被删除,或者如果该属性是数据描述符,是否可以改变它的特性(如writable)。

示例:数据描述符

const obj = {};

// 定义一个数据描述符
Object.defineProperty(obj, 'name', {
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
});

console.log(obj.name); // Alice

// 修改值
obj.name = 'Bob';
console.log(obj.name); // Bob

// 删除属性
delete obj.name;
console.log(obj.name); // undefined

3. 访问器描述符

访问器描述符是具有以下特性的对象:

  • get: 一个函数,当读取属性时调用。
  • set: 一个函数,当写入属性时调用。
  • enumerable: 一个布尔值,表示是否可以枚举该属性。
  • configurable: 一个布尔值,表示该属性是否可以被删除,或是否可以改变描述符特性。

示例:访问器描述符

const obj = {
  firstName: 'John',
  lastName: 'Doe',
};

// 定义访问器描述符
Object.defineProperty(obj, 'fullName', {
  get: function() {
    return `${this.firstName} ${this.lastName}`;
  },
  set: function(value) {
    const parts = value.split(' ');
    this.firstName = parts[0];
    this.lastName = parts[1];
  },
  enumerable: true,
  configurable: true
});

// 读 get()
console.log(obj.fullName); // John Doe

// 修改 fullName (写)set(xx)
obj.fullName = 'Jane Smith';
console.log(obj.firstName); // Jane
console.log(obj.lastName); // Smith

4. 查询属性描述符

可以使用 Object.getOwnPropertyDescriptor 方法来获取对象属性的描述符。

示例:

const obj = {};

Object.defineProperty(obj, 'name', {
  value: 'Alice',
  writable: false,
  enumerable: true,
  configurable: false
});

// 获取属性描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// 输出:
// {
//   value: 'Alice',
//   writable: false,
//   enumerable: true,
//   configurable: false
// }

varletconst

ES6 之前 var声明变量造成的问题

  • 允许重复的变量声明,导致数据被覆盖
  • 变量提升(例如闭包)
  • 全局变量挂在到到全局对象:全局对象成员被污染 (例如window.name

1. 函数作用域(Function Scope)

使用var声明的变量具有函数作用域,这意味着变量的作用域限于其所在的函数内部。如果在函数外部使用var声明变量,则该变量会成为全局变量,但这可能会导致意外的命名冲突。

示例:

function example() {
  var x = 10; // x 在这个函数内部可用
}

example();
console.log(x); // ReferenceError: x is not defined

如果在函数外部声明var,则可能会引发意外问题:

var globalVar = 'I am global';

function scopeExample() {
  var localVar = 'I am local';
}

console.log(globalVar); // I am global
console.log(localVar); // ReferenceError: localVar is not defined

2. 变量提升(Hoisting)

使用var声明的变量会被提升到其作用域的顶部。这意味着在声明之前可以引用变量,但它的值为undefined,这可能导致意想不到的错误。

示例:

console.log(a); // undefined
var a = 5;
console.log(a); // 5

因为var a会被提升,所以上述代码输出undefined。许多新手可能会认为在console.log(a)a已被赋值,但实际上只有声明被提升。

3. 重复声明

在同一作用域内,使用var可以重复声明同一变量,可能会导致意外的重写,增加了代码的复杂性及出错的风险。

示例:

var b = 10;
var b = 20; // 没有报错
console.log(b); // 20

重复声明会覆盖原有值,这样可能导致逻辑错误。

4. 闭包中的问题

当使用var在循环中创建函数时,所有函数共享同一个变量的引用,因为var是函数作用域的,因此会导致在异步回调中引用到最后一次循环的值。

示例:

var funcs = [];
for (var i = 0; i < 5; i++) {
  funcs.push(function() {
    console.log(i);
  });
}

funcs.forEach(func => func()); // 输出 5,五次

在这里,所有的函数都引用了同一个i,因此在所有回调中输出的是i的最终值(5)。

5. 解决方案

为了解决var带来的问题,JavaScript在ES6中引入了letconst这两个关键字,它们具备以下优势:

  • 块级作用域letconst具有块级作用域,只在其所在的代码块内有效。
  • 不提升(Hoisting) :虽然它们也具有提升特性,但不会初始化为undefined,引用前必须声明。
  • 禁止重复声明:在相同作用域内,使用letconst声明变量会抛出错误。

示例:

for (let j = 0; j < 5; j++) {
  setTimeout(() => {
    console.log(j); // 输出 0 1 2 3 4
  }, 100);
}

var const let 对比和区别

特性 var  let  const 
作用域函数作用域;如果在函数外,则全局作用域块级作用域块级作用域
变量提升提升;未初始化为undefined 提升;在定义前不可访问提升;在定义前不可访问
可变性可重新赋值可重新赋值不可重新赋值(声明常量)
重复声明允许不允许不允许

箭头函数

历史问题 this指向(当前执行上下文的一个特殊对象)

  • 通过对象调用函数,this指向对象
  • 直接调用函数,this指向全局对象 window
  • 通过new调用函数,this指向新创建的对象
  • 通过apply、call、bind调用函数 this指向指定的数据
  • 如果是dom事件函数,this指向事件源

普通函数与箭头函数的区别

this 绑定的差异

  • 普通函数: this在普通函数中是动态绑定的:它依赖于函数调用的上下文。可以通过不同的方式(如callapplybind)改变this的值。

    function greet() {
      console.log(this.name);
    }
    
    const person = { name: 'Alice' };
    greet.call(person); // 输出 'Alice'
    

箭头函数: 箭头函数没有自己的this,它从定义时的外部作用域中继承this的值。这就意味着在箭头函数中,this的值是固定的,不能被动态改变。

const person = {
  name: 'Alice',
  greet: function() {
    const innerGreet = () => {
      console.log(this.name);
    };
    innerGreet();
  }
};
person.greet(); // 输出 'Alice'

其他差异

  • 构造函数: 普通函数可以作为构造函数,但是箭头函数不能。

    function Person(name) {
      this.name = name;
    }
    const alice = new Person('Alice'); // 可以
    
    const ArrowPerson = (name) => {
      this.name = name;
    };
    // const bob = new ArrowPerson('Bob'); // TypeError: ArrowPerson is not a constructor
    
  • arguments 对象: 普通函数有自己的 arguments 对象,但箭头函数没有。

function showArgs() {
  console.log(arguments);
}
showArgs(1, 2, 3); // 输出 { '0': 1, '1': 2, '2': 3 }

const arrowShowArgs = () => {
  console.log(arguments); // ReferenceError: arguments is not defined
};
// arrowShowArgs(1, 2, 3);

小结

  • this关键字的值依赖于函数的调用方式,普通函数的this是动态变化的,而箭头函数的this是静态绑定的,继承自外部作用域。
  • 普通函数可以作为构造函数,拥有arguments对象;而箭头函数无法作为构造函数,并且没有arguments对象。
  • 对于需要根据调用上下文动态绑定的函数,使用普通函数;对于需要保持外部上下文this的情况,使用箭头函数。

理解this的行为以及普通函数与箭头函数之间的区别,能够帮助你在编写JavaScript时避免常见的陷阱和错误。

普通符号(Symbol)&& 共享符号(Global Symbols)&& 知名符号(Well-known Symbols)

在JavaScript中,普通符号(Symbol)是一种独特的数据类型,主要用于创建匿名和唯一的值,以避免属性名称冲突。根据你的描述,“普通符号”、“共享符号”和“知名符号”可以理解为对Symbols的不同分类或用法。

1. 符号(Symbol)概述

  • Symbol 是 ES6 新增的一种数据类型,用于创建唯一的标识符。
  • 使用 Symbol() 函数可以创建一个新的符号,每个符号都是唯一的。

示例:

const sym1 = Symbol('description');
const sym2 = Symbol('description');

console.log(sym1 === sym2); // false,因为每个Symbol都是唯一的

2. 共享符号(Global Symbols)

  • JavaScript 允许使用 Symbol.for() 创建共享符号。在全局注册表中,如果提供相同的键名,将返回相同的符号。
  • 这适用于需要跨不同模块或文件共享符号的场景。

示例:

const globalSym1 = Symbol.for('shared');
const globalSym2 = Symbol.for('shared');

console.log(globalSym1 === globalSym2); // true,因为它们是共享的符号
  • Symbol.for(key)

    • 如果指定的键已注册,则返回该符号。
    • 如果键没有注册,则创建一个新的符号并将其注册。

3. 知名符号(Well-known Symbols)

  • 知名符号是 JS 语言定义的特定符号,通常用于对象的默认操作,可以在内置对象中使用,例如 Symbol.iteratorSymbol.toStringTag 等。

示例:

// 使用 Symbol.iterator
const myArray = [1, 2, 3];

myArray[Symbol.iterator] = function() {
  let index = 0;
  const data = this; // 保存对数组的引用
  return {
    next: function() {
      return {
        value: data[index++],
        done: index > data.length
      };
    }
  };
};

const iterator = myArray[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 }

常见的知名符号

  • Symbol.iterator: 定义对象的默认迭代器。
  • Symbol.asyncIterator: 定义对象的默认异步迭代器。
  • Symbol.toStringTag: 使对象有一个自定义的 toString 标签。
  • Symbol.hasInstance: 定义instanceof的行为。

符号小结

  • 普通符号:使用 Symbol() 创建的唯一值。
  • 共享符号:使用 Symbol.for() 创建或获取全局共享的符号。
  • 知名符号:预定义的特定符号,供语言内部使用,如 Symbol.iterator,用于实现特定的语言功能。

ES6总结

哈哈哈,看到这是不是很慌,用了这么久这么多年的ES6你不懂的细节还有知识点是不是地动山摇了。。。别慌个后面我们一点点详细仔细梳理继续分享。。。