(ES6+)随笔

336 阅读8分钟

(ES6+)随笔

本文主要记录一些关于( ES6+ )的历史背景、应用场景、版本变动、新的属性和方法、类、模块化和一些其他内容,供自己以后查漏补缺,也欢迎同道朋友交流学习。

历史背景

ES6 的历史背景可以追溯到 1996 年,当时 JS 的创造者 Netscape 公司决定将 JS 提交给国际标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布了 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。

1997 年至 1999 年间,ECMAScript 经历了 2.03.0 两个版本的迭代。其中,ECMAScript 3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了JavaScript语言的基本语法。

2000 年,ECMAScript 4.0 开始酝酿,这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,可以说 ES6 制定的起点其实是 2000 年。ECMAScript 4.0之所以没有通过,是因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。

2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA决定中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分发布为 ECMAScript 3.1 ,而将其他激进的设想扩大范围,放入以后的版本,这个版本的项目代号起名为Harmony(和谐)。会后不久,ECMAScript 3.1就改名为 ECMAScript 5

2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony项目一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6。

2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从2000年算起,这时已经过去了15年,也被称为 ECMAScript 2015

2015 年以来,JS 语言规范每年都会发布新版本,引入新的语言特性和改进。

应用场景

ES6 及其后续版本引入了众多新特性,极大地改进了 JS 语言的能力,提高了开发效率和代码质量。以下是一些 ES6 在现代 Web 开发中的主要应用场景

  • 模块化编程:使用ES6的模块化特性,开发者可以创建结构清晰、易于维护的前端代码库。
  • 异步编程:利用 asyncawait 语法,简化了异步操作和回调函数的管理,改善了代码的可读性和错误处理。
  • 面向对象编程:通过 class 关键字,ES6提供了更简洁的语法来实现面向对象编程,包括继承、封装等特性。
  • 函数式编程箭头函数 和数组的高阶函数(如 mapfilterreduce)支持函数式编程范式。
  • 简化代码:使用 ${} 模板字符串、参数解构赋值、展开运算符等语法,可以更简化代码编写流程。

版本变动

自 ES6(ECMAScript 2015)以来,JS 语言规范每年都会发布新版本,引入新的语言特性和改进。"ES6+" 通常是指 ES6 之后的所有 ECMAScript 版本,包括 ES7(2016)ES8(2017)ES9(2018)ES10(2019),以及后续的年度版本。以下是一些 ES6+ 引入的关键特性的简要概述:

  1. ES7 (2016)

    • 引入了 Array.prototype.includes 方法,用于检查数组中是否存在特定元素。
    • 新增了指数运算符(**)。
  2. ES8 (2017)

    • 引入了 async/await 语法,简化了异步编程。
    • 新增了 Object.valuesObject.entries 方法。
    • 增加了 String.prototype.padStartString.prototype.padEnd 方法。
  3. ES9 (2018)

    • 引入了异步迭代器和 for-await-of 循环。
    • 新增了 Promise.finally 方法。
    • 增加了对正则表达式命名捕获组的支持。
  4. ES10 (2019)

    • 引入了 Array.prototype.flatArray.prototype.flatMap 方法,用于扁平化数组。
    • 新增了 Object.fromEntries 方法。
    • 增加了 String.prototype.trimStartString.prototype.trimEnd 方法。
  5. ES11 (2020)

    • 引入了可选链操作符(?.),简化了访问深层嵌套对象属性的语法。
    • 新增了空值合并运算符(??)。
  6. ES12 (2021)

    • 引入了逻辑赋值运算符,如 ||=&&=??= 等。
    • 新增了 Promise.allSettled 的支持。
  7. ESNext

    • ECMAScript 的持续发展,每年都会有新的提案和特性加入,如 class 字段和方法的提案,新的国际化 API,以及对 JavaScript 引擎性能的持续优化。

新的变量声明方式

ES6 引入了两种新的变量声明方式:letconst。这些声明方式与之前的 var 声明方式相比,提供了块级作用域不可变性等特点。

