阅读视图

发现新文章,点击刷新页面。

ES6+ 新特性解析:让 JavaScript 开发更优雅高效

ES6(ECMAScript 2015)是 JavaScript 语言发展的里程碑,引入了大量让代码更简洁、更易维护的新特性。本文将深入解析这些特性,并通过实际代码示例展示它们的强大之处。

解构赋值:优雅的数据提取

解构赋值让我们能够从数组或对象中快速提取值,赋给变量。

数组解构

// 基本数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 嵌套数组解构
const [a, [b, c, [d], e]] = [1, [2, 3, [4], 5]];
console.log(a, b, c, d, e); // 1 2 3 4 5

// 剩余参数解构
const arr = [1, 2, 3, 4, 5];
const [first, ...rest] = arr;
console.log(first, rest); // 1 [2, 3, 4, 5]

// 实际应用:提取教练和球员
const users = ['Darvin Ham', 'James', 'Luka', 'Davis', 'Ayton', 'Kyle'];
const [captain, ...players] = users;
console.log(captain, players); 
// Darvin Ham ['James', 'Luka', 'Davis', 'Ayton', 'Kyle']

对象解构

const sex = 'boy';
const obj = {
    name: 'Darvin Ham',
    age: 25,
    sex, // 对象属性简写
    like: {
        n: '唱跳'
    }
};

// 对象解构
let { name, age, like: { n } } = obj;
console.log(name, age, n); // Darvin Ham 25 唱跳

// 字符串也可以解构
const [e, r, ...u] = 'hello';
console.log(e, r, u); // h e ['l', 'l', 'o']

// 获取字符串长度
const { length } = 'hello';
console.log(length); // 5

解构的实用技巧

// 交换变量(无需临时变量)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1

// 函数参数解构
function greet({ name, greeting = 'Hello' }) {
    console.log(`${greeting}, ${name}!`);
}
greet({ name: 'Bob' }); // Hello, Bob!

// 设置默认值
const { name, gender = 'unknown' } = { name: 'Alice' };
console.log(gender); // unknown(因为 user 中没有 gender 属性)

模板字符串:更优雅的字符串处理

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

let myName = 'zhangsan';

// 传统字符串拼接
console.log('hello, i am ' + myName);

// 模板字符串
console.log(`hello, i am ${myName}`);
console.log(`hello, i am ${myName.toUpperCase()}`);

// 多行字符串
const message = `
  亲爱的 ${myName}:
  欢迎使用我们的服务!
  祝您使用愉快!
`;
console.log(message);

更现代的循环:for...of

for...of 循环提供了更好的可读性和性能。

let myName = 'zhangsan';

// for...of 遍历字符串
for(let x of myName) {
    console.log(x); // 依次输出: z h a n g s a n
}

// 与 for 循环对比
const arr = [1, 2, 3, 4, 5];

// 传统的 for 循环
for(let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
}

// 更简洁的 for...of
for(let item of arr) {
    console.log(item);
}

性能建议for...of 语义更好,可读性更强,性能也不会比计数循环差太多。而 for...in 性能较差,应尽量避免使用。

BigInt:处理大整数的新数据类型

JavaScript 的数字类型有精度限制,最大安全整数是 2^53-1。

// JavaScript 的数字精度问题
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

let num = 1234567890987654321;
console.log(num); // 1234567890987654400(精度丢失!)

// 使用 BigInt
let num2 = 1234567890987654321n;
console.log(num2, typeof num2); // 1234567890987654321n 'bigint'

// BigInt 运算
const big1 = 9007199254740991n;
const big2 = 1n;
console.log(big1 + big2); // 9007199254740992n

// 注意事项
console.log(1n + 2); // ❌ TypeError: Cannot mix BigInt and other types
console.log(Math.sqrt(16n)); // ❌ TypeError
const x = 3.14n; // ❌ SyntaxError: Invalid or unexpected token

BigInt 使用要点:

  • 使用 n 后缀表示 BigInt 字面量
  • 不能与 Number 类型直接混合运算
  • 不能使用 Math 对象的方法
  • 只能表示整数,不支持小数

