深入理解ECMAScript 2015:版本特性全解析
作者:佚名 时间:2024-11-18

引言

JavaScript(JS)作为一门轻量级的面向对象编程语言,一直在不断发展和完善。ES6(ECMAScript 2015)是JS的一个重要里程碑,引入了大量新特性和语法改进,极大地提升了开发效率和代码可读性。

ES6版本概述

ES6,也称为ECMAScript 2015,是JS语言的一次重大更新。它不仅修复了旧版本的许多问题,还引入了许多新特性,使得JS更加现代化和强大。

主要特性

<h5>1. let和const

letconst是ES6引入的两个新的变量声明关键字,用于解决var的变量提升问题。

let a = 10;
const b = 20;
2. 箭头函数

箭头函数提供了一种更简洁的函数声明方式。

const sum = (x, y) => x + y;
console.log(sum(1, 2)); // 3
3. 模板字符串

模板字符串可以简化字符串的拼接和处理。

const name = '张三';
const greeting = `你好,${name}!`;
console.log(greeting); // 你好,张三!
4. 解构赋值

解构赋值可以简化从数组或对象中提取数据的操作。

const [x, y] = [1, 2];
const {a, b} = {a: 3, b: 4};
console.log(x, y, a, b); // 1 2 3 4
5. Promise

Promise用于处理异步操作,使代码更加清晰和易于管理。

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  }, 1000);
}).then(result => console.log(result)); // 成功
6. 类(Class)

ES6引入了类的概念,使得面向对象编程更加直观。

class Person {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }
}

const p = new Person('李四');
p.sayHello(); // 你好,我是李四

实际应用

理解ES6的特性不仅有助于提升代码质量,还能在实际项目中解决复杂问题。例如,使用Promise可以更好地处理异步请求,使用解构赋值可以简化数据处理逻辑。

ES6作为JS的一次重要更新,带来了许多实用的新特性和语法改进。掌握这些特性,不仅能提升编程技能,还能在实际开发中事半功倍。希望本文能帮助大家更好地理解和应用ES6。

ECMAScript2015+ 的新特性

let 和 const

新增的块级作用域声明方式。let 用于声明可变变量,const 用于声明常量。

解构赋值

允许从数组或对象中提取值并赋值给变量,简化代码书写。

字符串扩展

模板字符串

新增了模板字符串,使用反引号(``)定义字符串,支持多行字符串和插值表达式。

扩展方法

常用的一些方法如下:

  • String.prototype.repeat():返回一个新字符串,表示将原字符串重复n次。例如:"x".repeat(3) // "xxx"
  • String.prototype.includes():返回布尔值,表示是否找到了参数字符串。例如:"Hello world".includes("world") // true
  • String.prototype.startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。例如:"Hello world".startsWith("Hello") // true
  • String.prototype.endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。例如:"Hello world".endsWith("world") // true

正则的扩展

RegExp 构造函数

当基于 RegExp 构造函数生成正则对象,第一个参数是正则表达式时,第二个参数可以指定修饰符,且修饰符会被第二个参数覆盖。

u 修饰符

u 修饰符用于处理 Unicode 字符,可以正确处理大于 \uFFFF 的 Unicode 字符。添加 u 修饰符后:

  • 使用点字符(.)可以匹配 32 位的 UTF-16 字符
  • 使用大括号包裹的 Unicode 字符可以被正确解析
  • 量词可以正确匹配 32 位的 UTF-16 字符
  • 预定义模式 \w、\W、\d、\D、\s、\S 可以正确匹配 32 位的 UTF-16 字符

y 修饰符

y 修饰符叫做"粘连"(sticky)修饰符,表示从剩余的第一个字符开始匹配。

y 修饰符的设计本意是,让头部匹配的标志^在全局匹配中都有效。

let s = 'aaa_aa_a';
let r1 = /a+/g; // 全局匹配
let r2 = /a+/y; // 粘连匹配
r1.exec(s); // ["aaa"]
r2.exec(s); // ["aaa"]
r1.exec(s); // ["aa"] // g修饰符会继续往后匹配
r2.exec(s); // null // y修饰符要求必须从剩余的第一个字符开始匹配,因为剩余的第一个字符是_,所以返回null

