0%

Javascript-异步

异步编程

JavaScript 是单线程的,意味着它一次只能执行一个任务。如果遇到耗时操作(如网络请求),程序会“阻塞”,导致页面卡顿。为了解决这个问题,JavaScript 使用异步编程,让耗时操作在后台执行,同时继续执行其他代码。

异步的特点:

  • 不会阻塞后续代码的执行。
  • 耗时操作完成后,通过回调函数、Promise 或 async/await 来处理结果。

也就是实现异步编程有 3 种方法:回调函数、Promise 或 async/await

回调函数容易出现”回调地狱”的情况,所以目前不怎么依赖它来实现异步编程,我们简单介绍即可


回调函数

回调函数本质就是一个函数,只是把它作为参数传递给了另一个函数,并在某个操作完成后被调用。回调函数的核心思想是:

  • 延迟执行:在某个条件满足或某个操作完成后,才执行这个函数。
  • 异步处理:常用于处理异步操作的结果。

示例:

// 1. 定义一个回调函数(取餐后的动作)
function takeFood(food) {
console.log("取餐:" + food);
}

// 2. 定义一个函数(点餐)
function orderFood(callback) {
console.log("点餐:汉堡");
setTimeout(() => {
const food = "汉堡";
callback(food); // 3秒后调用回调函数
}, 3000);
}

// 3. 点餐,并告诉服务员取餐时调用 takeFood 函数
orderFood(takeFood);

// 4. 继续做其他事情
console.log("等待取餐中,先玩手机...");

输出结果:

点餐:汉堡
等待取餐中,先玩手机...
取餐:汉堡

Promise

本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了,它表示一个异步操作的最终完成(或失败)及其结果值。你可以把它理解为一个“承诺”:

  • 承诺:将来会完成某个操作(成功或失败)
  • 结果:操作完成后,会返回一个值(或错误)

Promise 的三种状态

一个 Promise 对象有三种状态:

  1. Pending(进行中):初始状态,表示操作尚未完成。
  2. Fulfilled(已成功):表示操作成功完成,并返回结果值。
  3. Rejected(已失败):表示操作失败,并返回错误原因。

一旦 Promise 的状态从 Pending 变为 Fulfilled 或 Rejected,就不可再改变。

then()

.then()Promise 对象的一个方法,用于处理 Promise 成功完成后的结果。它是 JavaScript 中实现异步编程的核心工具之一,让代码更清晰、更易读

.then() 的作用

  • 处理成功的结果:当 Promise 的状态从 Pending 变为 Fulfilled(即成功)时,.then() 中的回调函数会被调用。
  • 链式调用.then() 返回一个新的 Promise,可以继续调用下一个 .then(),从而实现多个异步操作的顺序执行。

.then()的语法

promise.then(onFulfilled, onRejected);
  • **onFulfilled**:一个回调函数,当 Promise 成功时调用。它接受一个参数,即 Promise 的成功结果。
  • **onRejected**(可选):一个回调函数,当 Promise 失败时调用。它接受一个参数,即 Promise 的失败原因。

通常,我们只使用 onFulfilled,而用 .catch() 来处理失败的情况。

链式调用-示例:

function doSomething() {
return new Promise((resolve) => {
setTimeout(() => resolve("第一步结果"), 1000);
});
}

function doSomethingElse(result) {
return new Promise((resolve) => {
setTimeout(() => resolve(result + " -> 第二步结果"), 1000);
});
}

function doThirdThing(result) {
return new Promise((resolve) => {
setTimeout(() => resolve(result + " -> 第三步结果"), 1000);
});
}

function failureCallback(error) {
console.error("出错:", error);
}

doSomething()
.then(function (result) {
return doSomethingElse(result);
})
.then(function (newResult) {
return doThirdThing(newResult);
})
.then(function (finalResult) {
console.log(`得到最终结果:${finalResult}`);
})
.catch(failureCallback);

输出结果:

得到最终结果:第一步结果 -> 第二步结果 -> 第三步结果

async/await

