ECMAScript 和 JavaScript 之间的关系可以概括如下:

  • ECMAScript 是标准:ECMAScript 是由欧洲计算机制造商协会(ECMA)制定的一个脚本语言标准。它定义了脚本语言的语法、类型、语句、关键字、保留字、操作符、对象等。

  • JavaScript 是实现:JavaScript 是 ECMAScript 标准的一种实现。它最初由 Netscape 开发,并在此基础上不断发展。JavaScript 不仅遵循 ECMAScript 标准,还包括一些特定于浏览器的功能和 API,例如 DOM 操作、事件处理等。

  • 版本关系:ECMAScript 的版本更新通常会引入新的语言特性,而 JavaScript 会随着这些标准的更新而发展。例如,ES6(ECMAScript 2015)引入了许多新特性,如箭头函数、类、模块、let 和 const 关键字等,这些特性随后在现代 JavaScript 中得到了广泛应用。

  • 其他实现:除了 JavaScript,ECMAScript 还有其他实现,如 JScript(由微软开发)和 ActionScript(用于 Adobe Flash)。

ES5和ES6的说明

ES6 的新特性对于现代前端开发尤为重要,因为许多现代框架和库(如 Vue.js、React 和 Angular)都充分利用了 ES6 及更高版本的特性来构建复杂和高效的应用程序。 ES5和ES6(也称为ECMAScript 2015)是JavaScript语言的两个重要版本,它们在语法和功能上有许多显著的区别。

其它版本

ES7 (ECMAScript 2016):发布于2016年。ES7引入了一些较小的特性,例如指数运算符(**),Array.prototype.includes 方法用于检查数组中是否包含某个元素,以及async和await关键字,使得异步代码的编写更加容易。

ES8 (ECMAScript 2017):发布于2017年。ES8带来了诸如异步迭代(for-await-of循环)、Object.values()和Object.entries()方法、字符串填充方法(String.prototype.padStart和String.prototype.padEnd)以及Rest/Spread属性等特性。

ES9 (ECMAScript 2018):发布于2018年。ES9引入的特性包括异步迭代和生成器、Rest/Spread属性、Promise.finally()方法、正则表达式改进等。

ES10 (ECMAScript 2019):发布于2019年。ES10的新特性包括Array.prototype.flat()和Array.prototype.flatMap()方法、String.prototype.trimStart和String.prototype.trimEnd方法、可选的catch绑定等。

ES11 (ECMAScript 2020):发布于2020年。ES11带来了可选链(Optional Chaining)、空值合并运算符(Nullish Coalescing)、国际化增强、String.prototype.replaceAll方法、逻辑赋值运算符等。

ES12 (ECMAScript 2021):发布于2021年。ES12引入了Promise.any方法、String.prototype.replaceAll方法、逻辑赋值运算符、Intl.DateTimeFormat和Intl.DisplayNames的改进等。

以下是ES5和ES6之间的一些主要区别:

1.变量声明:

  • ES5:仅使用var关键字声明变量,存在变量提升(hoisting)的现象。
  1. var name = 'Alice';
  • ES6:引入了let和const关键字。let用于声明块级作用域的变量,而const用于声明不变的常量。这两个关键字解决了变量提升和作用域的问题。
  1. let age = 25;
  2. const PI = 3.14159;

2.块级作用域中的函数声明

  • ES5: 在块级作用域中声明函数可能导致不可预测的行为,特别是在严格模式下。

  • ES6: 明确支持在块级作用域中声明函数,遵循块级作用域规则。

  1. {
  2. function foo() {
  3. console.log('ES6 块级作用域中的函数');
  4. }
  5. foo(); // 正常调用
  6. }
  7. // foo 在块外不可访问