let 声明

  • let 用于声明一个块级作用域的变量,其值可以被重新赋值。
  • 在同一作用域内,不能重复声明同一个变量。
  • 存在暂时性死区,变量声明要在使用之前。
let age;
// 不能重复声明
// let age; // SyntaxError: Identifier 'age' has already been declared
age = 30;
console.log(age); // 输出 30

// 暂时性死区
// Uncaught ReferenceError: Cannot access 'name' before initialization
name = 'Tom';
let name; 

const 声明

  • const 用于声明一个块级作用域的常量,一旦声明并初始化后,其值不能被重新赋值。
  • 对于基本数据类型(如数字、字符串、布尔值),这意味着它们的值不可变;
  • 对于复合数据类型(如对象、数组),这意味着引用不可变,但对象或数组的内容可以变。
const age = 30;
// age = 40; // 尝试重新赋值会报错

const obj = { key: 'value' };
obj.key = 'newValue'; // 允许修改对象的内容

var 的对比

  • var 声明的变量具有函数作用域或全局作用域,而不是块级作用域。
  • var 声明的变量可以在同一作用域内重复声明。

使用 letconst 可以避免 var 可能引起的一些作用域相关的问题,如变量提升(hoisting)和全局变量污染。

箭头函数

ES6 箭头函数(Arrow Function)是 JS 中的一种新的函数定义方式,提供了更简洁的语法和自动绑定 this 的特性。以下是箭头函数的一些关键特点:

  • 简洁的语法:箭头函数使用一个箭头 => 代替了传统的 function 关键字。
// 箭头函数
const multiply = (x, y) => x * y;
  • 隐式返回:如果箭头函数的函数体只有一条语句,可以使用隐式返回,即省略花括号 {}return 关键字。
const double = x => x * 2;
console.log(double(10)); // 输出 20
  • 没有 this 关键字:箭头函数没有自己的 this 上下文,它会捕获其所在上下文的 this 值,作为自己的 this

  • 没有 arguments 对象:箭头函数不能使用 arguments 对象,如果需要使用类似的功能,可以使用剩余参数(rest parameters)代替。

  • 不适用于构造函数:箭头函数不能用作构造函数,也就是说,不能用 new 关键字来调用箭头函数。

  • 不绑定 callapplybind 方法:由于箭头函数没有自己的 this 上下文,所以 callapplybind 方法对箭头函数无效。

默认参数和模板字符串

  • 默认参数:在函数中,可以使用默认参数语法来为函数参数提供默认值。
  • 模板字符串:在字符串中,可以使用模板字符串语法来插入变量。
function greet(name = 'World') {
  // 使用模板字符串去做拼接
  console.log(`Hello, ${name}!`);
}

greet(); // "Hello, World!"
greet('niunai'); // "Hello, niunai!"

展开运算符和解构赋值

  • 展开运算符:在数组、字符串和函数参数中,可以使用展开运算符 ... 将一个数组、字符串或其他可迭代对象中的元素展开。
  • 解构赋值:在数组和对象中,可以使用解构赋值语法将数组或对象的元素赋值给变量。
const arr = [1, 2, 3];
const [a, ...b] = arr;
console.log(a); // 1
console.log(b); // [2, 3]

const obj = { x: 1, y: 2, z: 3 };
const { x, ...y } = obj;
console.log(x);  // 输出 1 2
console.log(y);  // {y: 2, z: 3}

类 (class)

ES6 中的 class 关键字是 JavaScript 中实现面向对象编程的一种新方式。虽然它看起来类似于其他面向对象语言中的类,但实际上,它是基于 JavaScript 现有的原型继承体系构建的语法糖。以下是 ES6 类的几个关键特性:

类的使用

// 使用 `class` 关键字声明一个新的类。
class Person {
  // 使用 `constructor` 方法初始化类的对象。
  // 当使用 `new` 关键字创建类的新实例时,会自动调用构造函数。
  constructor(name, age) {
    // 在构造函数中定义的属性是实例属性,每个实例都有自己的属性副本。
    this.name = name;
    this.age = age;
  }

  // 方法定义
  greet() {
    console.log(`Hello, my name is ${this.name}!`);
  }

  // 静态方法
  static info() {
    console.log('This is a static method.');
  }
}

