Appearance
ES6 及 ES6+ 新特性详解
概述
ECMAScript 是 JavaScript 的标准规范。ES6(ECMAScript 2015)是一次重大更新,引入了大量新特性。此后,ECMA 国际组织决定每年发布一个新版本,统称为 ES6+ 或 ES.Next。
一、ES6 (ES2015) 新特性
ES6 是 JavaScript 历史上最重要的更新之一,引入了许多现代 JavaScript 开发的核心特性。
1. let 和 const 声明
javascript
// let - 块级作用域,可重新赋值
let name = 'Alice'
name = 'Bob' // 允许
// const - 块级作用域,常量,不可重新赋值
const PI = 3.14159
// PI = 3.14; // TypeError
// 块级作用域示例
if (true) {
let blockScoped = 'only in this block'
var functionScoped = 'function scoped'
}
console.log(functionScoped) // 'function scoped'
// console.log(blockScoped); // ReferenceError
// 暂时性死区 (Temporal Dead Zone)
console.log(x) // undefined (var 提升)
var x = 10
// console.log(y); // ReferenceError (TDZ)
let y = 20特点:
let和const具有块级作用域- 不存在变量提升(存在暂时性死区)
- 不允许重复声明
const声明的引用类型可以修改属性,但不能重新赋值
2. 箭头函数 (Arrow Functions)
javascript
// 基本语法
const add = (a, b) => a + b
const square = x => x * x
const greet = () => 'Hello!'
// 带函数体的写法
const sum = (a, b) => {
const result = a + b
return result
}
// 返回对象字面量需要加括号
const createUser = (name, age) => ({ name, age })
// this 绑定特性
const obj = {
name: 'Alice',
// 普通函数:this 取决于调用方式
greetRegular: function () {
console.log(this.name)
},
// 箭头函数:this 继承自外层作用域
greetArrow: () => {
console.log(this.name) // undefined (继承外层 this)
},
// 正确用法
greetCorrect: function () {
const arrow = () => console.log(this.name)
arrow() // 'Alice'
}
}特点:
- 更简洁的语法
- 没有自己的
this,继承外层作用域的this - 没有
arguments对象 - 不能用作构造函数(不能使用
new) - 没有
prototype属性
3. 模板字符串 (Template Literals)
javascript
const name = 'Alice'
const age = 25
// 基本用法
const greeting = `Hello, ${name}! You are ${age} years old.`
// 多行字符串
const html = `
<div class="card">
<h2>${name}</h2>
<p>Age: ${age}</p>
</div>
`
// 标签模板 (Tagged Templates)
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : ''
return result + str + value
}, '')
}
const result = highlight`Hello ${name}, you are ${age}`
// "Hello <mark>Alice</mark>, you are <mark>25</mark>"
// 原始字符串
const raw = String.raw`Line 1\nLine 2` // 包含实际的 \n 字符4. 解构赋值 (Destructuring)
javascript
// 数组解构
const [first, second, ...rest] = [1, 2, 3, 4, 5]
console.log(first) // 1
console.log(rest) // [3, 4, 5]
// 默认值
const [a = 10, b = 20] = [5]
console.log(a, b) // 5, 20
// 交换变量
let x = 1,
y = 2
;[x, y] = [y, x]
// 对象解构
const person = { name: 'Alice', age: 25, city: 'NYC' }
const { name, age, country = 'USA' } = person
// 重命名
const { name: userName, age: userAge } = person
// 嵌套解构
const company = {
name: 'Tech Corp',
address: {
city: 'San Francisco',
country: 'USA'
}
}
const {
address: { city }
} = company
// 函数参数解构
function printUser({ name, age }) {
console.log(`${name} is ${age} years old`)
}
printUser(person)
// 函数参数默认值 + 解构
function createUser({ name = 'Anonymous', age = 0 } = {}) {
return { name, age }
}5. 展开运算符和剩余参数 (Spread & Rest)
javascript
// 展开运算符 (Spread) - 数组
const arr1 = [1, 2, 3]
const arr2 = [...arr1, 4, 5] // [1, 2, 3, 4, 5]
const arr3 = [0, ...arr1] // [0, 1, 2, 3]
// 数组复制(浅拷贝)
const copy = [...arr1]
// 合并数组
const merged = [...arr1, ...arr2]
// 展开运算符 - 对象
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3, ...obj1 } // { c: 3, a: 1, b: 2 }
const obj3 = { ...obj1, b: 3 } // { a: 1, b: 3 } (后者覆盖)
// 函数调用时展开
const numbers = [1, 2, 3]
Math.max(...numbers) // 3
// 剩余参数 (Rest)
function sum(...args) {
return args.reduce((total, n) => total + n, 0)
}
sum(1, 2, 3, 4) // 10
// 结合解构
const [first, ...others] = [1, 2, 3, 4]
const { a, ...remaining } = { a: 1, b: 2, c: 3 }6. 默认参数 (Default Parameters)
javascript
// 基本用法
function greet(name = 'World') {
return `Hello, ${name}!`
}
greet() // 'Hello, World!'
greet('Alice') // 'Hello, Alice!'
// 表达式作为默认值
function add(a, b = a * 2) {
return a + b
}
add(5) // 15 (5 + 10)
// 函数作为默认值
function getDefault() {
return 'default value'
}
function test(value = getDefault()) {
console.log(value)
}
// 必填参数模式
function required(paramName) {
throw new Error(`Parameter ${paramName} is required`)
}
function createUser(name = required('name')) {
return { name }
}7. 类 (Classes)
javascript
// 类声明
class Person {
// 构造函数
constructor(name, age) {
this.name = name
this.age = age
}
// 实例方法
greet() {
console.log(`Hello, I'm ${this.name}`)
}
// Getter
get info() {
return `${this.name}, ${this.age} years old`
}
// Setter
set age(value) {
if (value > 0) {
this._age = value
}
}
// 静态方法
static createAnonymous() {
return new Person('Anonymous', 0)
}
// 静态属性 (ES2022 正式支持)
static species = 'Homo sapiens'
}
// 继承
class Employee extends Person {
constructor(name, age, position) {
super(name, age) // 必须先调用 super
this.position = position
}
// 重写父类方法
greet() {
super.greet() // 调用父类方法
console.log(`I work as a ${this.position}`)
}
}
// 类表达式
const Animal = class {
constructor(name) {
this.name = name
}
}8. 模块化 (Modules)
javascript
// ========== math.js ==========
// 命名导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
// ...
}
// 默认导出(每个模块只能有一个)
export default class MathUtils {
static square(x) {
return x * x;
}
}
// ========== main.js ==========
// 命名导入
import { PI, add, Calculator } from './math.js';
// 默认导入
import MathUtils from './math.js';
// 混合导入
import MathUtils, { PI, add } from './math.js';
// 全部导入
import * as MathModule from './math.js';
MathModule.add(1, 2);
// 重命名导入
import { add as sum } from './math.js';
// 动态导入 (ES2020)
async function loadModule() {
const module = await import('./math.js');
module.add(1, 2);
}
// 重新导出
export { add, PI } from './math.js';
export * from './utils.js';
export { default } from './math.js';9. Promise
javascript
// 创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true
if (success) {
resolve('Operation succeeded')
} else {
reject(new Error('Operation failed'))
}
}, 1000)
})
// 使用 Promise
promise
.then(result => {
console.log(result)
return 'Next step'
})
.then(nextResult => {
console.log(nextResult)
})
.catch(error => {
console.error(error)
})
.finally(() => {
console.log('Cleanup')
})
// Promise 静态方法
const p1 = Promise.resolve('immediate value')
const p2 = Promise.reject('error')
// Promise.all - 所有成功才成功
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(error => console.error(error))
// Promise.race - 返回最先完成的结果
Promise.race([promise1, promise2]).then(result => console.log(result))
// 封装回调为 Promise
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err)
else resolve(result)
})
})
}
}10. 迭代器与生成器 (Iterators & Generators)
javascript
// 迭代器协议
const iterator = {
data: [1, 2, 3],
index: 0,
[Symbol.iterator]() {
return {
next: () => {
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false }
}
return { done: true }
}
}
}
}
// 生成器函数
function* numberGenerator() {
yield 1
yield 2
yield 3
}
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: false }
console.log(gen.next()) // { value: undefined, done: true }
// 无限序列
function* infiniteSequence() {
let i = 0
while (true) {
yield i++
}
}
// yield* 委托给另一个生成器
function* generator1() {
yield 1
yield 2
}
function* generator2() {
yield* generator1()
yield 3
}
// 生成器作为可迭代对象
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i
}
}
for (const num of range(1, 5)) {
console.log(num)
}
// 异步生成器 (ES2018)
async function* asyncGenerator() {
const data = await fetch('/api/data')
yield data.json()
}11. for...of 循环
javascript
// 遍历数组
const arr = ['a', 'b', 'c']
for (const item of arr) {
console.log(item)
}
// 遍历字符串
for (const char of 'Hello') {
console.log(char)
}
// 遍历 Map
const map = new Map([
['a', 1],
['b', 2]
])
for (const [key, value] of map) {
console.log(key, value)
}
// 遍历 Set
const set = new Set([1, 2, 3])
for (const item of set) {
console.log(item)
}
// 与 for...in 的区别
const obj = { a: 1, b: 2 }
for (const key in obj) {
console.log(key) // 'a', 'b' (遍历键名)
}
// for...of 不能直接遍历普通对象
// 使用 break 和 continue
for (const item of arr) {
if (item === 'b') break
console.log(item)
}12. 新的数据结构
Map
javascript
const map = new Map()
// 设置值
map.set('name', 'Alice')
map.set(1, 'number key')
map.set({ id: 1 }, 'object key')
// 获取值
map.get('name') // 'Alice'
// 检查存在
map.has('name') // true
// 删除
map.delete('name')
// 大小
map.size
// 清空
map.clear()
// 初始化
const map2 = new Map([
['a', 1],
['b', 2]
])
// 遍历
for (const [key, value] of map) {
console.log(key, value)
}
map.forEach((value, key) => {
console.log(key, value)
})
// 转换为数组
const arr = [...map]
const keys = [...map.keys()]
const values = [...map.values()]
const entries = [...map.entries()]Set
javascript
const set = new Set()
// 添加值
set.add(1)
set.add(2)
set.add(2) // 重复值会被忽略
// 检查存在
set.has(1) // true
// 删除
set.delete(1)
// 大小
set.size
// 数组去重
const unique = [...new Set([1, 2, 2, 3, 3])] // [1, 2, 3]
// 集合运算
const setA = new Set([1, 2, 3])
const setB = new Set([2, 3, 4])
// 并集
const union = new Set([...setA, ...setB]) // {1, 2, 3, 4}
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x))) // {2, 3}
// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))) // {1}WeakMap 和 WeakSet
javascript
// WeakMap - 键必须是对象,键是弱引用
const weakMap = new WeakMap()
let obj = { name: 'test' }
weakMap.set(obj, 'value')
obj = null // 键值对会被垃圾回收
// WeakSet - 值必须是对象,值是弱引用
const weakSet = new WeakSet()
let obj2 = { id: 1 }
weakSet.add(obj2)
obj2 = null // 会被垃圾回收13. Symbol
javascript
// 创建唯一的 Symbol
const sym1 = Symbol()
const sym2 = Symbol('description') // 描述仅用于调试
const sym3 = Symbol('description') // sym2 !== sym3
// 全局 Symbol 注册表
const globalSym1 = Symbol.for('app.id')
const globalSym2 = Symbol.for('app.id') // 相同的 Symbol
globalSym1 === globalSym2 // true
// 获取 Symbol 的 key
Symbol.keyFor(globalSym1) // 'app.id'
// 作为对象属性键
const obj = {
[Symbol('secret')]: 'private data',
name: 'public'
}
// 内置 Symbol
const iterableObj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false }
}
return { done: true }
}
}
}
}
// 其他内置 Symbol
// Symbol.toStringTag
// Symbol.toPrimitive
// Symbol.hasInstance
// Symbol.isConcatSpreadable
// Symbol.species
// Symbol.match, Symbol.replace, Symbol.search, Symbol.split14. Proxy 和 Reflect
javascript
// Proxy - 代理对象
const target = {
name: 'Alice',
age: 25
}
const handler = {
get(obj, prop) {
console.log(`Getting ${prop}`)
return obj[prop]
},
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`)
obj[prop] = value
return true
},
has(obj, prop) {
return prop in obj
},
deleteProperty(obj, prop) {
console.log(`Deleting ${prop}`)
delete obj[prop]
return true
}
}
const proxy = new Proxy(target, handler)
proxy.name // Getting name
proxy.age = 26 // Setting age to 26
// 可撤销的 Proxy
const { proxy, revoke } = Proxy.revocable(target, handler)
revoke() // 撤销后访问会报错
// Reflect - 与 Proxy handler 方法一一对应
const obj = { name: 'test' }
Reflect.get(obj, 'name') // 'test'
Reflect.set(obj, 'age', 25) // true
Reflect.has(obj, 'name') // true
Reflect.deleteProperty(obj, 'age')
Reflect.ownKeys(obj) // ['name']
// 实际应用:数据绑定、验证、日志
function createReactiveObject(target, callback) {
return new Proxy(target, {
set(obj, prop, value) {
const oldValue = obj[prop]
obj[prop] = value
callback(prop, value, oldValue)
return true
}
})
}15. 新增的数组方法
javascript
// Array.from() - 从类数组或可迭代对象创建数组
Array.from('hello') // ['h', 'e', 'l', 'l', 'o']
Array.from([1, 2, 3], x => x * 2) // [2, 4, 6]
Array.from({ length: 5 }, (_, i) => i) // [0, 1, 2, 3, 4]
// Array.of() - 创建数组
Array.of(1, 2, 3) // [1, 2, 3]
Array.of(7) // [7] (与 new Array(7) 不同)
// fill() - 填充数组
;[1, 2, 3].fill(0) // [0, 0, 0]
;[1, 2, 3].fill(0, 1) // [1, 0, 0]
;[1, 2, 3, 4].fill(0, 1, 3) // [1, 0, 0, 4]
// find() 和 findIndex()
const arr = [1, 2, 3, 4, 5]
arr.find(x => x > 3) // 4
arr.findIndex(x => x > 3) // 3
// copyWithin() - 数组内部复制
;[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
// entries(), keys(), values()
for (const [index, value] of ['a', 'b'].entries()) {
console.log(index, value)
}
for (const index of ['a', 'b'].keys()) {
console.log(index)
}
for (const value of ['a', 'b'].values()) {
console.log(value)
}
// includes() (ES2016)
;[1, 2, 3].includes(2) // true
;[1, 2, NaN].includes(NaN) // true16. 新增的字符串方法
javascript
// includes(), startsWith(), endsWith()
'Hello World'.includes('World') // true
'Hello World'.startsWith('Hello') // true
'Hello World'.endsWith('World') // true
'Hello World'.startsWith('World', 6) // true
// repeat()
'abc'.repeat(3) // 'abcabcabc'
'ab'.repeat(0) // ''
// padStart(), padEnd() (ES2017)
'5'.padStart(3, '0') // '005'
'5'.padEnd(3, '0') // '500'
'hello'.padStart(10) // ' hello'
// trimStart(), trimEnd() (ES2019)
' hello '.trimStart() // 'hello '
' hello '.trimEnd() // ' hello'
// at() (ES2022)
'hello'.at(0) // 'h'
'hello'.at(-1) // 'o' (支持负索引)17. 新增的对象特性
javascript
// 对象属性简写
const name = 'Alice'
const age = 25
const person = { name, age } // { name: 'Alice', age: 25 }
// 方法简写
const obj = {
greet() {
return 'Hello'
}
}
// 计算属性名
const key = 'dynamic'
const obj2 = {
[key]: 'value',
[`${key}Key`]: 'another value'
}
// Object.assign()
const merged = Object.assign({}, obj1, obj2)
// Object.is() - 严格相等比较
Object.is(NaN, NaN) // true (=== 为 false)
Object.is(-0, 0) // false (=== 为 true)
Object.is('hello', 'hello') // true
// Object.keys(), values(), entries()
Object.keys({ a: 1, b: 2 }) // ['a', 'b']
Object.values({ a: 1, b: 2 }) // [1, 2]
Object.entries({ a: 1, b: 2 }) // [['a', 1], ['b', 2]]
// Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptors({ a: 1 })
// { a: { value: 1, writable: true, enumerable: true, configurable: true } }
// Object.setPrototypeOf() 和 Object.getPrototypeOf()
const proto = {
greet() {
return 'Hello'
}
}
Object.setPrototypeOf(obj, proto)
Object.getPrototypeOf(obj) // proto
// Object.fromEntries() (ES2019)
Object.fromEntries([
['a', 1],
['b', 2]
]) // { a: 1, b: 2 }18. 尾调用优化 (Tail Call Optimization)
javascript
// 正常递归 - 可能栈溢出
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1) // 不是尾调用
}
// 尾调用优化版本
function factorialOptimized(n, acc = 1) {
if (n <= 1) return acc
return factorialOptimized(n - 1, n * acc) // 尾调用
}
// 尾调用的条件:
// 1. 函数的最后一步是调用另一个函数
// 2. 调用函数后不再有其他操作
// 3. 返回值只依赖被调用函数的返回值二、ES2016 (ES7) 新特性
1. Array.prototype.includes()
javascript
const arr = [1, 2, 3, NaN]
arr.includes(2) // true
arr.includes(4) // false
arr.includes(NaN) // true (与 indexOf 不同)
// 与 indexOf 的区别
;[NaN].indexOf(NaN) // -1
;[NaN].includes(NaN) // true
// 第二个参数:起始位置
;[1, 2, 3].includes(1, 1) // false2. 指数运算符 (**)
javascript
// 基本用法
2 ** 3 // 8
3 ** 2 // 9
10 ** -1 // 0.1
// 右结合
2 ** (3 ** 2) // 512 (2 ** (3 ** 2))
// 赋值运算符
let a = 2
a **= 3 // a = 8
// 与 Math.pow 的区别
Math.pow(2, 3) // 8 (功能相同,但 ** 更简洁)三、ES2017 (ES8) 新特性
1. async/await
javascript
// 基本用法
async function fetchUser() {
try {
const response = await fetch('/api/user')
const user = await response.json()
return user
} catch (error) {
console.error('Error:', error)
}
}
// 并行执行
async function fetchAll() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
])
return { users, posts }
}
// async 函数返回 Promise
async function greet() {
return 'Hello'
}
greet().then(msg => console.log(msg))
// 错误处理
async function handleErrors() {
try {
const result = await riskyOperation()
return result
} catch (error) {
console.error(error)
throw error // 重新抛出
}
}
// 循环中的 await
async function processItems(items) {
for (const item of items) {
await processItem(item)
}
}
// 立即执行 async 函数
;(async () => {
const data = await fetchData()
console.log(data)
})()2. Object.values() 和 Object.entries()
javascript
const obj = { a: 1, b: 2, c: 3 }
Object.values(obj) // [1, 2, 3]
Object.entries(obj) // [['a', 1], ['b', 2], ['c', 3]]
// 实际应用
const prices = { apple: 1, banana: 2, orange: 3 }
// 计算总价
const total = Object.values(prices).reduce((sum, price) => sum + price, 0)
// 转换为 Map
const map = new Map(Object.entries(prices))
// 过滤对象
const filtered = Object.fromEntries(
Object.entries(prices).filter(([_, price]) => price > 1)
)3. 字符串填充方法
javascript
// padStart() - 左填充
'5'.padStart(3, '0') // '005'
'123'.padStart(5, '0') // '00123'
'hello'.padStart(10, '.') // '.....hello'
// padEnd() - 右填充
'5'.padEnd(3, '0') // '500'
'hello'.padEnd(10, '.') // 'hello.....'
// 实际应用:格式化
function formatNumber(num, length = 5) {
return String(num).padStart(length, '0')
}
formatNumber(123) // '00123'
// 格式化货币
function formatCurrency(amount) {
return '$' + amount.toFixed(2).padStart(8)
}4. Object.getOwnPropertyDescriptors()
javascript
const obj = {
name: 'Alice',
get fullName() {
return `${this.name} Smith`
}
}
const descriptors = Object.getOwnPropertyDescriptors(obj)
// {
// name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
// fullName: { get: [Function: get fullName], set: undefined, enumerable: true, configurable: true }
// }
// 实际应用:正确复制对象(包括 getter/setter)
function clone(obj) {
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
}5. 函数参数尾逗号
javascript
// 允许在参数列表末尾添加逗号
function foo(
param1,
param2,
param3 // 允许尾逗号
) {
// ...
}
const obj = {
name: 'Alice',
age: 25,
city: 'NYC' // 允许尾逗号
}
const arr = [
1,
2,
3 // 允许尾逗号
]6. SharedArrayBuffer 和 Atomics
javascript
// SharedArrayBuffer - 共享内存
const sharedBuffer = new SharedArrayBuffer(1024)
const sharedArray = new Int32Array(sharedBuffer)
// Atomics - 原子操作
Atomics.add(sharedArray, 0, 5) // 原子加
Atomics.load(sharedArray, 0) // 原子读取
Atomics.store(sharedArray, 0, 10) // 原子存储
Atomics.compareExchange(sharedArray, 0, 10, 20) // 条件交换
// 用于 Web Workers 之间的数据共享四、ES2018 (ES9) 新特性
1. 异步迭代 (Asynchronous Iteration)
javascript
// 异步生成器
async function* asyncGenerator() {
const urls = ['/api/1', '/api/2', '/api/3']
for (const url of urls) {
const response = await fetch(url)
yield response.json()
}
}
// for await...of 循环
async function processAsyncData() {
for await (const data of asyncGenerator()) {
console.log(data)
}
}
// 异步迭代器协议
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
i: 0,
next() {
if (this.i < 3) {
return Promise.resolve({ value: this.i++, done: false })
}
return Promise.resolve({ done: true })
}
}
}
}
;(async () => {
for await (const num of asyncIterable) {
console.log(num)
}
})()2. 对象剩余属性和展开属性
javascript
// 对象剩余属性
const { a, ...rest } = { a: 1, b: 2, c: 3 }
console.log(rest) // { b: 2, c: 3 }
// 对象展开属性
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3, ...obj1 } // { c: 3, a: 1, b: 2 }
// 合并对象
const merged = { ...obj1, ...obj2 }
// 浅拷贝
const copy = { ...obj1 }
// 实际应用:移除属性
function removeProperty(obj, key) {
const { [key]: _, ...rest } = obj
return rest
}3. Promise.prototype.finally()
javascript
fetch('/api/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
console.log('Request completed')
// 无论成功或失败都会执行
// 用于清理工作
})
// 实际应用
async function withLoading(promise) {
showLoading()
try {
return await promise
} finally {
hideLoading()
}
}4. 正则表达式增强
javascript
// s 标志 (dotAll) - . 匹配任意字符包括换行符
;/hello.world/s.test('hello\nworld') // true
// 命名捕获组
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const match = dateRegex.exec('2024-01-15')
match.groups.year // '2024'
match.groups.month // '01'
match.groups.day // '15'
// 后行断言 (Lookbehind)
// 正向后行断言 (?<=...)
const priceRegex = /(?<=\$)\d+/
priceRegex.exec('Price: $100') // ['100']
// 负向后行断言 (?<!...)
const regex = /(?<!\$)\d+/
regex.exec('Price: $100') // null
regex.exec('Count: 100') // ['100']
// Unicode 属性转义
// \p{...} 匹配 Unicode 属性
;/\p{Script=Greek}+/u.test('π') // true
;/\p{Number}+/u.test('①②③') // true
;/\p{Emoji}/u.test('😊') // true五、ES2019 (ES10) 新特性
1. Array.prototype.flat() 和 flatMap()
javascript
// flat() - 扁平化数组
;[1, [2, 3]].flat() // [1, 2, 3]
;[1, [2, [3, 4]]].flat() // [1, 2, [3, 4]]
;[1, [2, [3, 4]]].flat(2) // [1, 2, 3, 4]
;[1, [2, [3, 4]]].flat(Infinity) // [1, 2, 3, 4]
// flatMap() - 映射后扁平化
const sentences = ['Hello World', 'Hi There']
const words = sentences.flatMap(s => s.split(' '))
// ['Hello', 'World', 'Hi', 'There']
// 实际应用:过滤并映射
const numbers = [1, 2, 3, 4, 5]
const result = numbers.flatMap(n => (n % 2 === 0 ? [n, n * 2] : []))
// [2, 4, 4, 8]2. Object.fromEntries()
javascript
// 从键值对数组创建对象
const entries = [
['a', 1],
['b', 2],
['c', 3]
]
Object.fromEntries(entries) // { a: 1, b: 2, c: 3 }
// Map 转对象
const map = new Map([
['name', 'Alice'],
['age', 25]
])
Object.fromEntries(map) // { name: 'Alice', age: 25 }
// URL 参数解析
const params = new URLSearchParams('name=Alice&age=25')
Object.fromEntries(params) // { name: 'Alice', age: '25' }
// 对象转换
const obj = { a: 1, b: 2, c: 3 }
const transformed = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key.toUpperCase(), value * 2])
)
// { A: 2, B: 4, C: 6 }3. 字符串 trim 方法增强
javascript
// trimStart() / trimLeft()
' hello '.trimStart() // 'hello '
' hello '.trimLeft() // 'hello '
// trimEnd() / trimRight()
' hello '.trimEnd() // ' hello'
' hello '.trimRight() // ' hello'4. Symbol.prototype.description
javascript
const sym = Symbol('My description')
sym.description // 'My description'
const sym2 = Symbol()
sym2.description // undefined
const sym3 = Symbol.for('global')
sym3.description // 'global'5. 可选的 catch 绑定
javascript
// 之前
try {
// ...
} catch (error) {
console.log('Error occurred')
}
// 现在 - 不需要 error 参数
try {
// ...
} catch {
console.log('Error occurred')
}
// 实际应用
function safeParse(json) {
try {
return JSON.parse(json)
} catch {
return null
}
}6. Array.prototype.sort() 稳定性
javascript
// ES2019 保证 sort() 是稳定排序
const students = [
{ name: 'Alice', grade: 90 },
{ name: 'Bob', grade: 90 },
{ name: 'Charlie', grade: 85 }
]
// 稳定排序:相同分数的元素保持原有顺序
students.sort((a, b) => b.grade - a.grade)
// Alice 仍在 Bob 之前7. Function.prototype.toString() 修订
javascript
function foo() {
/* comment */
}
foo.toString() // 返回完整的源代码,包括注释和空格
// 内置函数
Array.isArray.toString() // "function isArray() { [native code] }"六、ES2020 (ES11) 新特性
1. 可选链操作符 (?.)
javascript
const user = {
name: 'Alice',
address: {
city: 'NYC'
}
}
// 之前
const city = user && user.address && user.address.city
// 现在
const city2 = user?.address?.city
// 函数调用
user.greet?.() // 如果 greet 存在则调用
// 数组访问
const first = arr?.[0]
// 与 nullish coalescing 结合
const name = user?.profile?.name ?? 'Anonymous'
// 各种形式
obj?.prop // 访问属性
obj?.[expr] // 访问动态属性
arr?.[index] // 访问数组元素
func?.(args) // 调用函数2. 空值合并运算符 (??)
javascript
// 只在 null 或 undefined 时使用默认值
const value = null ?? 'default' // 'default'
const value2 = undefined ?? 'default' // 'default'
const value3 = '' ?? 'default' // '' (空字符串不是 null/undefined)
const value4 = 0 ?? 'default' // 0
// 与 || 的区别
const a = 0 || 'default' // 'default' (0 是 falsy)
const b = 0 ?? 'default' // 0 (0 不是 null/undefined)
const c = '' || 'default' // 'default'
const d = '' ?? 'default' // ''
// 短路求值
const x = null ?? console.log('evaluated') // 打印 'evaluated'
const y = 'value' ?? console.log('not evaluated') // 不打印
// 与可选链结合
const name = user?.profile?.name ?? 'Anonymous'3. BigInt
javascript
// 创建 BigInt
const big1 = 9007199254740991n // 后缀 n
const big2 = BigInt(9007199254740991)
const big3 = BigInt('9007199254740991')
// 运算
const result = 9007199254740991n + 1n
const multiplied = big1 * 2n
// 比较
9007199254740991n === 9007199254740991 // true
9007199254740991n == 9007199254740991 // true
// 类型转换
Number(10n) // 10
BigInt(10) // 10n
// 不能混合运算
// 10n + 5; // TypeError
10n + BigInt(5) // 正确
// JSON 序列化问题
// JSON.stringify({ value: 10n }); // TypeError
// 解决方案
JSON.stringify({ value: 10n.toString() }) // '{"value":"10"}'4. globalThis
javascript
// 统一的全局对象访问方式
// 浏览器: window
// Node.js: global
// Web Worker: self
// 现在统一使用
globalThis.setTimeout // 在任何环境都有效
// 之前需要检测环境
const getGlobal = () => {
if (typeof self !== 'undefined') return self
if (typeof window !== 'undefined') return window
if (typeof global !== 'undefined') return global
throw new Error('No global object found')
}5. Promise.allSettled()
javascript
// 等待所有 Promise 完成(无论成功或失败)
const promises = [
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
]
Promise.allSettled(promises).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value)
} else {
console.log('Failed:', result.reason)
}
})
})
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
// 实际应用:批量请求
async function fetchAllUrls(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
)
return {
succeeded: results.filter(r => r.status === 'fulfilled').map(r => r.value),
failed: results.filter(r => r.status === 'rejected').map(r => r.reason)
}
}6. String.prototype.matchAll()
javascript
const text = 'test1test2test3'
const regex = /t(e)(st(\d?))/g
// 之前需要循环调用 exec
// 现在可以使用 matchAll
const matches = [...text.matchAll(regex)]
// [
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2test3'],
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2test3'],
// ['test3', 'e', 'st3', '3', index: 10, input: 'test1test2test3']
// ]
// 实际应用:提取所有匹配
const html = '<div>content1</div><div>content2</div>'
const divRegex = /<div>(.*?)<\/div>/g
const contents = [...html.matchAll(divRegex)].map(m => m[1])
// ['content1', 'content2']7. 动态导入 (Dynamic Import)
javascript
// 静态导入
// import { add } from './math.js';
// 动态导入 - 返回 Promise
async function loadModule() {
const module = await import('./math.js')
return module.add(1, 2)
}
// 条件加载
if (condition) {
import('./feature.js').then(module => {
module.init()
})
}
// 按需加载
button.addEventListener('click', async () => {
const module = await import('./heavy-module.js')
module.doSomething()
})
// 加载多个模块
async function loadAll() {
const [math, utils] = await Promise.all([
import('./math.js'),
import('./utils.js')
])
return { math, utils }
}8. import.meta
javascript
// 获取模块元信息
console.log(import.meta.url) // 当前模块的 URL
// 在 Node.js 中
console.log(import.meta.url) // file:///path/to/module.js
// 判断是否在主模块
if (import.meta.url === `file://${process.argv[1]}`) {
// 是主模块
}
// 动态导入相对路径
async function loadRelative(path) {
const baseUrl = new URL('.', import.meta.url)
return import(new URL(path, baseUrl).href)
}9. for...in 顺序标准化
javascript
// ES2020 规范了 for...in 的遍历顺序
const obj = {
a: 1,
b: 2,
2: 'two',
1: 'one'
}
for (const key in obj) {
console.log(key)
}
// 规范顺序: '1', '2', 'a', 'b'
// 数字键按数值升序,字符串键按添加顺序七、ES2021 (ES12) 新特性
1. 逻辑赋值运算符
javascript
// ||= 或赋值
let a = false
a ||= 'default' // a = 'default'
// 等价于
a = a || 'default'
// &&= 与赋值
let b = 'value'
b &&= 'new value' // b = 'new value'
let c = false
c &&= 'new value' // c 仍然是 false
// ??= 空值合并赋值
let d = null
d ??= 'default' // d = 'default'
let e = 'value'
e ??= 'default' // e 仍然是 'value'
// 实际应用
const config = {
timeout: 0,
retries: null
}
config.timeout ??= 3000 // 不改变(0 不是 null/undefined)
config.retries ??= 3 // retries = 3
config.name ||= 'default' // name = 'default'2. 数字分隔符
javascript
// 使用下划线分隔数字,提高可读性
const billion = 1_000_000_000
const bytes = 0xff_ff_ff_ff
const bits = 0b1010_0001_1000_0101
const octal = 0o1234_5670
const fractional = 1_000.000_001
// 可以在任意位置使用
const num = 1_2_3_4_5 // 12345
// 实际应用
const budget = 1_000_000
const mask = 0b1111_1111_1111_1111
const hexColor = 0xff_ff_ff3. String.prototype.replaceAll()
javascript
// 替换所有匹配项
'hello world'.replaceAll('l', 'L') // 'heLLo worLd'
// 之前需要使用正则表达式全局标志
'hello world'.replace(/l/g, 'L')
// 使用正则表达式
'aabbcc'.replaceAll(/b/g, 'X') // 'aaXXcc'
// 特殊字符需要转义
'1.2.3'.replaceAll('.', '-') // '1-2-3'4. Promise.any()
javascript
// 返回第一个成功的 Promise
const promises = [
Promise.reject('error 1'),
Promise.resolve('success 1'),
Promise.resolve('success 2')
]
Promise.any(promises)
.then(result => console.log(result)) // 'success 1'
.catch(error => console.log('All failed'))
// 所有都失败时抛出 AggregateError
Promise.any([Promise.reject('error 1'), Promise.reject('error 2')]).catch(
error => {
console.log(error instanceof AggregateError) // true
console.log(error.errors) // ['error 1', 'error 2']
}
)
// 实际应用:请求多个服务器,使用最快响应的
async function fetchFromMultiple(urls) {
return Promise.any(urls.map(url => fetch(url).then(r => r.json())))
}5. WeakRef 和 FinalizationRegistry
javascript
// WeakRef - 弱引用对象
let obj = { data: 'large data' }
const weakRef = new WeakRef(obj)
// 获取对象(可能已被垃圾回收)
const dereferenced = weakRef.deref()
if (dereferenced) {
console.log(dereferenced.data)
}
obj = null // 允许垃圾回收
// FinalizationRegistry - 对象被垃圾回收时的回调
const registry = new FinalizationRegistry(heldValue => {
console.log(`Object with id ${heldValue} was garbage collected`)
})
function createObject(id) {
const obj = { data: 'some data' }
registry.register(obj, id)
return obj
}
const temp = createObject('obj-1')
// 当 temp 被垃圾回收时,会调用回调
// 实际应用:缓存
class Cache {
constructor() {
this.cache = new Map()
this.registry = new FinalizationRegistry(key => {
this.cache.delete(key)
})
}
set(key, value) {
const ref = new WeakRef(value)
this.cache.set(key, ref)
this.registry.register(value, key)
}
get(key) {
const ref = this.cache.get(key)
return ref?.deref()
}
}八、ES2022 (ES13) 新特性
1. 顶层 await
javascript
// 在模块顶层直接使用 await
// 不需要包装在 async 函数中
// 之前
// async function init() {
// const data = await fetch('/api/data');
// return data.json();
// }
// 现在
const response = await fetch('/api/data')
const data = await response.json()
console.log(data)
// 动态加载模块
const module = await import('./feature.js')
// 条件加载
const config = await (process.env.NODE_ENV === 'production'
? import('./config.prod.js')
: import('./config.dev.js'))
// 数据库连接
const db = await connectDB()2. 类的私有字段和方法
javascript
class Person {
// 私有字段(以 # 开头)
#name
#age
// 私有静态字段
static #count = 0
constructor(name, age) {
this.#name = name
this.#age = age
Person.#count++
}
// 私有方法
#validate() {
return this.#age > 0
}
// 公共方法访问私有成员
getName() {
if (this.#validate()) {
return this.#name
}
}
// 私有 getter/setter
get #info() {
return `${this.#name}, ${this.#age}`
}
// 静态私有方法
static #getCount() {
return Person.#count
}
// 静态块
static {
console.log('Person class initialized')
}
}
// 外部无法访问私有成员
const person = new Person('Alice', 25)
// person.#name; // SyntaxError
// person.#validate(); // SyntaxError3. 类的静态块
javascript
class Database {
static connection
static config = {
host: 'localhost',
port: 5432
}
// 静态初始化块
static {
// 执行复杂的静态初始化逻辑
try {
this.connection = createConnection(this.config)
console.log('Database connected')
} catch (error) {
console.error('Connection failed:', error)
}
}
static async query(sql) {
return this.connection.execute(sql)
}
}
// 多个静态块按顺序执行
class Example {
static {
console.log('Block 1')
}
static {
console.log('Block 2')
}
}4. Array.prototype.at()
javascript
const arr = ['a', 'b', 'c', 'd', 'e']
// 正向索引
arr.at(0) // 'a'
arr.at(2) // 'c'
// 负向索引(从末尾开始)
arr.at(-1) // 'e'
arr.at(-2) // 'd'
// 与传统方式的对比
arr[arr.length - 1] // 'e'
arr.at(-1) // 'e' (更简洁)
// 字符串也支持
'hello'.at(-1) // 'o'
// 实际应用
function getLastItem(array) {
return array.at(-1)
}5. Object.hasOwn()
javascript
const obj = {
name: 'Alice',
age: 25
}
// 新方法
Object.hasOwn(obj, 'name') // true
Object.hasOwn(obj, 'age') // true
Object.hasOwn(obj, 'toString') // false
// 与 Object.prototype.hasOwnProperty.call 的区别
// 更安全,不依赖原型链
const obj2 = Object.create(null)
obj2.name = 'test'
// obj2.hasOwnProperty('name'); // TypeError
Object.hasOwn(obj2, 'name') // true
// 与 in 操作符的区别
'name' in obj // true
'toString' in obj // true (检查原型链)
Object.hasOwn(obj, 'toString') // false (只检查自有属性)6. Error Cause
javascript
// 错误链 - 记录错误原因
try {
await fetchData()
} catch (error) {
throw new Error('Failed to process data', { cause: error })
}
// 捕获并查看原因
try {
process()
} catch (error) {
console.log(error.message) // 'Failed to process data'
console.log(error.cause) // 原始错误
console.log(error.cause.message) // 'Network error'
}
// 实际应用:错误追踪
async function saveUser(user) {
try {
await validateUser(user)
await saveToDatabase(user)
} catch (error) {
throw new Error('User save failed', {
cause: error,
user: user.id
})
}
}九、ES2023 (ES14) 新特性
1. 数组非破坏性方法
javascript
const arr = [3, 1, 4, 1, 5, 9]
// toSorted() - 返回排序后的新数组
const sorted = arr.toSorted() // [1, 1, 3, 4, 5, 9]
console.log(arr) // [3, 1, 4, 1, 5, 9] (原数组不变)
// toReversed() - 返回反转后的新数组
const reversed = arr.toReversed() // [9, 5, 1, 4, 1, 3]
// toSpliced() - 返回删除/插入元素后的新数组
const spliced = arr.toSpliced(2, 2, 'a', 'b')
// [3, 1, 'a', 'b', 5, 9]
// with() - 返回修改指定索引后的新数组
const modified = arr.with(0, 'first') // ['first', 1, 4, 1, 5, 9]
// 链式调用
const result = arr.toSorted().toReversed().with(0, 'biggest')
// 对比破坏性方法
arr.sort() // 原数组被修改
arr.toSorted() // 原数组不变2. 从尾部查找数组元素
javascript
const arr = [1, 2, 3, 4, 3, 2, 1]
// findLast() - 从后向前查找
arr.findLast(x => x > 2) // 3
// findLastIndex() - 从后向前查找索引
arr.findLastIndex(x => x > 2) // 4
// 对比
arr.find(x => x > 2) // 3 (第一个)
arr.findLast(x => x > 2) // 3 (最后一个)
arr.findIndex(x => x > 2) // 2
arr.findLastIndex(x => x > 2) // 4
// 实际应用
const tasks = [
{ id: 1, status: 'done' },
{ id: 2, status: 'pending' },
{ id: 3, status: 'done' }
]
const lastDone = tasks.findLast(t => t.status === 'done')3. Hashbang 语法
javascript
#!/usr/bin/env node
// 文件开头的 #! 被识别为 hashbang 注释
// 用于指定脚本的解释器
// example.js
#!/usr/bin/env node
console.log('Hello from script!');
// 可以直接执行:./example.js4. Symbol 作为 WeakMap 的键
javascript
// 之前 WeakMap 的键只能是对象
// 现在 Symbol 也可以作为键
const weak = new WeakMap()
const key = Symbol('key')
weak.set(key, 'value')
weak.get(key) // 'value'
// 实际应用:私有数据存储
const privateData = new WeakMap()
class MyClass {
constructor() {
const key = Symbol('private')
privateData.set(this, { key, secret: 'hidden' })
}
}十、ES2024 (ES15) 新特性
1. 数组分组方法
javascript
const students = [
{ name: 'Alice', grade: 'A' },
{ name: 'Bob', grade: 'B' },
{ name: 'Charlie', grade: 'A' },
{ name: 'David', grade: 'C' }
]
// Object.groupBy() - 返回对象
const byGrade = Object.groupBy(students, student => student.grade)
// {
// A: [{ name: 'Alice', grade: 'A' }, { name: 'Charlie', grade: 'A' }],
// B: [{ name: 'Bob', grade: 'B' }],
// C: [{ name: 'David', grade: 'C' }]
// }
// Map.groupBy() - 返回 Map
const byGradeMap = Map.groupBy(students, student => student.grade)
// 实际应用
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const { even, odd } = Object.groupBy(numbers, n =>
n % 2 === 0 ? 'even' : 'odd'
)2. Promise.withResolvers()
javascript
// 之前创建 Promise 的方式
let resolve, reject
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
// 新方法 - 更简洁
const { promise, resolve, reject } = Promise.withResolvers()
// 实际应用:事件转 Promise
function waitForEvent(emitter, eventName) {
const { promise, resolve, reject } = Promise.withResolvers()
emitter.once(eventName, data => {
resolve(data)
})
emitter.once('error', error => {
reject(error)
})
return promise
}3. String.prototype.isWellFormed() 和 toWellFormed()
javascript
// 检查字符串是否包含代理对
const valid = 'Hello 😊'
const invalid = 'Hello \uD83D' // 不完整的代理对
valid.isWellFormed() // true
invalid.isWellFormed() // false
// 转换为格式正确的字符串
invalid.toWellFormed() // 'Hello �' (替换为替换字符)
// 实际应用:处理用户输入
function processText(text) {
if (!text.isWellFormed()) {
text = text.toWellFormed()
}
// 安全处理文本
}4. Atomics.waitAsync()
javascript
// 异步等待共享内存的变化
const sharedBuffer = new SharedArrayBuffer(4)
const view = new Int32Array(sharedBuffer)
// 异步等待
async function waitForValue() {
const result = Atomics.waitAsync(view, 0, 0)
if (result.async) {
await result.value
console.log('Value changed!')
}
}
// 用于跨 Worker 通信5. 正则表达式 v 标志
javascript
// v 标志 - Unicode 字符串模式
const regex = /[\p{Emoji}]/v
// 支持集合操作
// 差集
;/[\p{Decimal_Number}--[0-9]]/v // 匹配非 ASCII 数字
// 交集
;/[\p{ASCII}&&\p{Letter}]/v // 匹配 ASCII 字母
// 并集
;/[[\p{ASCII}][\p{Emoji}]]/v // 匹配 ASCII 或 Emoji十一、ES6+ 特性速查表
| 版本 | 年份 | 主要特性 |
|---|---|---|
| ES6 | 2015 | let/const、箭头函数、类、模块、Promise、解构、展开运算符、Map/Set、Proxy、Symbol |
| ES7 | 2016 | Array.includes()、指数运算符(**) |
| ES8 | 2017 | async/await、Object.values/entries、字符串填充、尾逗号 |
| ES9 | 2018 | 异步迭代、对象剩余/展开、Promise.finally()、正则增强 |
| ES10 | 2019 | flat/flatMap、Object.fromEntries、trimStart/End、可选catch |
| ES11 | 2020 | 可选链(?.)、空值合并(??)、BigInt、globalThis、动态import、allSettled |
| ES12 | 2021 | 逻辑赋值、数字分隔符、replaceAll、Promise.any、WeakRef |
| ES13 | 2022 | 顶层await、私有字段、静态块、at()、hasOwn()、Error Cause |
| ES14 | 2023 | toSorted/toReversed/toSpliced/with、findLast、Hashbang |
| ES15 | 2024 | groupBy、Promise.withResolvers、isWellFormed、正则v标志 |
十二、最佳实践建议
1. 变量声明
javascript
// 优先使用 const
const PI = 3.14159
const config = { timeout: 3000 }
// 需要重新赋值时使用 let
let count = 0
count++
// 避免使用 var2. 异步编程
javascript
// 优先使用 async/await
async function fetchData() {
try {
const response = await fetch('/api/data')
return response.json()
} catch (error) {
console.error(error)
}
}
// 并行任务使用 Promise.all
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()])
// 容错场景使用 allSettled
const results = await Promise.allSettled(tasks)3. 对象和数组操作
javascript
// 使用解构提取数据
const { name, age } = user
const [first, ...rest] = items
// 使用展开运算符复制
const newObj = { ...obj, newProp: 'value' }
const newArr = [...arr, newItem]
// 使用可选链安全访问
const city = user?.address?.city
// 使用空值合并提供默认值
const name = user?.name ?? 'Anonymous'4. 模块化
javascript
// 使用 ES 模块
export function add(a, b) {
return a + b
}
import { add } from './math.js'
// 动态导入大型模块
const heavy = await import('./heavy-module.js')5. 类的设计
javascript
class UserService {
#apiUrl
#cache = new Map()
constructor(apiUrl) {
this.#apiUrl = apiUrl
}
async getUser(id) {
if (this.#cache.has(id)) {
return this.#cache.get(id)
}
const user = await this.#fetchUser(id)
this.#cache.set(id, user)
return user
}
async #fetchUser(id) {
const response = await fetch(`${this.#apiUrl}/users/${id}`)
return response.json()
}
}总结
ES6 及后续版本的更新使 JavaScript 成为更现代、更强大的编程语言。主要改进包括:
- 更好的作用域控制:let/const 提供块级作用域
- 更简洁的语法:箭头函数、解构、展开运算符
- 面向对象增强:类、私有字段、静态块
- 异步编程改进:Promise、async/await、异步迭代
- 模块化支持:ES 模块、动态导入
- 更好的数据处理:Map/Set、数组方法增强
- 更安全的代码:可选链、空值合并、Proxy
- 更好的开发者体验:数字分隔符、模板字符串
建议开发者持续关注 ECMAScript 的最新发展,合理使用新特性来提高代码质量和开发效率。