函数参数的增强

默认参数

function foo(x = 1, y = 1) {
    return x + y;
}
console.log(foo(3)); // 4 (使用默认值 y=1)
console.log(foo(3, 5)); // 8
console.log(foo()); // 2 (使用默认值 x=1, y=1)

剩余参数 vs arguments

function foo(...args) {
    console.log('剩余参数:', args, typeof args); 
    // [1, 2, 3, 4] 'object'
    console.log('是数组吗:', Array.isArray(args)); // true
    
    console.log('arguments:', arguments, typeof arguments); 
    // [Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 } 'object'
    console.log('arguments是数组吗:', Array.isArray(arguments)); // false
    
    // 剩余参数支持数组方法
    console.log('参数个数:', args.length);
    console.log('参数总和:', args.reduce((a, b) => a + b, 0));
}

foo(1, 2, 3, 4);

剩余参数的优势:

  • 是真正的数组,可以使用所有数组方法
  • 更清晰的语法
  • 更好的类型推断(TypeScript)

其他实用特性

指数运算符

// ES7 (2016) 引入的指数运算符
console.log(2 ** 10); // 1024
console.log(3 ** 3); // 27

// 替代 Math.pow()
console.log(Math.pow(2, 10)); // 1024 (传统方式)

对象属性简写

const name = 'Alice';
const age = 25;

// 传统写法
const obj1 = {
    name: name,
    age: age
};

// ES6 简写写法
const obj2 = {
    name,
    age
};

console.log(obj2); // { name: 'Alice', age: 25 }

总结

ES6+ 的新特性让 JavaScript 开发变得更加优雅和高效:

  • 解构赋值 让数据提取更直观
  • 模板字符串 让字符串处理更简洁
  • for...of 提供更好的循环体验
  • BigInt 解决大整数计算问题
  • 函数参数增强 提供更灵活的函数设计

这些特性不仅提高了开发效率,也让代码更易读、易维护。建议在实际项目中积极采用这些现代 JavaScript 特性,提升代码质量。

学习建议:从解构赋值和模板字符串开始,逐步掌握其他特性,让 ES6+ 成为你的开发利器!

深入理解 JavaScript 异步编程:从 Ajax 到 Promise

在现代 Web 开发中,异步编程是不可或缺的核心概念。本文将通过几个实际的代码示例,带你深入理解 JavaScript 中的异步操作,从传统的 Ajax 到现代的 Promise 和 Fetch API。

1. 传统 Ajax 与现代 Fetch API

XMLHttpRequest:经典的异步请求方式

<script>
    const xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://api.github.com/orgs/lemoncode/members', true);
    xhr.send();
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4 && xhr.status === 200) {
            const data = JSON.parse(xhr.responseText);
            console.log(data);
        }
    }
</script>

XMLHttpRequest 是传统的异步请求方式,基于回调函数实现。需要手动处理 readyState 和 status 状态码,代码相对复杂。

Fetch API:现代化的替代方案

<script>
    fetch('https://api.github.com/orgs/lemoncode/members')
        .then(res => res.json())
        .then(data => {
            console.log(data);
        })
</script>

Fetch API 的优势:

  • 基于 Promise 实现,支持链式调用
  • 语法简洁,无需手动处理状态码
  • 更符合现代 JavaScript 编程风格

2. 封装基于 Promise 的 getJSON 函数

为了将传统的 Ajax 改造成 Promise 风格,我们可以封装一个 getJSON 函数:

const getJSON = (url) => {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.send();
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4) {
                if(xhr.status === 200) {
                    const data = JSON.parse(xhr.responseText);
                    resolve(data);
                } else {
                    reject(`HTTP错误: ${xhr.status}`);
                }
            }
        }
        xhr.onerror = function() {
            reject('网络错误');
        }
    });
}

// 使用示例
getJSON('https://api.github.com/orgs/lemoncode/members')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    })