// 调用静态方法
Person.info();

类的继承

使用 extends 关键字实现类的继承。子类可以继承并扩展父类的功能。

class Employee extends Person {
    constructor(name, age, jobTitle) {
      super(name, age); // 调用父类的构造函数
      this.jobTitle = jobTitle;
    }
    jobDescription() {
      super.info() // 调用父类的方法
      console.log(`${this.name} is a ${this.jobTitle}.`);
    }
}

getter 和 setter

使用 getset 关键字定义属性的 gettersetter,允许在读取或设置属性值时添加额外的逻辑。

class Person {
  // ...
  get age() {
    return this._age;
  }
  set age(value) {
    if (value > 0) {
      this._age = value;
    }
  }
}
  • 私有属性和方法:虽然 ES6 不直接支持私有属性,但可以使用符号(Symbols)或闭包来模拟私有属性。

模块化

ES6 模块化是 JS 语言的一项重大改进,它提供了一种新的组织代码的方式,允许开发者将功能分割成独立的模块,然后根据需要导入和导出这些模块。以下是 ES6 模块化的一些关键特性:

导出

使用 export 关键字从模块中导出函数、类、表达式或声明(变量、函数、类等)。

// math.js
export function add(x, y) {
  return x + y;
}

export const PI = 3.14;

// 在每个模块中,可以有一个默认导出,它使用 `default` 关键字标记。
const tools = {}
export default tools

导入

使用 import 关键字从其他模块导入导出的内容。

// 按需导入
import { add, PI } from './math.js';
console.log(add(1, 2)); // 3
console.log(PI); // 3.14

// 默认导入
import defaultFunction from './utils.js';
defaultFunction();

// 重命名导入
import * as MathUtils from './math.js';
MathUtils.add(2, 3);

// 动态导入
if (someCondition) {
  import('./module.js')
    .then(module => {
      module.doSomething();
    })
    .catch(err => {
      console.error('Module failed to load', err);
    });
}

模块化的好处

  • 封装性:模块可以封装代码和数据,避免全局命名空间的污染。
  • 可维护性:模块化代码更容易理解和维护。
  • 可重用性:模块可以在不同的项目中重复使用。
  • 依赖管理:模块之间的依赖关系更加清晰。

ES6 模块化是现代 JavaScript 开发的基础,它使得代码更加模块化、可维护和可扩展。随着前端工程化的发展,ES6 模块化已经成为大型前端项目的标准实践。

异步编程和Promise

ES6 引入了 Promise 作为异步编程的一种解决方案,它提供了一种新的方式来处理异步操作,使得异步代码的编写更加清晰和易于管理。

Promise

Promise 是一个代表异步操作最终完成或失败的对象。它有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。

// 创建 Promise
const myPromise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 异步操作成功 */) {
    resolve(value); // 操作成功,执行 resolve 回调
  } else {
    reject(error); // 操作失败,执行 reject 回调
  }
});

// 使用 Promise
myPromise
  .then(value => {
    // 处理异步操作的返回值
  })
  .catch(error => {
    // 处理异步操作中的错误
  });
Promise 的链式调用
  • Promise 的链式调用then() 方法返回一个新的 Promise 对象,这允许你将多个 Promise 调用链接在一起,形成链式调用。

    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        // 处理获取的数据
      })
      .catch(error => {
        // 处理请求或数据处理中的错误
      });
    
Promise 的静态方法
  • Promise.all():当需要等待多个异步操作都完成时,Promise.all() 方法可以接收一个 Promise 数组作为参数,只有当所有的 Promise 都成功时,它才会解决。

    Promise.all([promise1, promise2, promise3]).then(results => {
      // 所有 promise 都成功时执行
    });
    
  • Promise.race()Promise.race() 方法同样是接收一个 Promise 数组,但它会立即解决,只要数组中任何一个 Promise 完成(无论是成功还是失败)。

    Promise.race([promise1, promise2]).then(result => {
      // 最先完成的 promise 会决定结果
    });
    
