Skip to content

现代 JavaScript 特性

随着 ECMAScript 标准的不断发展,JavaScript 语言持续引入新的特性和改进。这些现代 JavaScript 特性使开发者能够编写更简洁、更高效、更易维护的代码。

ES2016 (ES7) 特性

Array.prototype.includes()

includes() 方法用来判断数组是否包含某个元素,返回布尔值。

javascript
let fruits = ["苹果", "香蕉", "橙子"];

console.log(fruits.includes("香蕉"));  // true
console.log(fruits.includes("葡萄"));  // false

// 检查第二个参数指定的索引位置之后是否包含元素
console.log(fruits.includes("苹果", 1)); // false

// 对于 NaN 的处理
let numbers = [1, 2, NaN];
console.log(numbers.includes(NaN)); // true

指数运算符 (**)

指数运算符提供了更简洁的幂运算语法。

javascript
console.log(2 ** 3);  // 8
console.log(3 ** 2);  // 9

// 与 Math.pow() 等价
console.log(2 ** 3 === Math.pow(2, 3)); // true

// 支持赋值运算符
let num = 2;
num **= 3;
console.log(num); // 8

ES2017 (ES8) 特性

async/await

async/await 提供了更优雅的异步编程方式。

javascript
// 使用 Promise
function fetchData() {
    return fetch('/api/data')
        .then(response => response.json())
        .then(data => {
            console.log(data);
            return data;
        })
        .catch(error => {
            console.error(error);
        });
}

// 使用 async/await
async function fetchDataAsync() {
    try {
        let response = await fetch('/api/data');
        let data = await response.json();
        console.log(data);
        return data;
    } catch (error) {
        console.error(error);
    }
}

// 并行执行多个异步操作
async function fetchMultipleData() {
    try {
        let [users, posts, comments] = await Promise.all([
            fetch('/api/users').then(r => r.json()),
            fetch('/api/posts').then(r => r.json()),
            fetch('/api/comments').then(r => r.json())
        ]);
        
        return { users, posts, comments };
    } catch (error) {
        console.error('获取数据失败:', error);
    }
}

Object.values() 和 Object.entries()

这两个方法提供了更方便的对象遍历方式。

javascript
let user = {
    name: "张三",
    age: 25,
    job: "开发者"
};

// Object.values() 返回对象值的数组
console.log(Object.values(user)); 
// ["张三", 25, "开发者"]

// Object.entries() 返回对象键值对的数组
console.log(Object.entries(user));
// [["name", "张三"], ["age", 25], ["job", "开发者"]]

// 实际应用示例
// 将对象转换为查询字符串
function objectToQueryString(obj) {
    return Object.entries(obj)
        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        .join('&');
}

let params = { name: "张三", age: 25, city: "北京" };
console.log(objectToQueryString(params));
// "name=%E5%BC%A0%E4%B8%89&age=25&city=%E5%8C%97%E4%BA%AC"

String padding

字符串填充方法用于在字符串的开始或结尾添加指定字符。

javascript
// padStart() 在字符串开头填充
console.log('5'.padStart(2, '0'));     // "05"
console.log('99'.padStart(4, '0'));    // "0099"
console.log('hello'.padStart(10));     // "     hello"

// padEnd() 在字符串结尾填充
console.log('5'.padEnd(2, '0'));       // "50"
console.log('99'.padEnd(4, '0'));      // "9900"
console.log('hello'.padEnd(10));       // "hello     "

// 实际应用:格式化时间显示
function formatTime(hours, minutes, seconds) {
    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}

console.log(formatTime(9, 5, 7)); // "09:05:07"

Object.getOwnPropertyDescriptors()

获取对象所有自有属性的描述符。

javascript
let obj = {
    name: "张三",
    get age() {
        return 25;
    }
};

let descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// {
//   name: {value: "张三", writable: true, enumerable: true, configurable: true},
//   age: {get: ƒ, set: undefined, enumerable: true, configurable: true}
// }

// 可用于正确复制对象(包括 getter 和 setter)
let copied = Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
);

ES2018 (ES9) 特性

异步迭代

异步迭代允许我们使用 for-await-of 循环遍历异步可迭代对象。

javascript
// 创建异步可迭代对象
async function* asyncNumbers() {
    let i = 0;
    while (i < 3) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        yield i++;
    }
}

// 使用 for-await-of 遍历
async function printAsyncNumbers() {
    for await (let num of asyncNumbers()) {
        console.log(num);
    }
}

// 实际应用:逐行读取文件
async function* readLines(file) {
    let lines = file.split('\n');
    for (let line of lines) {
        yield line;
    }
}

async function processFile() {
    let fileContent = "第一行\n第二行\n第三行";
    for await (let line of readLines(fileContent)) {
        console.log(line);
    }
}

Promise.finally()

finally() 方法用于指定无论 Promise 对象最后状态如何都会执行的回调函数。

javascript
function fetchData() {
    return fetch('/api/data')
        .then(response => response.json())
        .catch(error => {
            console.error('请求失败:', error);
            throw error;
        })
        .finally(() => {
            console.log('请求完成(无论成功还是失败)');
            // 可以在这里进行清理工作,如隐藏加载指示器
            hideLoadingIndicator();
        });
}

function hideLoadingIndicator() {
    // 隐藏加载指示器的代码
    console.log('隐藏加载指示器');
}

对象展开运算符

对象展开运算符允许我们展开对象属性。

javascript
let obj1 = { a: 1, b: 2 };
let obj2 = { c: 3, d: 4 };

// 合并对象
let merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }

// 复制对象
let copy = { ...obj1 };
console.log(copy); // { a: 1, b: 2 }

// 覆盖属性
let updated = { ...obj1, b: 20 };
console.log(updated); // { a: 1, b: 20 }