关键点说明:

  • Promise 构造函数接收一个执行器函数,同步执行
  • resolve() 将 Promise 状态改为 fulfilled,触发 then 回调
  • reject() 将 Promise 状态改为 rejected,触发 catch 回调
  • onerror 只能捕获网络错误,不能捕获 HTTP 错误状态码

3. Promise 状态管理

状态 (State) 含义 说明
pending (等待中) 初始状态 Promise 被创建后,尚未被兑现或拒绝时的状态
fulfilled (已成功) 操作成功完成 异步任务成功结束,调用了 resolve(value)
rejected (已失败) 操作失败 异步任务出错,调用了 reject(reason)

4. 实现 Sleep 函数:控制异步流程

在同步语言中,我们可以使用 sleep 函数暂停程序执行,但在 JavaScript 中需要借助 Promise 模拟这一行为:

// 基础版本
function sleep(n) {
    let p;
    p = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(p); // pending 状态
            resolve(); // 或 reject()
            console.log(p); // fulfilled/rejected 状态
        }, n);
    });
    return p;
}

// 简化版本
const sleep = n => new Promise(resolve => setTimeout(resolve, n));

// 使用示例
sleep(3000)
    .then(() => {
        console.log('3秒后执行');
    })
    .catch(() => {
        console.log('error');
    })
    .finally(() => {
        console.log('finally'); // 无论成功失败都会执行
    });

setTimeout vs Sleep:本质区别

setTimeout - 事件调度

console.log("第1步");
setTimeout(() => {
    console.log("第3步 - 在2秒后执行");
}, 2000);
console.log("第2步 - 不会等待setTimeout");

// 输出顺序:
// 第1步
// 第2步 - 不会等待setTimeout
// 第3步 - 在2秒后执行

Sleep - 流程控制

async function demo() {
    console.log("第1步");
    await sleep(2000);  // 真正暂停函数的执行
    console.log("第2步 - 在2秒后执行");
}

demo();

// 输出顺序:
// 第1步
// (等待2秒)
// 第2步 - 在2秒后执行

这么来说吧:setTimeout是告诉 JavaScript 引擎:“等 X 毫秒后,帮我执行这个函数。” 它会去做别的同步操作,不会阻塞其他代码的执行。sleep则是在一个连续的异步操作流中,插入一段等待时间。sleep 让你写出“顺序等待”的逻辑,而 setTimeout 只是“未来某个时间点做某事”。

5. JavaScript 内存管理与数据拷贝

理解 JavaScript 的内存管理对于编写高效代码至关重要:

const arr = [1, 2, 3, 4, 5, 6];

const arr2 = [].concat(arr); 
arr2[0] = 0;
console.log(arr, arr2); // arr 不变,arr2 改变

// 深拷贝 - 开销大
const arr3 = JSON.parse(JSON.stringify(arr));
arr3[0] = 100;
console.log(arr, arr3); // arr 不变,arr3 改变

在数组上进行存储的时候我们应该尽量规避JSON序列化的深拷贝,开销太大,我们可以选择用[].cancat(arr)这种巧妙的方法实现拷贝,虽然创建了一个新数组,但开销仍然小于序列化。

内存管理要点

  1. JS 变量在编译阶段分配内存空间
  2. 简单数据类型存储在栈内存中
  3. 复杂数据类型存储在堆内存中,栈内存存储引用地址
  4. 浅拷贝只复制引用,深拷贝创建完全独立的新对象

总结

通过本文的示例和解析,我们深入探讨了:

  1. Ajax 与 Fetch 的对比:Fetch API 提供了更简洁的 Promise-based 接口
  2. 手写getJson函数:将传统的 Ajax 改造成 现代的 Promise 风格
  3. 手写sleep函数:使用 Promise 实现类似同步的编程体验
  4. 内存管理:理解变量存储方式对编写高效代码的重要性

掌握这些概念将帮助你编写更清晰、更易维护的异步 JavaScript 代码,为学习更高级的 async/await 语法打下坚实基础。

❌