Async/Await
  • asyncawaitasync 关键字用于声明一个异步函数,它使得函数内部的异步代码可以用同步的方式书写。await 关键字用于等待一个 Promise 解决,暂停函数的执行,直到 Promise 完成。

    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        // 使用数据
      } catch (error) {
        // 处理错误
      }
    }
    

Map、WeakMap

MapWeakMap 是 ES6 引入的两种集合类型,它们提供了存储键值对的方式,类似于传统的对象({}),但它们提供了一些额外的功能和灵活性。

Map

Map 是一个集合,它存储键值对的数组,其中键可以是任意类型。

  • 键可以是任意类型,包括对象、函数等。
  • Map 对象保持键的插入顺序。
// 创建 Map
const map = new Map();

// 添加元素
map.set(key, value);

// 获取元素
const value = map.get(key);

// 检查 Map 中是否存在键
const hasKey = map.has(key);

// 删除元素
map.delete(key);

// 遍历 Map*
map.forEach((value, key) => {
  console.log(key, value);
});

WeakMap

WeakMap 是一个收集键值对的集合,与 Map 类似,但它的键必须是对象,而且这些对象的引用是弱引用,意味着如果对象没有其他的引用,它们可以被垃圾回收器回收。

  • 键必须是对象。
  • 没有 size 属性,因为垃圾回收器可以随时回收只有 WeakMap 引用的对象。
  • 不可遍历,没有 forEach 方法。
// 创建 WeakMap
const weakMap = new WeakMap();

// 添加元素
weakMap.set(keyObject, value);

// 获取元素
const value = weakMap.get(keyObject);

// 检查 WeakMap 中是否存在键
const hasKey = weakMap.has(keyObject);

// 删除元素
weakMap.delete(keyObject);

Map和WeakMap的应用场景

MapWeakMap 是两种不同的集合类型,它们在 JS 中有着各自的应用场景:

  1. Map 的应用场景

    • 需要有序的键值对存储Map 保持元素的插入顺序,这在需要有序遍历键值对时非常有用。
    • 键可以是任意类型:与普通对象不同,Map 的键可以是任何值,包括对象、函数或任何原始类型。
    • 需要大量键值对:当对象的键数量非常大时,使用 Map 可以避免潜在的性能问题,因为对象的键作为字符串处理可能会影响性能。
    • 需要快速查找键是否存在Map 提供了 .has() 方法,可以快速检查一个键是否存在于 Map 中。
    • 需要删除键值对:使用 Map 可以方便地使用 .delete() 方法删除特定的键值对。
    • 需要遍历键值对Map 提供了 .forEach() 方法,可以遍历所有键值对,这在需要对键和值执行操作时非常有用。
    • 需要大小可变的集合Map 的大小可以动态变化,适合需要根据运行时数据调整集合大小的场景。
  2. WeakMap 的应用场景

    • 需要自动垃圾回收的键WeakMap 的键是弱引用,如果键在其他地方没有被引用,那么垃圾回收器可以回收这些键所占用的内存。
    • 管理内存敏感的场景:当需要缓存大量对象时,使用 WeakMap 可以减少内存泄漏的风险。
    • 私有数据存储WeakMap 可以用来存储对象的私有数据,而不污染对象本身,因为 WeakMap 不可迭代,外部代码无法访问到存储的数据。
    • 事件处理器或回调的存储:当需要为对象关联事件处理器或回调,并且希望这些处理器或回调在对象被销毁时自动清理时,WeakMap 是一个很好的选择。
    • 缓存和性能优化WeakMap 可以用作缓存实现,当缓存的键对象不再被使用时,缓存可以被自动清理。
    • 避免循环引用:在某些情况下,对象之间可能形成循环引用,使用 WeakMap 可以避免这种循环引用导致的内存泄漏问题。

Set、WeakSet

ES6 中的 SetWeakSet 提供了不同的集合功能,以下是它们的使用方式和一些示例。

Set 的使用

Set 是一种新的集合类型,它存储唯一的值,无论是原始类型还是对象。

// 创建一个 Set
const mySet = new Set();

// 添加元素
mySet.add(1);
mySet.add('text');
mySet.add({ name: 'Tom' });