3. 箭头函数:

  • ES5:使用function关键字或函数表达式声明函数。

    1. var add = function(a, b) {
    2. return a + b;
    3. };
  • ES6:引入了箭头函数((args) => { // }),提供了更简洁的函数写法,并且不绑定自己的this上下文。

  1. const add = (a, b) => a + b;

4. 字符串模板(模板字面量):

  • ES5:字符串拼接通常使用+操作符。
  1. var greeting = 'Hello, ' + name + '!';
  • ES6:引入了模板字符串(使用反引号`),支持多行字符串和表达式插值。
  1. const greeting = `Hello, ${name}!`;

5. 解构赋值:

  • ES5:没有直接的解构赋值语法。
  1. var person = { name: 'Bob', age: 30 };
  2. var name = person.name;
  3. var age = person.age;
  • ES6:允许从数组或对象中提取值,并直接赋值给变量。
  1. const { name, age } = person;

6. 类(Class):

  • ES5:使用构造函数和原型链实现面向对象编程。
  1. function Person(name) {
  2. this.name = name;
  3. }
  4. Person.prototype.sayHello = function() {
  5. console.log('Hello, ' + this.name);
  6. };
  • ES6:引入了class关键字,提供了更简洁的语法来定义类和方法。
  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. sayHello() {
  6. console.log(`Hello, ${this.name}`);
  7. }
  8. }

7. 模块化:

  • ES5:没有原生的模块系统,通常使用CommonJS(Node.js)或AMD(RequireJS)等模块规范。
  1. // 使用 IIFE
  2. var myModule = (function() {
  3. var privateVar = 'secret';
  4. return {
  5. getSecret: function() {
  6. return privateVar;
  7. }
  8. };
  9. })();
  • ES6:引入了模块化(使用import和export),支持原生的模块导入和导出。
  1. // 导出
  2. export const pi = 3.14159;
  3. // 导入
  4. import { pi } from './math.js';

8. Promise对象:

  • ES5:没有原生的Promise对象。
  1. asyncFunction1(function(result1) {
  2. asyncFunction2(result1, function(result2) {
  3. // 继续嵌套
  4. });
  5. });
  • ES6:引入了Promise,提供了一种更优雅的方式来处理异步操作。
  1. asyncFunction1()
  2. .then(result1 => asyncFunction2(result1))
  3. .then(result2 => {
  4. // 继续处理
  5. })
  6. .catch(error => {
  7. // 错误处理
  8. });

9. 扩展运算符和剩余运算符:

  • ES5:没有这些运算符。使用 apply 或手动拼接数组。
  1. function sum(a, b, c) {
  2. return a + b + c;
  3. }
  4. var numbers = [1, 2, 3];
  5. sum.apply(null, numbers);
  • ES6:引入了扩展运算符(…),允许将数组或对象展开到另一个数组或对象中,以及剩余运算符,允许将剩余的参数收集到一个数组中。
  1. const sum = (a, b, c) => a + b + c;
  2. const numbers = [1, 2, 3];
  3. sum(...numbers);
  4. const func = (...args) => {
  5. console.log(args);
  6. };

10. 函数参数的默认值和扩展属性:

  • ES5:函数参数没有默认值,也没有扩展属性。
  1. function greet(name) {
  2. name = name || 'Guest';
  3. console.log('Hello, ' + name);
  4. }
  • ES6:允许为函数参数设置默认值,并且对象字面量可以扩展属性。
  1. function greet(name = 'Guest') {
  2. console.log(`Hello, ${name}`);
  3. }

11. 生成器(Generators)

  • ES5: 没有内置生成器功能。

  • ES6: 引入了生成器函数,通过 function* 和 yield 实现迭代器模式。

  1. function* generator() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. }
  6. const gen = generator();
  7. console.log(gen.next().value); // 1
  8. console.log(gen.next().value); // 2
  9. console.log(gen.next().value); // 3

12. Symbol 和 迭代器(和异步迭代器)

  • ES5: 没有 Symbol 类型,迭代器需要手动实现。

  • ES6: 引入了 Symbol 类型和内置的迭代器协议,支持更高级的迭代功能。

  1. const sym = Symbol('description');
  2. const obj = {
  3. [sym]: 'value'
  4. };
  5. for (let key in obj) {
  6. console.log(key); // 不会输出 Symbol 类型的属性
  7. }
  8. // ES6 迭代器示例(同步)
  9. const iterable = {
  10. *[Symbol.iterator]() {
  11. yield 1;
  12. yield 2;
  13. yield 3;
  14. }
  15. };
  16. for (const value of iterable) {
  17. console.log(value);
  18. }
  19. // ES2018 异步迭代器示例
  20. async function* asyncGenerator() {
  21. yield 'a';
  22. yield 'b';
  23. yield 'c';
  24. }
  25. (async () => {
  26. for await (const value of asyncGenerator()) {
  27. console.log(value); // 'a', 'b', 'c'
  28. }
  29. })();

13. 增强的对象字面量

  • ES5: 对象属性和方法需要完整的语法。
  1. var obj = {
  2. name: name,
  3. sayHello: function() {
  4. console.log('Hello');
  5. }
  6. };
  • ES6: 支持属性和值相同的简写、方法定义更简洁。
  1. const obj = {
  2. name,
  3. sayHello() {
  4. console.log('Hello');
  5. }
  6. };

14. Map 和 Set 数据结构

  • ES5: 使用对象和数组来模拟键值对和集合,但效率较低,且不支持非字符串类型的键。
  1. // 使用对象模拟 Map
  2. var map = {};
  3. map['key1'] = 'value1';
  4. // 使用数组模拟 Set
  5. var set = [];
  6. set.push(1);
  7. if (set.indexOf(1) === -1) {
  8. set.push(1);
  9. }
  • ES6: 引入了 Map 和 Set,提供了更高效和便捷的键值对和集合操作。
  1. // 使用 Map
  2. const map = new Map();
  3. map.set('key1', 'value1');
  4. console.log(map.get('key1')); // 'value1'
  5. // 使用 Set
  6. const set = new Set();
  7. set.add(1);
  8. set.add(1); // 重复添加不会生效
  9. console.log(set.has(1)); // true

15. WeakMap 和 WeakSet

  • ES5: 没有弱引用的数据结构,所有键和值都被强引用,无法垃圾回收。

  • ES6: 引入了 WeakMap 和 WeakSet,允许键或值为弱引用,有助于内存管理。

  1. // WeakMap 示例
  2. const wm = new WeakMap();
  3. let obj = {};
  4. wm.set(obj, 'value');
  5. obj = null; // obj 可以被垃圾回收
  6. // WeakSet 示例
  7. const ws = new WeakSet();
  8. let obj2 = {};
  9. ws.add(obj2);
  10. obj2 = null; // obj2 可以被垃圾回收

16. 模块的动态导入(Dynamic Imports)

  • 模块加载通常依赖于同步或异步加载器,如 RequireJS,但不支持动态导入。

-ES6: 除了静态的 import 语法,还可以使用动态导入(需要结合 Promise 使用)。

  1. // 静态导入
  2. import { moduleA } from './moduleA.js';
  3. // 动态导入
  4. import('./moduleB.js')
  5. .then(moduleB => {
  6. moduleB.doSomething();
  7. })
  8. .catch(err => {
  9. console.error('模块加载失败', err);
  10. });

17. 尾调用优化(尾递归函数)

  • ES5: 不支持尾调用优化,递归调用可能导致堆栈溢出。

  • ES6: 规范中引入了尾调用优化,允许编译器优化尾递归调用,防止堆栈溢出(虽然 ES6 规范支持尾递归优化,但实际应用中,由于多数 JavaScript 引擎尚未完全实现这一特性,开发者在编写递归函数时仍需谨慎,避免深度递归导致的堆栈溢出问题。)。

  1. // 尾递归函数
  2. function factorial(n, acc = 1) {
  3. if (n <= 1) return acc;
  4. return factorial(n - 1, n * acc); // 尾调用
  5. }
  6. console.log(factorial(5)); // 120

18. 二进制和八进制字面量

  • ES5: 不支持二进制和八进制字面量,需使用字符串或十六进制表示。
  1. // 使用十六进制
  2. var num = 0xFF; // 255
  • ES6: 支持二进制(0b)和八进制(0o)字面量。
  1. const binary = 0b11111111; // 255
  2. const octal = 0o377; // 255

19. Unicode 字符的增强支持

  • ES5: 对 Unicode 支持有限,无法直接表示所有 Unicode 字符。

  • ES6: 引入了 Unicode 转义序列,可以使用 \u{} 表示任意 Unicode 字符。

  1. const smile = '\u{1F600}'; // 😀
  2. console.log(smile);

20. 新的内置方法和对象

  • ES5: 内置方法相对较少,功能有限。

  • ES6: 增加了许多新的内置方法和对象,如 Array.from、Array.find、Object.assign、Math 方法的扩展等。

  1. // Array.from
  2. const arrayLike = { length: 2, 0: 'a', 1: 'b' };
  3. const arr = Array.from(arrayLike); // ['a', 'b']
  4. // Array.find
  5. const numbers = [1, 2, 3, 4];
  6. const found = numbers.find(num => num > 2); // 3
  7. // Object.assign
  8. const target = { a: 1 };
  9. const source = { b: 2 };
  10. const merged = Object.assign(target, source); // { a: 1, b: 2 }

21. 符号(Symbol)

  • ES5: 没有原生支持符号类型,无法创建独一无二的标识符。

  • ES6: 引入 Symbol 类型,用于创建唯一且不可变的标识符,常用于对象属性的私有化。

  1. const sym1 = Symbol('desc');
  2. const sym2 = Symbol('desc');
  3. console.log(sym1 === sym2); // false
  4. const obj = {
  5. [sym1]: 'value1'
  6. };
  7. console.log(obj[sym1]); // 'value1'

22. 尾部逗号(Trailing Commas)

  • ES5: 在对象和数组字面量中,尾部逗号可能导致某些浏览器解析错误。

  • ES6: 允许在对象和数组的最后一个元素后添加尾部逗号,提升代码的可维护性。

  1. const obj = {
  2. a: 1,
  3. b: 2,
  4. };
  5. const arr = [
  6. 1,
  7. 2,
  8. 3,
  9. ];

23. 标签模板(Tagged Templates)

  • ES5: 只能使用普通的模板字符串,无法对模板字符串进行处理。

  • ES6: 引入标签模板,允许在解析模板字符串时调用函数,实现更复杂的字符串处理。

  1. function tag(strings, ...values) {
  2. console.log(strings);
  3. console.log(values);
  4. return 'Tagged Template Result';
  5. }
  6. const name = 'Alice';
  7. const age = 25;
  8. const result = tag`Name: ${name}, Age: ${age}`;
  9. console.log(result); // 'Tagged Template Result'

24. 尾部扩展

  • ES5: 函数参数列表中的尾部逗号可能导致语法错误。

  • ES6: 允许在函数参数列表中使用尾部逗号,提升代码的可维护性和可扩展性。

  1. function foo(
  2. param1,
  3. param2,
  4. ) {
  5. // 函数体
  6. }

25. Reflect API

  • ES5: 没有 Reflect 对象,无法统一处理对象的操作。

  • ES6: 引入了 Reflect 对象,提供了一组用于操作对象的方法。这些方法与 Object 的方法类似,但返回布尔值或其他更合理的结果,有助于元编程和代理操作。

  1. // 使用 Reflect.set
  2. const obj = {};
  3. Reflect.set(obj, 'name', 'Alice');
  4. console.log(obj.name); // 'Alice'
  5. // 使用 Reflect.get
  6. console.log(Reflect.get(obj, 'name')); // 'Alice'

26. Proxy API

  • ES5: 无法拦截和定义对象的底层操作,如属性访问、赋值、函数调用等。
  • ES6: 引入了 Proxy 对象,允许开发者创建一个对象的代理,从而拦截和自定义基本操作(如属性查找、赋值、枚举、函数调用等)。
  1. const target = {};
  2. const handler = {
  3. get: function(target, prop, receiver) {
  4. console.log(`获取属性 ${prop}`);
  5. return Reflect.get(target, prop, receiver);
  6. },
  7. set: function(target, prop, value, receiver) {
  8. console.log(`设置属性 ${prop} 为 ${value}`);
  9. return Reflect.set(target, prop, value, receiver);
  10. }
  11. };
  12. const proxy = new Proxy(target, handler);
  13. proxy.name = 'Bob'; // 控制台输出: 设置属性 name 为 Bob
  14. console.log(proxy.name); // 控制台输出: 获取属性 name,结果: 'Bob'

27. 指数运算符(Exponentiation Operator)

  • ES5: 使用 Math.pow 方法进行指数运算。
  1. var result = Math.pow(2, 3); // 8
  • ES6: 引入了 ** 运算符,使指数运算更加简洁。
  1. const result = 2 ** 3; // 8

28. 新的字符串方法

  • ES5: 字符串操作功能有限,主要依赖于字符串方法如 indexOf、substr 等。
  • ES6: 增加了许多新的字符串方法,提升了字符串处理的便利性。
  1. // includes: 判断字符串是否包含指定子串。
  2. const str = 'Hello, world!';
  3. console.log(str.includes('world')); // true
  4. // startsWith 和 endsWith: 判断字符串是否以指定子串开始或结束。
  5. console.log(str.startsWith('Hello')); // true
  6. console.log(str.endsWith('!')); // true
  7. //repeat: 重复字符串指定次数。
  8. console.log('ha'.repeat(3)); // 'hahaha'
  9. //padStart 和 padEnd: 在字符串前后填充指定字符,以达到指定长度。
  10. console.log('5'.padStart(2, '0')); // '05'
  11. console.log('5'.padEnd(3, '0')); // '500'

29. 新的数字方法

  • ES5: 数字方法相对有限,主要依赖于 Math 对象的方法。
  • ES6: 增加了一些新的数字相关的方法,提升了数值处理的能力。
  1. // Number.isNaN: 判断值是否为 NaN。
  2. console.log(Number.isNaN(NaN)); // true
  3. console.log(Number.isNaN('NaN')); // false
  4. // Number.isInteger: 判断值是否为整数。
  5. console.log(Number.isInteger(4)); // true
  6. console.log(Number.isInteger(4.5)); // false
  7. // Number.isFinite: 判断值是否为有限数。
  8. console.log(Number.isFinite(10)); // true
  9. console.log(Number.isFinite(Infinity)); // false

30. 新的 Math 方法

  • ES5: Math 对象的方法主要包括基本的数学运算,如 Math.max、Math.min、Math.floor 等。
  • ES6: 增加了一些新的数学方法,扩展了 Math 对象的功能。
  1. // Math.trunc: 去除数字的小数部分,返回整数部分。
  2. console.log(Math.trunc(4.9)); // 4
  3. console.log(Math.trunc(-4.9)); // -4
  4. // Math.sign: 判断数字的符号,返回 1、-1 或 0。
  5. console.log(Math.sign(10)); // 1
  6. console.log(Math.sign(-10)); // -1
  7. console.log(Math.sign(0)); // 0
  8. // Math.cbrt: 计算数字的立方根。
  9. console.log(Math.cbrt(27)); // 3
  10. // Math.hypot: 计算所有参数的平方和的平方根,类似于欧几里得距离。
  11. console.log(Math.hypot(3, 4)); // 5

31. 新增的数组方法

  • ES5: 数组方法主要包括 forEach、map、filter、reduce 等。
  • ES6: 增加了一些新的数组方法,进一步增强了数组的操作能力。
  1. // Array.findIndex: 返回满足提供的测试函数的第一个元素的索引,否则返回 -1。
  2. const array = [5, 12, 8, 130, 44];
  3. const foundIndex = array.findIndex(element => element > 10);
  4. console.log(foundIndex); // 1
  5. // Array.fill: 用静态值填充数组中的元素,从起始索引到结束索引(不包括)。
  6. const array1 = [1, 2, 3];
  7. array1.fill(4, 1, 3);
  8. console.log(array1); // [1, 4, 4]
  9. // Array.copyWithin: 在数组内部将指定位置的元素复制到另一个位置,并返回该数组。
  10. const array2 = ['a', 'b', 'c', 'd', 'e'];
  11. array2.copyWithin(0, 3, 4);
  12. console.log(array2); // ['d', 'b', 'c', 'd', 'e']

32. Promise.finally

  • ES5: 处理异步操作时,通常需要在 then 或 catch 中处理完成后的逻辑,可能导致代码重复。
  • ES6: 虽然 Promise.finally 是在 ES2018 中引入的,但许多现代项目也将其视为 ES6+ 的一部分。finally 方法允许在 Promise 结束后执行某些操作,无论是成功还是失败。
  1. fetch('https://api.example.com/data')
  2. .then(response => response.json())
  3. .then(data => {
  4. // 处理数据
  5. })
  6. .catch(error => {
  7. // 处理错误
  8. })
  9. .finally(() => {
  10. // 无论成功或失败,都执行的逻辑
  11. console.log('请求完成');
  12. });

33. 异步编程的进一步支持

虽然 Promise 已在 ES6 中引入,但 ES6 对异步编程的支持为后续的异步特性(如 async/await)奠定了基础

  • ES5: 异步操作主要依赖回调函数,容易导致“回调地狱”。
  • ES6: 引入 Promise 对象,简化了异步操作的处理,提高了代码的可读性和可维护性。
  1. // ES5 回调示例
  2. function fetchData(callback) {
  3. setTimeout(() => {
  4. callback('Data received');
  5. }, 1000);
  6. }
  7. fetchData(function(data) {
  8. console.log(data);
  9. });
  10. // ES6 Promise 示例
  11. function fetchData() {
  12. return new Promise((resolve, reject) => {
  13. setTimeout(() => {
  14. resolve('Data received');
  15. }, 1000);
  16. });
  17. }
  18. fetchData()
  19. .then(data => console.log(data))
  20. .catch(error => console.error(error));

34. 新增的全局对象方法

  • ES5: 全局对象方法较少,主要包括 parseInt、parseFloat 等。
  • ES6: 增加了一些新的全局对象方法,提升了全局环境的功能。
  1. // globalThis: 提供了一个统一的方式来访问全局对象,无论是在浏览器、Node.js 还是其他环境中。
  2. console.log(globalThis === window); // 在浏览器中为 true
  3. console.log(globalThis === global); // 在 Node.js 中为 true
  4. // isFinite 和 isNaN: ES6 对这两个全局方法进行了改进,更加严格地判断值的有限性和是否为 NaN。
  5. console.log(isFinite('15')); // true
  6. console.log(Number.isFinite('15')); // false
  7. console.log(isNaN('NaN')); // true
  8. console.log(Number.isNaN('NaN')); // false

35. 更好的错误处理

  • ES6: 引入了更丰富的错误类型,如 RangeError、ReferenceError 等,帮助开发者更精确地捕捉和处理不同类型的错误。
    1. try {
    2. throw new RangeError('超出范围');
    3. } catch (error) {
    4. if (error instanceof RangeError) {
    5. console.log('范围错误:', error.message);
    6. } else {
    7. console.log('其他错误:', error.message);
    8. }
    9. }