数值的扩展

二进制和八进制表示法

ES6 提供了二进制和八进制数值的新的写法:

  • 二进制用前缀0b(或0B)表示
  • 八进制用前缀0o(或0O)表示
// 二进制
0b111110111 === 503 // true
// 八进制
0o767 === 503 // true

Number.isFinite(), Number.isNaN()

新增了两个方法来检测数值:

  • Number.isFinite()用来检查一个数值是否为有限的
  • Number.isNaN()用来检查一个值是否为 NaN
Number.isFinite(15); // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite(NaN); // false

Number.isNaN(NaN); // true
Number.isNaN(15); // false
Number.isNaN('15'); // false

Number.parseInt(), Number.parseFloat()

ES6 将全局方法parseInt()parseFloat()移植到 Number 对象上:

// 等同于
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true

Number.isInteger()

用来判断一个数值是否为整数:

Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false

Number.EPSILON

表示一个极小的常量,它的值是 JavaScript 能够表示的最小精度:

Number.EPSILON // 2.220446049250313e-16

// 用于浮点数运算的误差检查
0.1 + 0.2 === 0.3 // false
Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON // true

安全整数和 Number.isSafeInteger()

JavaScript 能够准确表示的整数范围在 -2^53 到 2^53 之间。ES6 引入了:

  • Number.MAX_SAFE_INTEGER:最大安全整数
  • Number.MIN_SAFE_INTEGER:最小安全整数
  • Number.isSafeInteger():判断一个整数是否在这个范围内
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_SAFE_INTEGER // -9007199254740991
Number.isSafeInteger(9007199254740991) // true
Number.isSafeInteger(9007199254740992) // false

Math 对象的扩展

ES6 在 Math 对象上新增了多个方法:

  • Math.trunc()去除小数部分
  • Math.sign()判断一个数是正数、负数、还是零
  • Math.cbrt()计算立方根
  • Math.hypot()返回所有参数的平方和的平方根
Math.trunc(4.9) // 4
Math.sign(-5) // -1
Math.cbrt(27) // 3
Math.hypot(3, 4) // 5

指数运算符

引入了**运算符,用来进行指数运算:

2 ** 3 // 8
2 ** 3 ** 2 // 512
let a = 2;
a **= 3; // 等同于 a = a ** 3

函数的扩展

箭头函数

ES6 引入了箭头函数,使用=>定义函数。箭头函数有以下特点:

  1. 更简洁的语法:
// 普通函数
const fun1 = function(x) {
  return x * 2;
}
// 箭头函数
const fun2 = x => x * 2;
  1. 不绑定自己的this:
const obj = {
  data: [],
  init() {
    // 箭头函数中的this指向定义时的对象
    document.addEventListener('click', () => {
      this.data.push('clicked');  // this指向obj
    });
  }};
  1. 不能用作构造函数
  2. 不绑定arguments对象
  3. 不能用作Generator函数

函数参数的默认值

可以直接在参数定义时设置默认值:

function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello')  // Hello World
log('Hello', 'China')  // Hello China

// 解构赋值默认值
function fetch({url, method = 'GET'} = {}) {
  console.log(method);
}
fetch({url: '/api'})  // GET
fetch()  // GET

注意事项:

  1. 参数默认值不是传值的,而是每次都重新计算默认值表达式的值
let count = 1;

function foo(value = count) {
  console.log(value);
}
foo();      // 1
count = 2;
foo();      // 2  // 每次调用时都会重新获取count的值

// 更复杂的例子
function bar(func = () => Date.now()) {
  console.log(func());
}
bar();  // 1709xxxxx1
bar();  // 1709xxxxx2  // 每次调用都会得到新的时间戳
  1. 默认值参数应该是函数的尾参数

rest 参数

rest 参数(形式为...变量名)用于获取函数的多余参数:

// 代替 arguments
function add(...numbers) {
  return numbers.reduce((sum, num) => sum + num, 0);
}
add(2, 3, 4)  // 9
// 结合解构使用
function push(array, ...items) {
  items.forEach(item => {
    array.push(item);
  });
}
let arr = [];
push(arr, 1, 2, 3);  // arr现在是[1, 2, 3]