// 检查 Set 中是否包含某个元素
console.log(mySet.has(1)); // true

// 获取 Set 的大小
console.log(mySet.size); // 3

// 移除 Set 中的元素
mySet.delete('text');

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

// 数组去重
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]

WeakSet 的使用

WeakSet 是一种只能存储对象引用的集合,且这些引用是弱引用,意味着如果对象没有被其他地方引用,它可以被垃圾回收。

基本用法

// 创建一个 WeakSet
const myWeakSet = new WeakSet();

// 添加元素到 WeakSet,元素必须是对象
const myObject = {};
myWeakSet.add(myObject);

// WeakSet 不提供 has 方法,因此无法直接检查对象是否存在

// 移除元素
myWeakSet.delete(myObject);

// WeakSet 不可迭代,因此无法直接遍历

Set和WeakSet使用场景对比

  • 使用 Set 存储一组不重复的数据,可以是任何类型,适合需要明确元素存在性检查和遍历的场景。
  • 使用 WeakSet 存储对象引用,适合不需要长期持有对象引用,或希望自动管理内存的场景。

Set和WeakSet注意事项

  • Set 是可迭代的,而 WeakSet 不是。这意味着你不能使用 forEachfor...of 循环遍历 WeakSet
  • WeakSet 只能存储对象,不能存储 null 或原始类型。
  • WeakSet 中的对象引用是弱引用,因此 WeakSet 本身不阻止垃圾回收器回收这些对象。

Symbol 和私有属性

Symbol 是 ES6 引入的一种新的原始数据类型,用于创建唯一的、不可变的(immutable)值,通常用作对象属性的键。使用 Symbol 可以创建一个私有属性,因为 Symbol 值不能被猜测或意外地访问。

// 创建 Symbol
const mySymbol = Symbol('mySymbol');

// 作为对象属性的键
const myObject = {
  [mySymbol]: 'This is a private property'
};

// 访问 Symbol 属性
console.log(myObject[mySymbol]); // 输出: This is a private property

// Symbol 唯一性
const uniqueSymbol = Symbol('unique');
console.log(uniqueSymbol === Symbol('unique')); // 输出:false

私有属性的使用

在 ES6 之前,JavaScript 对象并没有真正的私有属性概念。但是,使用 Symbol 可以模拟私有属性:

  1. 定义私有属性: 通过将属性的键设置为 Symbol,可以使得属性在对象外部不可见或不可访问。

    const privateKey = Symbol('privateKey');
    class MyClass {
       constructor() {
         this[privateKey] = 'I am a private property';
       }
    }
    
    const myInstance = new MyClass();
    // console.log(myInstance[privateKey]); // 引用错误,privateKey 在外部是不可访问的
    
  2. 在类中访问私有属性:在类的内部,可以通过 this[Symbol] 访问私有属性。

  3. 私有属性的继承:子类无法访问父类的 Symbol 属性,这提供了一种额外的封装层。

    class ChildClass extends MyClass {
       showPrivate() {
         console.log(this[privateKey]); // 引用错误,privateKey 在子类中不可访问
       }
    }
    

Symbol注意事项

  • Symbol 作为属性键不会显示在 for...in 循环或 Object.keys() 中。
  • Symbol 属性不能通过 Object.getOwnPropertyNames() 获取,但可以通过 Object.getOwnPropertySymbols() 获取。

迭代器和生成器

在 ES6 中,迭代器生成器是两个重要的概念,它们改变了我们处理数据结构的方式以及编写异步代码的方法。下面我将详细介绍这两个概念及其基本使用方式。

迭代器 (Iterator)

迭代器是一种遍历集合中元素的对象。每个迭代器都有一个 next() 方法,用于获取序列中的下一个值。这个方法返回一个包含两个属性的对象:valuedone。其中 value 是当前元素的值,而 done 是一个布尔值,指示是否已经到达序列的末尾

// 创建一个简单的迭代器
const simpleIterator = {
  count: 0,
  next: function() {
    if (this.count < 3) {
      this.count++;
      return { value: this.count, done: false };
    } else {
      return { done: true };
    }
  }
};

