ECMAScript 2019(ES10)
Optional catch binding
catch
子句可以省略参数,当不需要使用错误对象时更简洁。
javascript
{
// 传统写法 - 必须提供参数
function isValidJSONOld(text) {
try {
JSON.parse(text)
return true
} catch (error) { // 即使不使用 error 也必须声明
return false
}
}
// ES10 新写法 - 可以省略参数
function isValidJSON(text) {
try {
JSON.parse(text)
return true
} catch { // 省略错误参数
return false
}
}
const validJson = '{ "key1": "value1", "key2": "value2" }'
const invalidJson = '{ key1: value1 }'
console.log(isValidJSON(validJson)) // true
console.log(isValidJSON(invalidJson)) // false
// 实际应用:功能检测
function hasLocalStorage() {
try {
localStorage.setItem('test', 'test')
localStorage.removeItem('test')
return true
} catch {
return false
}
}
// 检查是否支持某个 API
function supportsWebP() {
try {
return document.createElement('canvas')
.toDataURL('image/webp')
.indexOf('data:image/webp') === 0
} catch {
return false
}
}
}
Array.prototype.flat()
按指定深度递归地将所有子数组连接,并返回一个新数组。
javascript
{
// 基本用法
const arr1 = [1, 2, [3, 4]]
const arr2 = [1, 2, [3, 4, [5, 6]]]
const arr3 = [1, 2, [3, 4, [5, 6, [7, 8]]]]
console.log(arr1.flat()) // [1, 2, 3, 4]
console.log(arr2.flat()) // [1, 2, 3, 4, [5, 6]] (默认深度为1)
console.log(arr2.flat(2)) // [1, 2, 3, 4, 5, 6]
console.log(arr3.flat(Infinity)) // [1, 2, 3, 4, 5, 6, 7, 8] (完全扁平化)
// 处理稀疏数组
const sparse = [1, 2, , 4, [5, , 7]]
console.log(sparse.flat()) // [1, 2, 4, 5, 7] (移除空槽)
// 实际应用:数据结构扁平化
const categories = [
['EF', ['JavaScript', 'TypeScript', 'React']],
['BE', ['Node.js', 'Python', 'Java']],
['DB', ['MySQL', 'MongoDB']]
]
const allTechnologies = categories
.map(([category, techs]) => techs)
.flat()
console.log(allTechnologies)
// ['JavaScript', 'TypeScript', 'React', 'Node.js', 'Python', 'Java', 'MySQL', 'MongoDB']
// 处理嵌套的用户权限
const userRoles = [
'user',
['admin', ['read', 'write', ['delete', 'modify']]],
'guest'
]
const flatRoles = userRoles.flat(3)
console.log(flatRoles) // ['user', 'admin', 'read', 'write', 'delete', 'modify', 'guest']
}
Array.prototype.flatMap()
首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。
javascript
{
const numbers = [1, 2, 3, 4]
// 对比 map() 和 flatMap()
console.log(numbers.map(x => [x * 2])) // [[2], [4], [6], [8]]
console.log(numbers.flatMap(x => [x * 2])) // [2, 4, 6, 8]
// 等价于 map().flat(),但 flatMap 更高效
console.log(numbers.map(x => [x * 2]).flat()) // [2, 4, 6, 8]
// 处理多层嵌套
console.log(numbers.flatMap(x => [[x * 2]])) // [[2], [4], [6], [8]]
// 实际应用:字符串分词
const sentences = ['Hello world', 'How are you', 'Fine thanks']
const words = sentences.flatMap(sentence => sentence.split(' '))
console.log(words) // ['Hello', 'world', 'How', 'are', 'you', 'Fine', 'thanks']
// 处理异构数据
const data = [
{ type: 'user', items: ['Alice', 'Bob'] },
{ type: 'admin', items: ['Charlie'] },
{ type: 'guest', items: [] }
]
const allUsers = data.flatMap(group => group.items)
console.log(allUsers) // ['Alice', 'Bob', 'Charlie']
// 条件性展开
const mixedData = [1, 2, 3, 4, 5]
const result = mixedData.flatMap(x =>
x % 2 === 0 ? [x, x * 2] : [x] // 偶数展开为两个值
)
console.log(result) // [1, 2, 4, 3, 4, 8, 5]
// 过滤并转换
const items = ['apple', '', 'banana', null, 'cherry']
const validItems = items.flatMap(item =>
item && item.length > 0 ? [item.toUpperCase()] : []
)
console.log(validItems) // ['APPLE', 'BANANA', 'CHERRY']
}
Function.prototype.toString() 修订
toString()
方法现在返回精确的源代码,包括空格和注释。
javascript
{
// 用户定义的函数
function myFunction() {
// 这是注释
console.log('Hello World')
}
console.log(myFunction.toString())
// function myFunction() {
// // 这是注释
// console.log('Hello World')
// }
// 箭头函数
const arrowFn = (x, y) => x + y
console.log(arrowFn.toString()) // (x, y) => x + 1
// 内置函数
console.log(parseInt.toString()) // function parseInt() { [native code] }
console.log(Math.max.toString()) // function max() { [native code] }
// 绑定函数
const boundFn = myFunction.bind(null)
console.log(boundFn.toString()) // function () { [native code] }
// 异步函数
async function asyncFn() {
await Promise.resolve()
}
console.log(asyncFn.toString())
// async function asyncFn() {
// await Promise.resolve()
// }
// 生成器函数
function* generatorFn() {
yield 1
yield 2
}
console.log(generatorFn.toString())
// function* generatorFn() {
// yield 1
// yield 2
// }
// 实际应用:代码分析和调试
function analyzeFunction(fn) {
const source = fn.toString()
return {
isAsync: source.includes('async'),
isGenerator: source.includes('function*'),
isArrow: source.includes('=>'),
lineCount: source.split('\n').length,
hasComments: source.includes('//') || source.includes('/*')
}
}
console.log(analyzeFunction(myFunction))
// { isAsync: false, isGenerator: false, isArrow: false, lineCount: 4, hasComments: true }
}
Well-formed JSON.stringify
修复 JSON.stringify()
对于某些 Unicode 字符的处理。
javascript
{
// ES10 之前的问题:不正确处理代理对
console.log(JSON.stringify('\uD83D\uDE0E')) // "😎" (正确)
// 孤立的代理字符现在能正确处理
console.log(JSON.stringify('\uD800')) // "\\ud800"
console.log(JSON.stringify('\uDFFF')) // "\\udfff"
// 混合的正常字符和代理字符
const mixedString = 'Hello \uD83D\uDE0E World \uD800'
console.log(JSON.stringify(mixedString)) // "Hello 😎 World \\ud800"
// 实际应用:安全的数据序列化
function safeStringify(obj) {
try {
return JSON.stringify(obj)
} catch (error) {
console.error('JSON.stringify failed:', error)
return null
}
}
// 处理包含各种 Unicode 字符的对象
const unicodeData = {
emoji: '🚀✨🎉',
chinese: '你好世界',
arabic: 'مرحبا بالعالم',
surrogate: '\uD83D\uDE0E',
invalid: '\uD800'
}
console.log(safeStringify(unicodeData))
// {"emoji":"🚀✨🎉","chinese":"你好世界","arabic":"مرحبا بالعالم","surrogate":"😎","invalid":"\\ud800"}
}
Object.fromEntries()
将键值对列表转换为对象,是 Object.entries()
的逆操作。
javascript
{
// 基本用法
const entries = [
['name', 'Alice'],
['age', 25],
['city', 'New York']
]
const person = Object.fromEntries(entries)
console.log(person) // { name: 'Alice', age: 25, city: 'New York' }
// 与 Object.entries() 的对称性
const original = { a: 1, b: 2, c: 3 }
const reconstructed = Object.fromEntries(Object.entries(original))
console.log(reconstructed) // { a: 1, b: 2, c: 3 }
// 从 Map 创建对象
const map = new Map([
['foo', 'bar'],
['baz', 42]
])
const objFromMap = Object.fromEntries(map)
console.log(objFromMap) // { foo: 'bar', baz: 42 }
// 数组转对象
const indexedArray = [
['0', 'first'],
['1', 'second'],
['2', 'third']
]
const objFromArray = Object.fromEntries(indexedArray)
console.log(objFromArray) // { '0': 'first', '1': 'second', '2': 'third' }
// 实际应用:数据转换和过滤
const rawData = {
name: 'John',
age: 30,
password: 'secret123',
email: 'john@example.com',
internal_id: 12345
}
// 过滤敏感数据
const publicData = Object.fromEntries(
Object.entries(rawData).filter(([key]) =>
!key.startsWith('password') && !key.startsWith('internal_')
)
)
console.log(publicData) // { name: 'John', age: 30, email: 'john@example.com' }
// 转换数据格式
const scores = {
math: 85,
science: 92,
english: 78,
history: 88
}
const gradeMap = {
A: score => score >= 90,
B: score => score >= 80,
C: score => score >= 70,
D: score => score >= 60,
F: score => score < 60
}
const grades = Object.fromEntries(
Object.entries(scores).map(([subject, score]) => {
const grade = Object.keys(gradeMap).find(g => gradeMap[g](score))
return [subject, grade]
})
)
console.log(grades) // { math: 'B', science: 'A', english: 'C', history: 'B' }
// URL 查询参数解析
function parseQuery(queryString) {
const params = new URLSearchParams(queryString)
return Object.fromEntries(params)
}
const query = 'name=Alice&age=25&city=New%20York'
console.log(parseQuery(query)) // { name: 'Alice', age: '25', city: 'New York' }
}
Symbol.prototype.description
提供 Symbol 的描述字符串,用于调试目的。
javascript
{
// 创建带描述的 Symbol
const sym1 = Symbol('debug info')
const sym2 = Symbol('user-id')
const sym3 = Symbol() // 无描述
console.log(sym1.description) // 'debug info'
console.log(sym2.description) // 'user-id'
console.log(sym3.description) // undefined
// Symbol 的唯一性不受描述影响
console.log(Symbol('same') === Symbol('same')) // false
console.log(sym1 === sym1) // true
// 实际应用:调试和日志
class EventEmitter {
constructor() {
this.events = new Map()
}
on(eventType, callback) {
const symbol = Symbol(`event-${eventType}`)
this.events.set(symbol, { type: eventType, callback })
return symbol
}
off(symbol) {
if (this.events.has(symbol)) {
console.log(`Removing event: ${symbol.description}`)
this.events.delete(symbol)
}
}
listEvents() {
const events = []
for (const [symbol, data] of this.events) {
events.push({
symbol: symbol.description,
type: data.type
})
}
return events
}
}
const emitter = new EventEmitter()
const clickHandler = emitter.on('click', () => {})
const keyHandler = emitter.on('keypress', () => {})
console.log(emitter.listEvents())
// [
// { symbol: 'event-click', type: 'click' },
// { symbol: 'event-keypress', type: 'keypress' }
// ]
// 私有属性标识
const PRIVATE_PROPS = {
userId: Symbol('private-user-id'),
apiKey: Symbol('private-api-key'),
internalState: Symbol('private-internal-state')
}
class User {
constructor(id, name) {
this[PRIVATE_PROPS.userId] = id
this.name = name
}
getDebugInfo() {
const props = Object.getOwnPropertySymbols(this)
return props.map(prop => ({
description: prop.description,
value: typeof this[prop]
}))
}
}
const user = new User(123, 'Alice')
console.log(user.getDebugInfo())
// [{ description: 'private-user-id', value: 'number' }]
}
String.prototype.trimStart() / trimEnd()
分别移除字符串开头和结尾的空白字符。
javascript
{
const str = ' Hello World! '
// 新方法
console.log(str.trimStart()) // 'Hello World! '
console.log(str.trimEnd()) // ' Hello World!'
console.log(str.trim()) // 'Hello World!'
// 别名(向后兼容)
console.log(str.trimLeft()) // 'Hello World! ' (trimStart 的别名)
console.log(str.trimRight()) // ' Hello World!' (trimEnd 的别名)
// 验证别名关系
console.log(String.prototype.trimLeft.name === 'trimStart') // true
console.log(String.prototype.trimRight.name === 'trimEnd') // true
// 处理不同类型的空白字符
const whitespaceStr = '\t\n Hello\t\n '
console.log(whitespaceStr.trimStart()) // 'Hello\t\n '
console.log(whitespaceStr.trimEnd()) // '\t\n Hello'
// 实际应用:代码格式化
function formatCode(code) {
return code
.split('\n')
.map(line => line.trimEnd()) // 移除行尾空白
.join('\n')
.trimStart() // 移除开头空行
}
const messyCode = `
function hello() {
console.log('Hello');
}
`
console.log(formatCode(messyCode))
// function hello() {
// console.log('Hello');
// }
// 解析用户输入
function parseUserInput(input) {
const lines = input.split('\n')
return lines
.map(line => line.trimEnd()) // 移除每行末尾空格
.filter(line => line.trimStart() !== '') // 过滤空行
.map(line => line.trimStart()) // 移除每行开头空格
}
const userInput = `
First item
Second item
Third item
`
console.log(parseUserInput(userInput))
// ['First item', 'Second item', 'Third item']
// 处理配置文件
function parseConfig(configText) {
const config = {}
const lines = configText.split('\n')
for (const line of lines) {
const trimmed = line.trim()
if (trimmed && !trimmed.startsWith('#')) {
const [key, ...valueParts] = trimmed.split('=')
if (key && valueParts.length > 0) {
config[key.trimEnd()] = valueParts.join('=').trimStart()
}
}
}
return config
}
const configFile = `
# Database configuration
host = localhost
port = 5432
database = myapp
# user = admin
timeout = 30
`
console.log(parseConfig(configFile))
// { host: 'localhost', port: '5432', database: 'myapp', timeout: '30' }
}