rest 参数的特点:

  1. 只能作为最后一个参数
  2. 函数的length属性不包括rest参数
  3. 替代了arguments对象,更加灵活
  4. rest参数是一个真正的数组,可以直接使用数组方法

数组的扩展

扩展运算符

扩展运算符(...)将一个数组转为用逗号分隔的参数序列:

console.log(...[1, 2, 3])  // 1 2 3

// 常见用途
// 1. 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1];

// 2. 合并数组
const arr3 = [...arr1, ...arr2];

// 3. 与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
// first: 1
// rest: [2, 3, 4, 5]

Array.from()

将类数组对象和可遍历对象转换为真正的数组:

// 转换类数组对象
const arrayLike = { 
    0: 'a', 
    1: 'b', 
    2: 'c', 
    length: 3 
};
Array.from(arrayLike)  // ['a', 'b', 'c']
// 转换可遍历对象
Array.from('hello')  // ['h', 'e', 'l', 'l', 'o']
// 接受第二个参数,类似于数组的map方法
Array.from([1, 2, 3], x => x * 2)  // [2, 4, 6]

Array.of()

将一组值转换为数组,弥补数组构造函Array()的不足:

Array.of(3)  // [3]
Array.of(1, 2, 3)  // [1, 2, 3]

// 对比Array()构造函数
Array(3)  // [empty × 3]
Array(1, 2, 3)  // [1, 2, 3]

数组实例的 copyWithin()

在当前数组内部,指定位置的成员复制到其他位置:

// array.copyWithin(target, start = 0, end = this.length)
[1, 2, 3, 4, 5].copyWithin(0, 3)  // [4, 5, 3, 4, 5]

数组实例的 find() 和 findIndex()

find()用于找出第一个符合条件的数组成员,findIndex()返回第一个符合条件的数组成员的位置:

const numbers = [1, 4, -5, 10];
numbers.find(n => n < 0)  // -5
numbers.findIndex(n => n < 0)  // 2

数组实例的 fill()

使用给定值,填充一个数组:

new Array(3).fill(7)  // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2)  // ['a', 7, 'c']

数组实例的 entries(),keys() 和 values()

用于遍历数组,它们都返回一个遍历器对象:

const arr = ['a', 'b', 'c'];
for (let index of arr.keys()) {
    console.log(index);  // 0, 1, 2
}
for (let elem of arr.values()) {
    console.log(elem);  // 'a', 'b', 'c'
}
for (let [index, elem] of arr.entries()) {
    console.log(index, elem);  // 0 'a', 1 'b', 2 'c'
}

对象的扩展

属性简写

当属性名与变量名相同时,可以简写:

const name = 'Tom';
const age = 18;
const person = { name, age }; // 等同于 { name: name, age: age }

方法简写

对象方法可以简写,省略function关键字:

const obj = {
  sayHi() {  // 等同于 sayHi: function() 
    console.log('Hi');
  }
}

属性名表达式

支持用表达式作为属性名:

const prop = 'foo';
const obj = {
  [prop]: 'bar',
  [`a${prop}`]: 'hello'
};

Object.assign()

用于对象的合并,将源对象的所有可枚举属性复制到目标对象:

const target = { a: 1 };
const source = { b: 2 };
Object.assign(target, source); // { a: 1, b: 2 }

Object.is()

用于比较两个值是否严格相等:

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

Symbol

Symbol 是 ES6 引入的一种新的原始数据类型,表示独一无二的值。

基本用法

// 创建 Symbol
const s1 = Symbol();
const s2 = Symbol('描述文字');

// Symbol 值是唯一的
Symbol() !== Symbol() // true
Symbol('foo') !== Symbol('foo') // true

// 作为对象属性
const obj = {
  [Symbol('foo')]: 'foo value',
  [Symbol('bar')]: 'bar value'
};

Symbol.for() 和 Symbol.keyFor()

// Symbol.for() 会在全局注册表中搜索键为"foo"的 Symbol
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
s1 === s2 // true
// Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key
Symbol.keyFor(s1) // "foo"