// 使用迭代器
let result = simpleIterator.next();
while (!result.done) {
  console.log(result.value);
  result = simpleIterator.next();
}

生成器 (Generator)

生成器是一种特殊的函数,它允许你创建迭代器。生成器函数使用 function* 语法声明,并且可以包含一个或多个 yield 表达式。yield 表达式使得生成器函数可以“挂起”其执行过程,并在之后通过调用 next() 方法恢复执行。

生成器函数的特性包括

  • 使用 function* 语法声明。
  • yield 表达式用于产生值并暂停函数执行。
  • 生成器函数返回一个迭代器。
function* simpleGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = simpleGenerator();

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 }

迭代器与生成器的结合

生成器通常被用来创建迭代器。生成器函数通过 yield 表达式来产生值,这些值可以被迭代器返回。

function* numberGenerator(limit) {
  for (let i = 1; i <= limit; i++) {
    yield i * i;
  }
}

const squareNumbers = numberGenerator(5);

for (let num of squareNumbers) {
  console.log(num); // 输出 1, 4, 9, 16, 25
}

Proxy 和 Reflect

在 ES6中,ProxyReflect 是两个非常有用但相对高级的概念,它们提供了强大的功能来拦截和控制对象的行为。

Proxy

Proxy 是一种对象,它可以作为目标对象的代理。当你尝试访问、修改或者执行某些操作在一个 Proxy 对象上时,你可以定义一组拦截器来改变或观察这些操作的行为。

Proxy 的创建需要两个参数:一个是目标对象,另一个是处理器对象(handler)。处理器对象定义了各种拦截行为,例如读取属性、设置属性、构造函数调用等。