async/await 是 JavaScript 中用于简化异步编程的语法糖。它基于 Promise,但让异步代码的写法更像同步代码,从而更容易理解和维护。

async:用于声明一个异步函数。异步函数会隐式返回一个 Promise

await:用于等待一个 Promise 完成(即 Promise 的状态变为 resolved)。await 只能在 async 函数中使用

promise章节中的代码用async/await简写如下:

async function main() {
try {
const result1 = await doSomething(); // 等待第一步完成
const result2 = await doSomethingElse(result1); // 等待第二步完成
const finalResult = await doThirdThing(result2); // 等待第三步完成
console.log(`得到最终结果:${finalResult}`);
} catch (error) {
failureCallback(error); // 捕获错误
}
}

// 调用 main 函数
main();

💡关于语法糖
不改变语言的功能:语法糖只是提供了一种更简洁的写法,底层实现仍然是原有的语法或机制。

提高代码的可读性:语法糖通常会让代码更直观、更易理解。

减少代码量:语法糖可以帮助开发者用更少的代码实现相同的功能。


闭包

闭包是指一个函数能够“记住”并访问它定义时的环境,即使这个函数在定义时的环境之外执行。即 闭包 = 函数 + 引用环境

闭包的核心

  1. 函数嵌套

    • 闭包通常发生在函数嵌套的情况下,即一个函数内部定义了另一个函数。
  2. 访问外部变量

    • 内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
  3. “记住”环境

    • 闭包会“记住”它定义时的环境(即外部函数的作用域),即使外部函数已经执行完毕。

示例 1:简单的闭包

function outer() {
const message = "Hello, Closure!"; // 外部函数的变量

function inner() {
console.log(message); // 内部函数访问外部函数的变量
}

return inner; // 返回内部函数
}

const innerFunc = outer(); // outer 执行完毕
innerFunc(); // 输出: Hello, Closure!
  • outer 函数内部有一个变量 message
  • inner 函数使用了 message
  • 即使 outer 函数执行完毕,inner 函数仍然可以访问 message,这就是闭包。

闭包的作用

  1. 记住变量

    • 闭包可以让函数“记住”它定义时的变量,即使外部函数已经执行完毕。
  2. 创建私有变量

    • 闭包可以用来隐藏变量,避免被外部直接修改。
    function createPerson(name) {
    let age = 0; // 私有变量

    return {
    getName: function() {
    return name;
    },
    getAge: function() {
    return age;
    },
    celebrateBirthday: function() {
    age++;
    }
    };
    }

    const person = createPerson("Alice");
    console.log(person.getName()); // 输出: Alice
    person.celebrateBirthday();
    console.log(person.getAge()); // 输出: 1
  3. 实现模块化

    • 闭包可以用来隐藏内部实现细节,只暴露必要的功能。
    const module = (function() {
    const privateVariable = "I am private";

    return {
    publicMethod: function() {
    console.log(privateVariable);
    }
    };
    })();

    module.publicMethod(); // 输出: I am private

闭包的注意事项

  1. 内存泄漏
    • 闭包会一直“记住”外部函数的变量,即使这些变量已经不再需要了。如果闭包用得太多,可能会导致内存占用过高。
    • 解决方法:在不需要时手动解除引用。
function outer() {
const largeData = new Array(1000000).fill("data");

return function() {
console.log("Closure!");
};
}

const innerFunc = outer();
// 不再需要时解除引用
innerFunc = null;

事件循环

JavaScript 是单线程的,这意味着它一次只能执行一个任务。如果所有任务都是同步的,那么一个耗时操作会阻塞整个程序的执行。为了解决这个问题,JavaScript 引入了异步操作和事件循环。

事件循环负责协调异步任务的执行。它的核心任务是:

  • 监控调用栈(Call Stack):调用栈是 JavaScript 执行同步代码的地方。
  • 管理任务队列(Task Queues):任务队列用于存储待执行的异步任务。
  • 在调用栈为空时,将任务队列中的任务推入调用栈执行

❗我个人用这个用的比较少,后续在nodejs中会继续学习