内置的 Symbol 值

ES6 提供了一些内置的 Symbol 值,指向语言内部使用的方法:

// Symbol.iterator
// 对象进行 for...of 循环时,会调用这个方法
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
iterator.next() // { value: 1, done: false }

// Symbol.hasInstance
// instanceof 运算符调用这个方法
class MyArray {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}
[] instanceof MyArray // true
// Symbol.toPrimitive
// 对象转换为原始类型值时调用
const obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number': return 123;
      case 'string': return 'str';
      case 'default': return 'default';
    }
  }
};
主要特点:
  1. Symbol 值是唯一的,可用作对象属性名
  2. Symbol 属性不会出现在常规的对象属性枚举中
  3. 可以用于定义一些非私有但又希望只用于内部的方法
  4. 提供了一些内置的 Symbol 值用于扩展对象的系统层面的功能

Set 和 Map 数据结构

Set

Set 是一个新的数据结构,类似于数组,但成员值都是唯一的:

// 基本用法
const s = new Set();
s.add(1).add(2).add(2); // Set(2) {1, 2}
// 接收数组初始化
const set = new Set([1, 2, 2, 3]); // Set(3) {1, 2, 3}
// 常用方法
set.has(1); // true
set.delete(2); // true
set.size; // 2
set.clear(); // 清空
// 数组去重
const arr = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
// 遍历方法
for (let item of set) {
  console.log(item);
}
set.forEach((value, key) => console.log(value));

WeakSet

WeakSet 结构与 Set 类似,但有两个重要的区别:

  1. 成员只能是对象
  2. 对象都是弱引用,可被垃圾回收机制回收
const ws = new WeakSet();
const obj = {};
ws.add(obj);
ws.has(obj); // true

Map

Map 是一种键值对的集合,类似于对象,但键的范围不限于字符串:

// 基本用法
const m = new Map();
const obj = {p: 'Hello'};
m.set(obj, 'content');
m.get(obj); // 'content'

// 接收数组初始化
const map = new Map([
  ['name', '张三'],
  ['age', 18]
]);
// 常用方法
map.has('name'); // true
map.delete('name'); // true
map.size; // 1
map.clear(); // 清空

// 遍历方法
for (let [key, value] of map) {
  console.log(key, value);
}
map.forEach((value, key) => console.log(key, value));

WeakMap

WeakMap 结构与 Map 类似,但有两个重要的区别:

  1. 只接受对象作为键名
  2. 键名所指向的对象,不计入垃圾回收机制
const wm = new WeakMap();
const key = {};
wm.set(key, 'value');
wm.get(key); // 'value'

主要特点:

  1. Set 用于存储唯一值的集合
  2. Map 提供了真正的"键值对"数据结构
  3. WeakSet 和 WeakMap 都是弱引用,主要用于防止内存泄漏
  4. 这些数据结构都提供了丰富的操作方法和遍历接口

Proxy 和 Reflect

Proxy

Proxy 是一个强大的对象代理机制,它可以拦截并自定义对象的基本操作,比如属性查找、赋值、枚举、函数调用等。通过 Proxy,你可以在这些操作执行前后添加自定义行为。

// 基本用法
const target = {
  name: '张三',
  age: 18
};
const handler = {
  // 拦截对象属性的读取
  get(target, prop) {
    console.log(`访问了${prop}属性`);
    return target[prop];
  },
  // 拦截对象属性的设置
  set(target, prop, value) {
    console.log(`设置${prop}属性为${value}`);
    target[prop] = value;
    return true;
  }
};
const proxy = new Proxy(target, handler);
// 使用代理对象
proxy.name;  // 输出:"访问了name属性"  返回:"张三"
proxy.age = 20;  // 输出:"设置age属性为20"

常用的 Proxy 处理方法