// 创建 Proxy
const target = {};
const handler = {
  get: function(target, prop, receiver) {
    console.log(`Fetching ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set: function(target, prop, value, receiver) {
    console.log(`Setting: ${prop} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};

const proxy = new Proxy(target, handler);
常见的 Proxy 方法
  • get: 当读取一个属性时触发。
  • set: 当设置一个属性时触发。
  • apply: 当调用一个函数时触发。
  • construct: 当使用 new 调用一个构造函数时触发。
  • has: 当使用 in 操作符时触发。
  • deleteProperty: 当删除一个属性时触发。
  • ownKeys: 获取所有属性键时触发。
  • getOwnPropertyDescriptor: 获取属性描述符时触发。
  • defineProperty: 定义或修改一个属性时触发。
  • preventExtensions: 当尝试阻止对象扩展时触发。
  • getPrototypeOf: 获取原型时触发。
  • setPrototypeOf: 设置原型时触发。

Reflect

Reflect 是一个内置的对象,它提供了一系列静态方法来直接操作目标对象。这些方法与 Proxy 处理器方法相对应,可以让你在不使用 Proxy 的情况下执行相同的操作。

Reflect 的设计目的是为了使 JavaScript 的行为更加一致和可预测,并且减少了语言中的“魔法”部分。

常见的 Reflect 方法
  • Reflect.get(target, propertyKey[, receiver]): 获取属性值。
  • Reflect.set(target, propertyKey, value[, receiver]): 设置属性值。
  • Reflect.apply(function, thisArg, argsArray): 调用函数。
  • Reflect.construct(constructor, argsArray[, newTarget]): 构造新对象。
  • Reflect.has(target, propertyKey): 检查属性是否存在。
  • Reflect.deleteProperty(target, propertyKey): 删除属性。
  • Reflect.ownKeys(target): 获取所有属性键。
  • Reflect.getOwnPropertyDescriptor(target, propertyKey): 获取属性描述符。
  • Reflect.defineProperty(target, propertyKey, attributes): 定义或修改属性。
  • Reflect.preventExtensions(target): 防止对象扩展。
  • Reflect.getPrototypeOf(target): 获取原型。
  • Reflect.setPrototypeOf(target, prototype): 设置原型。

Proxy和Reflect的作用

  • Proxy 主要用于拦截操作,而 Reflect 则用于执行这些操作。
  • Proxy 使得我们可以定义自定义的行为模式,而 Reflect 则提供了一种标准化的方式来执行这些行为。
  • 两者经常配合使用,以实现更复杂的对象行为控制。

ES6新增的对象方法

从ES6开始,JavaScript引入了多个新特性来增强对象的操作,以下是一些在ES6及之后版本中新增或改进的对象方法:

  • 属性的简写:当对象的键名和对应的值名相同时,可以省略值名只写键名
  • 属性名表达式:允许使用表达式作为对象的属性名,需要将表达式放在方括号内。
  • super关键字:指向当前对象的原型对象,只能在对象的方法中使用 。
  • 扩展运算符:允许从一个对象中取出所有可枚举的属性,并将它们复制到另一个对象中 。
  • 属性的遍历:ES6提供了多种方法来遍历对象的属性,包括for...in循环、Object.keys()Object.getOwnPropertyNames()Object.getOwnPropertySymbols()Reflect.ownKeys()
  • Object.is():一个新方法,用来比较两个值是否严格相等,与===运算符相似,但+0不等于-0NaN等于自身。
  • Object.assign():用于对象的合并,将源对象的所有可枚举属性复制到目标对象 。
  • Object.getOwnPropertyDescriptors():返回指定对象所有自身属性的描述对象 。
  • Object.setPrototypeOf()Object.getPrototypeOf():分别用来设置和获取一个对象的原型对象 。
  • Object.keys(), Object.values(), Object.entries():分别返回一个数组,包含对象自身的所有可枚举属性的键名、值和键值对 。
  • Object.fromEntries():将键值对数组转换为一个对象 。
  • Object.hasOwn():检查对象是否具有指定的自有属性 。

ES6新增的数组方法

ES6 为 Array 对象新增了一些有用的方法,使得数组的操作更加简洁和高效。以下是一些在 ES6 中引入的数组方法:

  1. Array.from():用于将类数组对象(如 NodeList)或可迭代对象(如 SetMap)转换成真正的数组。

    const divs = document.querySelectorAll('div');
    const arrayFromDivs = Array.from(divs);
    
  2. Array.of():用于创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

    const array = Array.of(1, 2, 3);
    // [1, 2, 3]
    
  3. find():返回数组中满足提供的测试函数的第一个元素的值,如果没有找到则返回 undefined

    const users = [{ name: 'Tom', age: 30 }, { name: 'Tony', age: 25 }];
    const user = users.find(user => user.age === 25);
    // { name: 'Tony', age: 25 }
    
  4. findIndex():返回数组中满足提供的测试函数的第一个元素的索引,如果没有找到则返回 -1

    const index = users.findIndex(user => user.name === 'Tom');
    // 0
    
  5. fill():用一个固定值填充一个数组从开始到结束(或到指定位置)的元素。

    const array = [1, 2, 3];
    array.fill(4);
    // [4, 4, 4]
    
  6. copyWithin():将数组的一部分复制到数组的另一个位置,覆盖或替换现有元素。

    const array = [1, 2, 3, 4, 5];
    array.copyWithin(0, 3);
    // [4, 5, 3, 4, 5]
    
  7. 扩展的 slice()slice() 方法现在可以接受负数参数,表示从数组末尾开始计算的位置。

    const array = [1, 2, 3, 4, 5];
    const lastTwo = array.slice(-2);
    // [4, 5]
    
  8. 扩展的 splice()splice() 方法现在可以接受更多的参数,包括用于替换的元素。

    const array = [1, 2, 3, 4];
    array.splice(2, 1, 'a', 'b');
    // [1, 2, 'a', 'b', 4]
    
  9. includes():用于判断数组是否包含某个值,返回 truefalse

    const array = [1, 2, 3];
    const contains = array.includes(2);
    // true
    
  10. flat()flatMap()flat() 方法用于将数组的嵌套结构(多维数组)扁平化为一维数组。flatMap() 方法首先使用映射函数映射每个元素,然后对返回的数组进行扁平化。

    const array = [1, [2, 3], 4];
    const flatArray = array.flat();
    // [1, 2, 3, 4]
    
    const array = [1, 2, 3];
    const flatMapped = array.flatMap(element => [element, element + 1]);
    // [1, 2, 2, 3, 3, 4]