正则表达式改进

命名捕获组

javascript
// 传统捕获组
let regex1 = /(\d{4})-(\d{2})-(\d{2})/;
let match1 = regex1.exec('2023-10-25');
console.log(match1[1]); // "2023"
console.log(match1[2]); // "10"
console.log(match1[3]); // "25"

// 命名捕获组
let regex2 = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let match2 = regex2.exec('2023-10-25');
console.log(match2.groups.year);  // "2023"
console.log(match2.groups.month); // "10"
console.log(match2.groups.day);   // "25"

// 在替换字符串中使用命名捕获组
let date = '2023-10-25';
let formatted = date.replace(regex2, '$<month>/$<day>/$<year>');
console.log(formatted); // "10/25/2023"

反向断言

javascript
// 正向先行断言
let text = "价格是 $100";
let regex1 = /\$(?=\d+)/; // 匹配后面跟着数字的 $
console.log(text.match(regex1)); // ["$"]

// 负向先行断言
let regex2 = /\$(?!\d+)/; // 匹配后面不跟数字的 $
console.log(text.match(regex2)); // null

// 正向后行断言
let regex3 = /(?<=\$)\d+/; // 匹配前面是 $ 的数字
console.log(text.match(regex3)); // ["100"]

// 负向后行断言
let regex4 = /(?<!\$)\d+/; // 匹配前面不是 $ 的数字
let text2 = "价格是 100 美元";
console.log(text2.match(regex4)); // ["100"]

dotAll 模式

javascript
// 传统模式下,点号不匹配换行符
let regex1 = /foo.bar/;
console.log(regex1.test('foo\nbar')); // false

// 使用 s 标志启用 dotAll 模式
let regex2 = /foo.bar/s;
console.log(regex2.test('foo\nbar')); // true

ES2019 (ES10) 特性

Array.prototype.flat() 和 flatMap()

javascript
// flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
let arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]

let arr2 = [1, 2, [3, [4, 5]]];
console.log(arr2.flat()); // [1, 2, 3, [4, 5]]
console.log(arr2.flat(2)); // [1, 2, 3, 4, 5]

// flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组
let arr = [1, 2, 3];
console.log(arr.flatMap(x => [x, x * 2])); 
// [1, 2, 2, 4, 3, 6]

Object.fromEntries()

将键值对列表转换为对象。

javascript
let entries = [['name', '张三'], ['age', 25]];
let obj = Object.fromEntries(entries);
console.log(obj); // { name: "张三", age: 25 }

// 实际应用:从 URLSearchParams 创建对象
let params = new URLSearchParams('name=张三&age=25');
let paramObj = Object.fromEntries(params);
console.log(paramObj); // { name: "张三", age: "25" }

String.prototype.trimStart() 和 trimEnd()

javascript
let str = '   hello world   ';

console.log(str.trimStart()); // "hello world   "
console.log(str.trimEnd());   // "   hello world"
console.log(str.trim());      // "hello world"

可选的 catch 绑定

javascript
// 传统写法
try {
    someRiskyOperation();
} catch (error) {
    // 即使不使用 error,也必须声明
    console.log('操作失败');
}

// ES2019 允许省略 catch 参数
try {
    someRiskyOperation();
} catch {
    console.log('操作失败');
}

ES2020 (ES11) 特性

可选链操作符 (?.)

javascript
let user = {
    name: "张三",
    address: {
        street: "长安街",
        city: "北京"
    }
};

// 传统写法需要多次检查
if (user && user.address && user.address.city) {
    console.log(user.address.city);
}

// 可选链操作符
console.log(user?.address?.city); // "北京"
console.log(user?.address?.zipcode); // undefined
console.log(user?.contact?.email); // undefined

// 可选链与函数调用
let obj = {
    method: function() {
        return "hello";
    }
};

console.log(obj.method?.()); // "hello"
console.log(obj.nonexistent?.()); // undefined

空值合并操作符 (??)

javascript
// 与 || 的区别
let value1 = "" || "默认值";
console.log(value1); // "默认值"

let value2 = "" ?? "默认值";
console.log(value2); // ""

let value3 = null ?? "默认值";
console.log(value3); // "默认值"

let value4 = undefined ?? "默认值";
console.log(value4); // "默认值"

// 实际应用
function configure(settings) {
    return {
        theme: settings.theme ?? "light",
        lang: settings.lang ?? "zh-CN",
        notifications: settings.notifications ?? true
    };
}

Promise.allSettled()

等待所有 Promise 完成(无论成功还是失败)。

javascript
let promises = [
    Promise.resolve("成功1"),
    Promise.reject("失败1"),
    Promise.resolve("成功2")
];

Promise.allSettled(promises).then(results => {
    console.log(results);
    // [
    //   { status: 'fulfilled', value: '成功1' },
    //   { status: 'rejected', reason: '失败1' },
    //   { status: 'fulfilled', value: '成功2' }
    // ]
    
    // 统计成功和失败的数量
    let fulfilled = results.filter(r => r.status === 'fulfilled').length;
    let rejected = results.filter(r => r.status === 'rejected').length;
    
    console.log(`成功: ${fulfilled}, 失败: ${rejected}`);
});

globalThis

提供了一个标准的方式来访问全局对象。

javascript
// 在浏览器中
console.log(globalThis === window); // true

// 在 Node.js 中
console.log(globalThis === global); // true

// 在 Web Workers 中
console.log(globalThis === self); // true

// 统一访问全局对象的方式
globalThis.myGlobalVar = "全局变量";

现代 JavaScript 特性持续为开发者提供更强大、更简洁的编程工具。掌握这些新特性有助于编写更高质量的代码,提高开发效率。