const handler = {
  // 拦截属性的读取
  get(target, prop) {},
  // 拦截属性的设置
  set(target, prop, value) {},
  // 拦截函数的调用
  apply(target, thisArg, argumentsList) {},
  // 拦截 new 操作
  construct(target, args) {},
  // 拦截 Object.defineProperty
  defineProperty(target, prop, descriptor) {},
  // 拦截属性的删除
  deleteProperty(target, prop) {},
  // 拦截获取原型
  getPrototypeOf(target) {},
  // 拦截设置原型
  setPrototypeOf(target, proto) {}
};

Reflect

Reflect 是一个内置的全局对象,它提供了一组统一的方法来操作对象。这些方法是对象操作的底层封装,与 Proxy 处理器的方法一一对应,使得代理对象的操作更加规范和可控。

// 基本用法
const obj = {
  name: '张三'
};
// 读取属性
Reflect.get(obj, 'name');  // '张三'
// 设置属性
Reflect.set(obj, 'age', 18);  // true
// 删除属性
Reflect.deleteProperty(obj, 'age');  // true
// 判断对象是否有某个属性
Reflect.has(obj, 'name');

Proxy 和 Reflect 结合使用

const target = {
  name: '张三',
  age: 18
};
const handler = {
  get(target, prop, receiver) {
    console.log(`获取${prop}属性`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置${prop}属性`);
    return Reflect.set(target, prop, value, receiver);
  }
};
const proxy = new Proxy(target, handler);

主要用途:

  1. Proxy 用于创建对象的代理,实现对象操作的拦截和自定义
  2. Reflect 提供了操作对象的统一 API
  3. 常用于:
    • 数据验证
    • 日志记录
    • 数据绑定
    • 访问控制

Promise 对象

Promise 是异步编程的一种解决方案,比传统的回调函数更加优雅。

基本用法

// 创建 Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    if(/* 成功 */) {
      resolve('成功结果');
    } else {
      reject('失败原因');
    }
  }, 1000);
});
// 使用 Promise
promise
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.log(error);
  })
  .finally(() => {
    console.log('无论成功失败都会执行');
  });

Promise 的状态

Promise 有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

状态一旦改变,就不会再变。

Promise 的常用方法

// Promise.all():所有 Promise 都成功才成功
Promise.all([promise1, promise2])
  .then(([result1, result2]) => {
    console.log(result1, result2);
  });
// Promise.race():返回最快的那个 Promise 的结果
Promise.race([promise1, promise2])
  .then(result => {
    console.log(result);
  });
// 【ES2020】Promise.allSettled():等待所有 Promise 完成
Promise.allSettled([promise1, promise2])
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('成功:', result.value);
      } else {
        console.log('失败:', result.reason);
      }
    });
  });
// 【ES2021】Promise.any():任意一个 Promise 成功就返回
Promise.any([promise1, promise2])
  .then(result => {
    console.log('第一个成功的结果:', result);
  });

实际应用示例

// 封装 AJAX 请求
function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(JSON.parse(xhr.response));
      } else {
        reject(new Error(`请求失败:${xhr.statusText}`));
      }
    };
    xhr.onerror = () => reject(new Error('网络错误'));
    xhr.send();
  });
}
// 使用 async/await 更优雅地处理
async function getData() {
  try {
    const result = await fetchData('api/data');
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}
主要特点:
  1. 链式调用,避免回调地狱
  2. 统一的错误处理机制
  3. 多个 Promise 的组合处理方法
  4. 与 async/await 完美配合

Iterator 和 for...of 循环

Iterator(迭代器)

Iterator 是一种遍历机制,它就像一个指针,指向数据结构的某个位置,并且通过 next() 方法指向下一个位置,直到遍历完所有数据。

// Iterator 的基本实现
function makeIterator(array) {
  let nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}
// 使用迭代器
const it = makeIterator(['a', 'b']);
console.log(it.next()); // { value: "a", done: false }
console.log(it.next()); // { value: "b", done: false }
console.log(it.next()); // { value: undefined, done: true }

原生具备 Iterator 接口的数据结构

// Array
const arr = ['a', 'b', 'c'];
const iter = arr[Symbol.iterator]();
iter.next(); // { value: 'a', done: false }
// String
const str = "hello";
for (let char of str) {
  console.log(char); // h,e,l,l,o
}
// Map
const map = new Map([['name', '张三'], ['age', 18]]);
for (let [key, value] of map) {
  console.log(key, value);
}
// Set
const set = new Set(['a', 'b', 'c']);
for (let value of set) {
  console.log(value);
}

for...of 循环

for...of 循环可以自动遍历具有 Iterator 接口的数据结构:

// 遍历数组
const arr = ['red', 'green', 'blue'];
for (let value of arr) {
  console.log(value);
}
// 与其他遍历方法的对比
const arr = ['a', 'b', 'c'];

// for...in 遍历索引
for (let index in arr) {
  console.log(index); // "0", "1", "2"
}
// for...of 遍历值
for (let value of arr) {
  console.log(value); // "a", "b", "c"
}
// forEach 无法中断
arr.forEach(value => {
  console.log(value);
  // 无法使用 break
});

自定义 Iterator 接口

class Collection {
  constructor() {
    this.items = ['a', 'b', 'c'];
  }
  [Symbol.iterator]() {
    let index = 0;
    const items = this.items;

    return {
      next() {
        return index < items.length ?
          {value: items[index++], done: false} :
          {value: undefined, done: true};
      }
    };
  }
}
const collection = new Collection();
for (let value of collection) {
  console.log(value); // 'a', 'b', 'c'
}

Generator 函数

Generator 函数是一种特殊的函数,可以暂停执行和恢复执行。通过 yield 关键字,实现函数的分段执行。

基本用法

// 定义 Generator 函数
function* numberGenerator() {
  yield 1;
  yield 2;
  return 3;
}
// 使用 Generator
const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }

yield 表达式

function* calculateNumbers() {
  const result1 = yield 1 + 1;  // result1 是下一次 next 传入的值
  console.log('第一次计算结果:', result1);  
  const result2 = yield 2 + 2;
  console.log('第二次计算结果:', result2);
}
const calc = calculateNumbers();
console.log(calc.next());      // { value: 2, done: false }
console.log(calc.next(100));   // 打印 "第一次计算结果: 100"
                              // { value: 4, done: false }
console.log(calc.next(200));   // 打印 "第二次计算结果: 200"
                              // { value: undefined, done: true }

实际应用示例

// 1. 实现迭代器
function* createIterator(array) {
  for(let item of array) {
    yield item;
  }
}
const iterator = createIterator(['a', 'b', 'c']);
console.log(iterator.next()); // { value: 'a', done: false }

// 2. 状态机
function* toggle() {
  while(true) {
    yield '开';
    yield '关';
  }}
const light = toggle();
console.log(light.next().value); // '开'
console.log(light.next().value); // '关'
console.log(light.next().value); // '开'

Generator 函数的特点

  1. 函数名前有*
  2. 函数体内使用yield表达式
  3. 调用后返回遍历器对象
  4. 可以通过next()方法依次遍历
  5. 可以配合 Promise 实现异步控制流程

async 函数

async 函数是 Generator 函数的语法糖,使异步操作更简单直观。它返回一个 Promise 对象。

基本用法

// 基础示例
async function getData() {
  try {
    const response = await fetch('api/data');
    const data = await response.json();
    return data;
  } catch (error) {
    console.log('错误:', error);
  }}
// 调用方式
getData().then(data => {
  console.log(data);
});

async 函数的几种写法

// 函数声明
async function foo() {}
// 函数表达式
const foo = async function() {}
// 对象方法
const obj = {
  async foo() {}
}
// 箭头函数
const foo = async () => {}
// Class 方法
class Storage {
  async getData() {}
}
实际应用示例
// 1. 并行请求
async function getMultipleData() {
  // Promise.all 并行执行多个请求
  const [users, posts] = await Promise.all([
    fetch('api/users').then(r => r.json()),
    fetch('api/posts').then(r => r.json())
  ]);  
  return { users, posts };
}
// 2. 错误处理
async function fetchData() {
  try {
    const response = await fetch('api/data');
    if (!response.ok) {
      throw new Error('请求失败');
    }
    return await response.json();
  } catch (error) {
    console.error('出错了:', error);
    // 可以返回默认值或重新抛出错误
    return [];
  }
}
// 3. 顺序请求
async function getOrderDetails() {
  // 每个请求依赖前一个请求的结果
  const user = await fetch('api/user').then(r => r.json());
  const orders = await fetch(`api/orders/${user.id}`).then(r => r.json());
  const details = await fetch(`api/details/${orders[0].id}`).then(r => r.json());
  return details;
}

async 函数的特点

  1. 内置执行器,不需要手动执行
  2. 更好的语义,async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果
  3. 返回值是 Promise,比 Generator 函数的返回值是 Iterator 对象方便
  4. await 命令后面,可以是 Promise 对象或原始类型的值
  5. 错误处理方便,可以用 try-catch 捕获同步和异步的错误

Class

Class 是 ES6 引入的定义类的新方式,让对象原型的写法更加清晰、更像面向对象编程的语法。

基本用法

class Person {
  // 构造方法
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }  
  // 实例方法
  sayHello() {
    console.log(`你好,我是${this.name}`);
  }  
  // getter
  get info() {
    return `${this.name}, ${this.age}岁`;
  }  
  // setter
  set age(value) {
    this._age = value < 0 ? 0 : value;
  }  
  // 静态方法
  static create(name, age) {
    return new Person(name, age);
  }}
// 使用类
const person = new Person('张三', 20);
person.sayHello(); // 你好,我是张三
console.log(person.info); // 张三, 20岁

继承

class Student extends Person {
  constructor(name, age, grade) {
    // 调用父类构造函数
    super(name, age);
    this.grade = grade;
  }  
  study() {
    console.log(`${this.name}正在学习`);
  }
  
  // 重写父类方法
  sayHello() {
    super.sayHello();
    console.log(`我是${this.grade}年级的学生`);
  }
}
const student = new Student('小明', 15, '初三');
student.sayHello();
// 你好,我是小明
// 我是初三年级的学生

【ES2022】私有属性和方法

class Account {
  // 私有属性(新提案)
  #balance = 0;  
  // 私有方法(新提案)
  #validate(amount) {
    return amount > 0 && amount <= this.#balance;
  }  
  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      return true;
    }
    return false;
  }  
  withdraw(amount) {
    if (this.#validate(amount)) {
      this.#balance -= amount;
      return true;
    }
    return false;
  }
  
  get balance() {
    return this.#balance;
  }
}
const account = new Account();
account.deposit(100);
console.log(account.balance); // 100
console.log(account.#balance); // 语法错误

Class 的特点

  1. 类声明不会提升,必须先声明后使用
  2. 类内部默认启用严格模式
  3. 类的所有方法都是不可枚举的
  4. 必须使用 new 调用类构造函数
  5. 类的方法内部的 this 默认指向类的实例

Module

ES6 模块化规范,通过 import 和 export 实现模块的导入导出。

导出(export)

// math.js
// 单个导出
export const add = (x, y) => x + y;
export const subtract = (x, y) => x - y;

// 默认导出
export default class Calculator {
  // ...
}
// 统一导出
const multiply = (x, y) => x * y;
const divide = (x, y) => x / y;
export { multiply, divide };

导入(import)

// main.js
// 导入单个或多个
import { add, subtract } from './math.js';

// 导入默认导出
import Calculator from './math.js';

// 导入全部并重命名
import * as MathUtils from './math.js';

// 重命名导入
import { add as addition } from './math.js';

// 同时导入默认和非默认
import Calculator, { add, subtract } from './math.js';

【ES2020】动态导入

// 按需加载模块
button.addEventListener('click', async () => {
  const module = await import('./dialog.js');
  module.showDialog();
});

【ES2020】在 HTML 中使用模块

<!-- 使用 type="module" -->
<script type="module">
  import { add } from './math.js';
  console.log(add(2, 3));
</script>
<!-- 导入外部模块脚本 -->
<script type="module" src="main.js"></script>

Module 的特点

  1. 自动采用严格模式
  2. 独立的模块作用域
  3. 单例模式:模块只会被加载一次
  4. 支持循环依赖
  5. 异步加载,不会阻塞主线程
    匿名评论
  • 评论
人参与,条评论
相关下载
H5游戏