当前位置: 首页 > news >正文

JavaScript系列03-异步编程全解析

本文介绍了异步相关的内容,包括:

  1. 回调函数与回调地狱
  2. Promise详解
  3. async/await语法
  4. Generator函数
  5. 事件循环机制
  6. 异步编程最佳实践

1、回调函数与回调地狱

JavaScript最初是为处理网页交互而设计的语言,异步编程是其核心特性之一。最早的异步编程方式是使用回调函数。

什么是回调函数?

回调函数是作为参数传递给另一个函数的函数,并在特定事件发生后执行。


// 基本回调示例function fetchData(callback) {setTimeout(() => {const data = { name: "张三", age: 30 };callback(data);}, 1000);}fetchData(function(data) {console.log("数据获取成功:", data);});

回调函数使我们能够非阻塞地执行代码,这对于网络请求、文件操作等耗时任务尤为重要。

回调地狱问题

当多个异步操作需要依次执行时,回调函数会嵌套在一起,形成所谓的"回调地狱"(Callback Hell):


fetchUserData(function(user) {console.log("获取用户信息:", user);fetchUserPosts(user.id, function(posts) {console.log("获取用户文章:", posts);fetchPostComments(posts[0].id, function(comments) {console.log("获取文章评论:", comments);fetchCommentAuthor(comments[0].authorId, function(author) {console.log("获取评论作者:", author);// 还可以继续嵌套...});});});});

回调地狱带来的问题:

  • 代码可读性差,形成"金字塔"结构

  • 错误处理复杂

  • 变量作用域容易混淆

  • 代码维护困难

2、Promise详解

Promise是JavaScript中解决回调地狱的第一个标准方案,ES6正式将其纳入规范。

Promise的基本概念

Promise是一个代表异步操作最终完成或失败的对象。它有三种状态:

  • pending: 初始状态,既未完成也未失败

  • fulfilled: 操作成功完成

  • rejected: 操作失败

一旦Promise状态改变,就不能再变,这就是"承诺"的含义。


// 创建Promiseconst promise = new Promise((resolve, reject) => {// 异步操作setTimeout(() => {const success = Math.random() > 0.5;if (success) {resolve("操作成功"); // 成功时调用} else {reject("操作失败"); // 失败时调用}}, 1000);});// 使用Promisepromise.then(result => {console.log(result); // "操作成功"}).catch(error => {console.log(error); // "操作失败"}).finally(() => {console.log("无论成功失败都会执行");});

Promise链式调用

Promise的优势之一是支持链式调用,可以优雅地处理依赖于前一个异步操作结果的多个异步操作:


fetchUserData(userId).then(user => {console.log("用户数据:", user);return fetchUserPosts(user.id); // 返回新的Promise}).then(posts => {console.log("用户文章:", posts);return fetchPostComments(posts[0].id);}).then(comments => {console.log("文章评论:", comments);return fetchCommentAuthor(comments[0].authorId);}).then(author => {console.log("评论作者:", author);}).catch(error => {// 捕获链中任何位置的错误console.error("发生错误:", error);});

这种链式写法将原本嵌套的回调拍平,提高了代码的可读性。

Promise常用方法

Promise类提供了几个实用的静态方法:

Promise.all()

Promise.all(): 并行执行多个Promise,当所有Promise都成功时返回结果数组


// 同时发起多个请求const promises = [fetch('https://api.example.com/users'),fetch('https://api.example.com/posts'),fetch('https://api.example.com/comments')];Promise.all(promises).then(responses => Promise.all(responses.map(res => res.json()))).then(data => {const [users, posts, comments] = data;console.log(users, posts, comments);}).catch(error => {// 任一请求失败都会进入catchconsole.error("至少有一个请求失败:", error);});

Promise.race()

Promise.race(): 返回最先解决或拒绝的Promise结果


// 实现超时功能function fetchWithTimeout(url, ms) {const fetchPromise = fetch(url);const timeoutPromise = new Promise((_, reject) => {setTimeout(() => reject(new Error("请求超时")), ms);});return Promise.race([fetchPromise, timeoutPromise]);}fetchWithTimeout('https://api.example.com/data', 3000).then(response => response.json()).then(data => console.log(data)).catch(error => console.error(error));

Promise.allSettled()

Promise.allSettled(): ES2020引入,等待所有Promise完成(无论成功或失败)


Promise.allSettled([Promise.resolve(1),Promise.reject('错误'),Promise.resolve(3)]).then(results => {console.log(results);// [// { status: "fulfilled", value: 1 },// { status: "rejected", reason: "错误" },// { status: "fulfilled", value: 3 }// ]});

Promise.any()

Promise.any(): ES2021引入,返回首个成功的Promise结果


// 尝试从多个源获取数据,返回最先成功的Promise.any([fetch('https://api-1.example.com/data').then(r => r.json()),fetch('https://api-2.example.com/data').then(r => r.json()),fetch('https://api-3.example.com/data').then(r => r.json())]).then(data => console.log("获取到数据:", data)).catch(errors => console.error("所有请求均失败:", errors));

4、Generator函数

Generator函数是ES6引入的新特性,它允许函数在执行过程中暂停和恢复,这使得它特别适合实现异步控制流。

Generator基础

Generator函数在声明时使用星号(*)标记,内部使用yield关键字暂停执行:


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 }

生成器返回一个迭代器,调用next()方法会执行代码直到遇到下一个yield语句。

使用Generator实现异步流程控制

Generator可以用来处理异步操作,但通常需要一个运行器函数:


function fetchData(url) {return new Promise((resolve, reject) => {setTimeout(() => {if (Math.random() > 0.3) {resolve(`来自${url}的数据`);} else {reject(`获取${url}失败`);}}, 1000);});}function* fetchSequence() {try {const user = yield fetchData('/api/user');console.log(user);const posts = yield fetchData('/api/posts');console.log(posts);const comments = yield fetchData('/api/comments');console.log(comments);return '所有数据获取完成';} catch (error) {console.error('出错了:', error);return '数据获取过程出错';}}// 手动运行生成器function runGenerator(generatorFn) {const generator = generatorFn();function handle(result) {if (result.done) return Promise.resolve(result.value);return Promise.resolve(result.value).then(res => handle(generator.next(res))).catch(err => handle(generator.throw(err)));}return handle(generator.next());}runGenerator(fetchSequence).then(result => console.log(result)).catch(err => console.error(err));

4、async/await语法

尽管Promise已经比回调函数有了很大改进,但ES2017引入的async/await语法进一步简化了异步编程,使异步代码看起来更像同步代码。

async/await基础

  • async:声明一个异步函数,它会返回一个Promise

  • await:暂停异步函数的执行,等待Promise解决

实现原理

(1)生成器与迭代器

async/await 的核心原理是利用生成器函数(Generator)的暂停和恢复能力:

function* genFunc() {yield 1;yield 2;
}

生成器可以通过 yield 暂停执行,并在之后通过 next() 恢复执行。

(2)Promise 结合

async/await 将 Generator 与 Promise 结合:

  • async 标记的函数总是返回 Promise
  • await 操作会暂停函数执行,等待 Promise 完成

(3)自动执行器

关键环节是一个自动执行器,负责:

  1. 执行生成器函数
  2. 当遇到 yield 时暂停
  3. 等待 Promise 解决
  4. 将结果传回生成器并恢复执行

简化版实现

一个简化的 async/await 实现可以是:

function asyncToGenerator(generatorFunc) {return function() {const gen = generatorFunc.apply(this, arguments);return new Promise((resolve, reject) => {function step(key, arg) {let result;try {result = gen[key](arg);} catch (error) {return reject(error);}const { value, done } = result;if (done) {return resolve(value);} else {return Promise.resolve(value).then(val => step("next", val),err => step("throw", err));}}step("next");});};
}

Babel 转译示例

以下是 Babel 如何将 async/await 转译为 ES5 代码(简化版):

// 原始 async 函数
async function foo() {const result = await someAsyncFunc();return result;
}// 转译后
function foo() {return _asyncToGenerator(function* () {const result = yield someAsyncFunc();return result;});
}

工作流程

  1. 当调用 async 函数时,自动创建一个 Promise 对象
  2. 函数体内代码正常执行,直到遇到 await 表达式
  3. await 表达式会暂停当前函数执行
  4. await 后的表达式会被转换成 Promise(如果不是已经是 Promise)
  5. 当该 Promise 完成时,恢复函数执行并返回 Promise 的结果
  6. 如果 Promise 被拒绝,await 表达式会抛出错误

// 使用async/await重写前面的例子async function getUserInfo(userId) {try {const user = await fetchUserData(userId);console.log("用户数据:", user);const posts = await fetchUserPosts(user.id);console.log("用户文章:", posts);const comments = await fetchPostComments(posts[0].id);console.log("文章评论:", comments);const author = await fetchCommentAuthor(comments[0].authorId);console.log("评论作者:", author);return author;} catch (error) {console.error("发生错误:", error);}}// 调用异步函数getUserInfo(123).then(result => console.log("最终结果:", result));

相比Promise链,async/await的优势:

  • 代码结构清晰,接近同步写法

  • 便于使用条件语句和循环

  • 易于进行错误处理

  • 调试更简单

并行执行

虽然await会暂停函数执行,但有时我们需要并行执行多个异步操作:


async function fetchAllData() {// 错误示范:串行执行,效率低const users = await fetchUsers();const posts = await fetchPosts();const comments = await fetchComments();// 正确示范:并行执行const [users, posts, comments] = await Promise.all([fetchUsers(),fetchPosts(),fetchComments()]);return { users, posts, comments };}

错误处理

async函数中可以使用try/catch来捕获错误,也能捕获await的Promise拒绝:


async function fetchWithErrorHandling() {try {const response = await fetch('https://api.example.com/data');if (!response.ok) {throw new Error(`HTTP错误: ${response.status}`);}const data = await response.json();return data;} catch (error) {console.error("获取数据失败:", error);// 可以返回默认值return { error: true, message: error.message };}}

Generator vs async/await

在ES2017引入async/await之前,Generator曾经是实现异步控制流的重要工具。现在,async/await基本上取代了Generator在异步编程中的角色,因为:

  • async/await是基于Generator和Promise的语法糖,更易于使用

  • async函数无需运行器,浏览器原生支持

  • 错误处理更加直观

然而,Generator在某些场景(如惰性计算、状态机实现)中仍然非常有用。

5、事件循环机制

要真正理解JavaScript的异步编程,必须了解底层的事件循环机制。JavaScript是单线程的,依靠事件循环来处理异步操作。

事件循环的关键组件

事件循环机制涉及以下几个关键组件:

  • 执行栈(Call Stack):管理函数调用的栈结构,遵循"后进先出"原则

  • 宏任务队列(Macrotask Queue):存放宏任务,如setTimeout、setInterval、I/O等

  • 微任务队列(Microtask Queue):存放微任务,如Promise回调、MutationObserver等

  • 事件循环(Event Loop):持续检查执行栈和任务队列的循环过程

宏任务与微任务

宏任务(Macrotask)包括:

  • script(整体代码)

  • setTimeout/setInterval

  • setImmediate(Node.js环境)

  • I/O操作

  • UI渲染(浏览器)

  • requestAnimationFrame(浏览器)

微任务(Microtask)包括:

  • Promise.then/catch/finally

  • MutationObserver

  • process.nextTick(Node.js环境)

  • queueMicrotask()

事件循环的基本流程

(1) 开始:执行第一个宏任务,即全局代码(script)

(2) 同步代码执行

  • 所有同步代码进入执行栈按顺序执行

  • 如遇异步API,其回调函数被分发到对应的任务队列中

(3) 执行栈清空

  • 同步代码执行完毕,执行栈清空

(4) 处理微任务

  • 检查微任务队列,有微任务则依次执行所有微任务

  • 执行过程中产生的新微任务也会在当前循环中执行

(5) UI渲染(仅浏览器环境):

  • 如有必要,进行页面渲染更新

(6) 处理宏任务

  • 从宏任务队列取出一个任务执行

  • 执行完后,返回步骤3,检查微任务队列

(7) 循环往复

  • 事件循环无限继续,直到所有任务队列清空

事件循环流程图:

在这里插入图片描述

实际例子解析


console.log('1. 开始'); // 同步代码setTimeout(() => {console.log('2. 第一个宏任务');Promise.resolve().then(() => {console.log('3. 宏任务中的微任务');});}, 0);Promise.resolve().then(() => {console.log('4. 第一个微任务');setTimeout(() => {console.log('5. 微任务中的宏任务');}, 0);});console.log('6. 结束'); // 同步代码// 输出顺序: 1 -> 6 -> 4 -> 2 -> 3 -> 5

(1) 第一个宏任务(script全局代码)

  • 执行同步代码,打印"1. 开始"

  • 遇到setTimeout,其回调被添加到宏任务队列

  • 遇到Promise.then,其回调被添加到微任务队列

  • 执行同步代码,打印"6. 结束"

  • 同步代码执行完毕,执行栈清空

(2) 检查微任务队列

  • 执行微任务,打印"4. 第一个微任务"

  • 遇到setTimeout,其回调被添加到宏任务队列

  • 微任务队列清空

(3) 进行UI渲染(如需)

(4) 取出下一个宏任务

  • 执行第一个setTimeout的回调,打印"2. 第一个宏任务"

  • 遇到Promise.then,其回调被添加到微任务队列

(5) 再次检查微任务队列

  • 执行微任务,打印"3. 宏任务中的微任务"

  • 微任务队列清空

(6) 进行UI渲染(如需)

(7) 取出下一个宏任务

  • 执行第二个setTimeout的回调,打印"5. 微任务中的宏任务"

关于async/await在事件循环中的位置

前面讲到async/await 就是生成器和Promise的语法糖,它的工作流程中讲到,await 表达式会暂停当前函数执行,await 后的表达式会被转换成 Promise(如果不是已经是 Promise),所以:

  • 当函数遇到 await 时,会将后续代码作为微任务放入事件循环
  • 这就是为什么 await 之后的代码总是在当前同步代码执行完毕后执行

6、异步编程最佳实践

使用Promise而非回调

所有新代码应该优先使用Promise API而非传统回调:


// 不推荐function fetchData(callback) {setTimeout(() => {callback(null, { data: 'success' });}, 1000);}// 推荐function fetchData() {return new Promise((resolve) => {setTimeout(() => {resolve({ data: 'success' });}, 1000);});}

优先使用async/await

对于大多数异步操作,使用async/await可以使代码更清晰:


// Promise链function getUserData(userId) {return fetchUser(userId).then(user => {return fetchPosts(user.id).then(posts => {user.posts = posts;return user;});});}// 使用async/awaitasync function getUserData(userId) {const user = await fetchUser(userId);user.posts = await fetchPosts(user.id);return user;}

正确处理错误

异步代码中的错误处理尤为重要:


// Promise错误处理fetchData().then(data => processData(data)).then(result => displayResult(result)).catch(error => {console.error('发生错误:', error);showErrorMessage(error);});// async/await错误处理async function handleData() {try {const data = await fetchData();const result = await processData(data);displayResult(result);} catch (error) {console.error('发生错误:', error);showErrorMessage(error);}}

避免嵌套async函数

当不需要等待内部异步操作时,避免嵌套async函数:


// 不好的实践async function processItems(items) {const results = [];for (const item of items) {// 没必要使用async函数results.push(await (async () => {const data = await fetchData(item.id);return processData(data);})());}return results;}// 更好的实践async function processItems(items) {const results = [];for (const item of items) {const data = await fetchData(item.id);results.push(processData(data));}return results;}

合理使用Promise并行执行

当多个异步操作相互独立时,应该并行执行它们:


// 低效方式:串行执行async function loadData() {const users = await fetchUsers();const products = await fetchProducts();const categories = await fetchCategories();return { users, products, categories };}// 高效方式:并行执行async function loadData() {const [users, products, categories] = await Promise.all([fetchUsers(),fetchProducts(),fetchCategories()]);return { users, products, categories };}

避免不必要的async/await

不是所有返回Promise的函数都需要async关键字:


// 不必要的asyncasync function getData() {return fetch('/api/data').then(r => r.json());}// 简化版本function getData() {return fetch('/api/data').then(r => r.json());}

使用Promise工具方法

利用Promise提供的静态方法简化常见任务:


// 并行请求并使用所有结果Promise.all([fetchUsers(), fetchPosts(), fetchComments()]).then(([users, posts, comments]) => {// 处理所有数据});// 超时处理function fetchWithTimeout(url, timeout = 5000) {return Promise.race([fetch(url),new Promise((_, reject) => {setTimeout(() => reject(new Error('请求超时')), timeout);})]);}// 任一请求成功即可function fetchFromMultipleSources(urls) {return Promise.any(urls.map(url => fetch(url)));}

编写可测试的异步代码

良好的异步代码应该易于测试:


// 可测试的异步函数async function processUserData(userId) {const user = await fetchUser(userId);if (!user) {throw new Error('用户不存在');}user.lastActive = new Date();return saveUser(user);}// 测试代码test('processUserData成功处理用户', async () => {// 使用mock替换真实APIfetchUser = jest.fn().mockResolvedValue({ id: 1, name: '张三' });saveUser = jest.fn().mockResolvedValue({ success: true });const result = await processUserData(1);expect(result.success).toBe(true);expect(saveUser).toHaveBeenCalledWith(expect.objectContaining({id: 1,lastActive: expect.any(Date)}));});

总结

JavaScript异步编程经历了从回调函数、Promise、Generator到async/await的演进。这些技术的发展使得异步代码越来越接近同步代码的直观性和可维护性,同时保留了非阻塞执行的优势。

理解事件循环机制是掌握JavaScript异步编程的关键,它解释了不同类型任务的执行顺序。在实际开发中,合理选择异步编程技术、遵循最佳实践,可以帮助我们编写出高效、可靠和易于维护的异步代码。

相关文章:

JavaScript系列03-异步编程全解析

本文介绍了异步相关的内容,包括: 回调函数与回调地狱Promise详解async/await语法Generator函数事件循环机制异步编程最佳实践 1、回调函数与回调地狱 JavaScript最初是为处理网页交互而设计的语言,异步编程是其核心特性之一。最早的异步编…...

深度解读 AMS1117:从电气参数到应用电路的全面剖析

在电子设备的电源管理领域,线性稳压器扮演着至关重要的角色,而 AMS1117 凭借其出色的性能和广泛的适用性,成为众多工程师的热门选择。本文将依据相关资料,对 AMS1117 的特性、应用、电气参数等方面进行详细解读。 一、功能特性概…...

深入理解Tomcat与Web应用部署:C/S与B/S架构下的实践指南

在当今的互联网时代,Web应用的开发与部署是软件开发领域的重要组成部分。无论是传统的C/S架构,还是现代广泛应用的B/S架构,了解它们的优缺点以及如何高效部署Web应用是每个开发者都需要掌握的技能。本文将深入探讨C/S与B/S架构的区别&#xf…...

XML 编辑器:全面指南与最佳实践

XML 编辑器:全面指南与最佳实践 引言 XML(可扩展标记语言)编辑器是处理XML文件的关键工具,对于开发人员、系统管理员以及任何需要处理XML数据的人来说至关重要。本文将全面介绍XML编辑器的概念、功能、选择标准以及最佳实践,旨在帮助读者了解如何选择和使用合适的XML编辑…...

Python实现GO鹅优化算法优化BP神经网络回归模型项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后关注获取。 1.项目背景 传统BP神经网络的局限性:BP(Back Propagation)神经网络作为一种…...

如何配置虚拟机的IP上网

要配置虚拟机的IP地址以便上网,你可以按照以下步骤操作: 打开虚拟机软件,确保虚拟机的网络设置为“桥接模式”或“NAT模式”,这样虚拟机可以与物理网络连接。 在虚拟机操作系统中,打开网络设置界面,一般在…...

【洛谷贪心算法题】P1094纪念品分组

该题运用贪心算法,核心思想是在每次分组时,尽可能让价格较小和较大的纪念品组合在一起,以达到最少分组的目的。 【算法思路】 输入处理:首先读取纪念品的数量n和价格上限w,然后依次读取每件纪念品的价格,…...

学习笔记08——ConcurrentHashMap实现原理及源码解析

1. 概述 为什么需要ConcurrentHashMap? 解决HashMap线程不安全问题:多线程put可能导致死循环(JDK7)、数据覆盖(JDK8) 优化HashTable性能:通过细粒度锁替代全局锁,提高并发度 对比…...

redis slaveof 命令 执行后为什么需要清库重新同步

在 Redis 中,执行 SLAVEOF(或 REPLICAOF)命令后,从节点需要清空现有数据并重新同步的主要原因如下: 1. 保证数据一致性 核心目标:确保从节点的数据与主节点 完全一致。问题场景: 如果从节点之前…...

6-1JVM的执行引擎处理

一、执行引擎的组成结构 ​解释器(Interpreter)​​ 逐条解释执行字节码指令,启动速度快但执行效率较低。适用于短生命周期或对启动时间敏感的场景,如调试环境。 ​即时编译器(JIT Compiler)​​ 通过动态…...

CMU15445(2023fall) Project #4 - Concurrency Control踩坑历程

把树木磨成月亮最亮时的样子, 就能让它更快地滚下山坡, 有时会比骑马还快。 完整代码见: SnowLegend-star/CMU15445-2023fall: Having Conquered the Loftiest Peak, We Stand But a Step Away from Victory in This Stage. With unwavering…...

腿足机器人之十三-强化学习PPO算法

腿足机器人之十三-强化学习PPO算法 腿足机器人位姿常用强化学习算法PPO算法核心原理PPO算法的创新设计PPO算法典型流程优势函数 对于复杂地形适应性(如楼梯、碎石路),传统的腿足机器人采用基于模型的控制器,该方法依赖精确动力学建…...

ubuntu下r8125网卡重启丢失修复案例一则

刚装的一台服务器,ubuntu24.04,主板网卡是r8125,安装服务后会莫名其妙丢失驱动 按照官网的方法下载最新8125驱动包: Realtek 然后卸载驱动 rmmod r8125 然后在驱动包里安装(幸好我之前装了build-essential&#x…...

设计一个“车速计算”SWC,通过Sender-Receiver端口输出车速信号。

1. 需求分析 功能目标:根据车轮脉冲信号(轮速传感器输入)计算当前车速,并将结果通过Sender端口发送给其他SWC。 输入:轮速脉冲数(如WheelPulse,类型uint32)。 输出:车速(如VehicleSpeed,类型float32,单位km/h)。 触发方式:周期性计算(例如每10ms执行一次)。 2.…...

DeepSeek 使用窍门与提示词写法指南

一、通用提示词技巧 窍门分类技巧说明示例提示词明确需求用“角色任务要求”明确目标作为健身教练,为30岁上班族设计一周减脂计划,需包含饮食和15分钟居家训练结构化提问分步骤、分模块提问第一步:列出Python爬虫必备的5个库;第二…...

MySQL零基础教程12—聚合查询(聚合函数)

背景 有时候我们需要汇总一些数据,比如查询一个班级的平均分数,这个时候我们需要的是把分数汇总,然后计算出一个平均值进行返回,并不需要返回某一列的值,针对这种场景,mysql中提供了一些聚合函数帮助快速完…...

JMeter 引入 JAR 包的几种方法

JMeter 支持加载外部 JAR 文件,用于: 扩展 JMeter 功能使用 Java 代码(BeanShell / JSR223)连接数据库 / 解析 Excel / 读取 CSV 📌 1. JMeter 引入 JAR 包的方式 ✅ 方式 1:将 JAR 放入 lib/ 或 lib/ext…...

一周一个Unity小游戏2D反弹球游戏 - 球板的发球

前言 本文将实现当游戏开始时球在球板上,且不具备物理性,在Windows平台上通过点击屏幕来球发射,安卓平台上当手指触摸到屏幕上时进行发球,并此时开始具备物理性。 发球逻辑 首先在球板上创建一个球的发射点,新建一个空的游戏物体,并命名为BallPoint,并将其作为SpringBoa…...

C++Primer学习(4.8位运算符)

4.8位运算符 位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能,如17.2节(第640页)将要介绍的,一种名为bitset的标准库类型也可以表示任意大小的二进制位集合,所以位运算符同样能用…...

Linux | Ubuntu 与 Windows 双系统安装 / 高频故障 / UEFI 安全引导禁用

注:本文为 “buntu 与 Windows 双系统及高频故障解决” 相关文章合辑。 英文引文,机翻未校。 How to install Ubuntu 20.04 and dual boot alongside Windows 10 如何将 Ubuntu 20.04 和双启动与 Windows 10 一起安装 Dave’s RoboShack Published in…...

SpringSecurity 实现token 认证

配置类 Configuration EnableWebSecurity EnableGlobalMethodSecurity(prePostEnabledtrue) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { Bean Override public AuthenticationManager authenticationManagerBean() throws Exception {return s…...

C语言基础要素(007):使用变量

定义变量的同时可以给定一个值,这叫初始化变量;未初始化的变量,其值在程序运行时是不确定的。变量在定义之后可以多次设置值,这称为赋值。定义使得变量从无到有,而赋值则改变已有变量的状态。变量只能被初始化一次&…...

6. Nginx 动静分离配置案例(附有详细说明+配图)

6. Nginx 动静分离配置案例(附有详细说明配图) 文章目录 6. Nginx 动静分离配置案例(附有详细说明配图)1. 动静分离概述说明2. 先使用传统方式实现,不使用 Nginx3. 使用上 Nginx 实现动静分离优化步骤4. 最后: 1. 动静分离概述说明 什么是动静分离&…...

Deepseek对ChatGPT的冲击?

从测试工程师的视角来看,DeepSeek对ChatGPT的冲击主要体现在**测试场景的垂直化需求与通用模型局限性之间的博弈**。以下从技术适配性、效率优化、风险控制及未来趋势四个维度展开分析: --- ### **一、技术适配性:垂直领域能力决定工具选择…...

在已安装二进制movit2的情况下使用自编译moveit2

在已安装二进制movit2的情况下,想使用自编译moveit2,只要引入一下自编译moveit2库的环境变量即可。主要是想搞明白这个过程发生了什么,也就是引入环境后有什么变化,以及如何对编译过程产生影响 一、setup.bash流程 所有资料上都…...

React 常见面试题及答案

记录面试过程 常见问题,如有错误,欢迎批评指正 1. 什么是虚拟DOM?为什么它提高了性能? 虚拟DOM是React创建的一个轻量级JavaScript对象,表示真实DOM的结构。当状态变化时,React会生成新的虚拟DOM&#xf…...

2025-03-01 学习记录--C/C++-C语言 整数类型对比

C语言 整数类型对比 类型位数范围(有符号)范围(无符号)格式化符号char8-128 到 1270 到 255%c 或 %hhdshort16-32,768 到 32,7670 到 65,535%hdint32-2,147,483,648 到 2,147,483,6470 到 4,294,967,295%dlong32 或 64-2,147,483…...

金融赋能绍兴纺织 民生银行助力外贸中小微企业“走出去”

在浙江绍兴,纺织业作为一张熠熠生辉的产业名片,承载着深厚的历史底蕴与蓬勃的发展活力。这里依傍长三角经济圈,交通网络纵横交错,将原材料产地与广阔市场紧密相连;产业集群高度成熟,上下游产业链完备&#…...

TCP的三握四挥

TCP协议 TCP( Transmission control protocol )即传输控制协议,是一种面向连接、可靠的数据传输协议,它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 TCP的基本特点 面向连接:通信双方在进行数据传输之前&…...

Phpstudy中的MySQL无法正常启动或启动后自动暂停,以及sqlilab环境搭建出现的问题解决方法

【解决方法】 无法启动的原因是Phpstudy中的MySQL与本地的mysql重名,导致无法正常启动;所以这时我们就需要将本地的MySQL进行修改名称; 或者修改phpstudy中数据库的端口号,但是我觉得还是不是很好解决这种问题 最后一个方法&#…...

用C语言实现一个链表(四)

用C语言实现一个链表(四) 在上期内容中,我们探讨了实现一个双向循环链表的准备工作以及一些功能——创建新结点,初始化头结点,尾插数据,尾删数据,遍历的代码,上期内容留下了一个判断…...

【我的 PWN 学习手札】House of Kiwi

House of Kiwi 之前我们利用IO_FILE一般是通过劫持vtable来实现的, House of Kiwi虽然不是通过劫持vtable来实现,但实质上是劫持vtable指向的全局的_IO_file_jumps_表来实现的。注意:对于某些版本的glibc,_IO_file_jumps_并不可写…...

象棋笔记-实战记录

文章目录 实战没发现杀招2024-06-16 实战又漏杀了,尴尬,炮震五子,3路炮有下底的机会,本来是绝杀,没算明白,以为窝心马和象都能看住这个点。。。2024-07-06 实战有进炮串打的机会,又错过了&#…...

RabbitMQ系列(六)基本概念之Routing Key

在 RabbitMQ 中,Routing Key(路由键) 是用于将消息从交换机(Exchange)路由到指定队列(Queue)的关键参数。其核心作用是通过特定规则匹配绑定关系,确保消息被正确分发。以下是其核心机…...

企业微信里可以使用的企业内刊制作工具,FLBOOK

如何让员工及时了解公司动态、行业资讯、学习专业知识,并有效沉淀企业文化?一份高质量的企业内刊是不可或缺的。现在让我来教你该怎么制作企业内刊吧 1.登录与上传 访问FLBOOK官网,注册账号后上传排版好的文档 2.选择模板 FLBOOK提供了丰富的…...

JAVA笔记【一】

现实 (抽象) 类 (创建) 对象 特点: 1.面向对象 2.跨平台 3.安全性 4.多线程 java程序基本结构 1. java源代码文件实际是普通的文本文件,源代码文件必须是.java扩展名,且必须小写 2. …...

Mybatis做批量操作

前面我们将动态标签foreach的时候,做过批量操作,但是foreach只能处理记录数不多的批量操作,数据量大了后,先不说效率,能不能成功操作都是问题,所以这里讲一讲Mybatis正确的批量操作方法: 在获取…...

C/C++动静态库的制作与原理 -- 静态库,动态库,目标文件,ELF文件,动态链接,静态链接

目录 1. 什么是库 2. 静态库 2.1 静态库的制作 2.2 静态库的使用 3. 动态库 3.1 动态库的制作 3.2 动态库的使用 4. 目标文件 5. ELF文件 6. ELF从形成到加载轮廓 6.1 ELF形成可执行 7.2 ELF可执行文件加载 7. 理解链接和加载 7.1 静态链接 7.2 ELF加载与进程地…...

Java 并发编程之synchronized

一、前言 在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在JDK1.5之前,Java是依靠Synchronized关键字实现锁功能来做到这点的。Synchronized是JVM实现的一种内置锁,锁的获取和释放是由JVM隐式实…...

Windows 11【1001问】查看Windows 11 版本的18种方法

随着技术的飞速发展,操作系统作为连接硬件与软件的核心桥梁,其版本管理和更新变得尤为重要。对于用户而言,了解自己设备上运行的具体Windows 11版本不仅有助于优化系统性能,还能确保安全性和兼容性。然而,不同场景和需…...

python 元组tuple

元组:有序不可变列表 (相当于只读的list) 注意:元组里的普通元素不可以修改,但是元组里的list可以修改 index(元素) 查找某个元素,有的话返回下标,没有的话报错 count(元素) 统计某元素在元组中出现的次数 len(元组) 统计元组内的元素个数 #定义元组,元组支持嵌套 t1("…...

485 多路信号采集,校验干扰问题

在RS-485总线中同时采集多路信号时,若某一路出现CRC校验失败,通常由总线冲突、信号干扰或硬件设计缺陷引起。以下是具体影响分析和解决方案: 一、多路信号同时采集的影响 1. 总线冲突风险 现象:多路信号同时发送时,485总线(半双工)无法区分信号,导致数据叠加损坏。 后…...

【Eureka 缓存机制】

今天简单介绍一下Eureka server 的缓存机制吧✌️✌️✌️ 一、先来个小剧场:服务发现的"拖延症" 想象你是个外卖小哥(客户端),每次接单都要打电话问调度中心(Eureka Server):“现在…...

MySQL并发知识(面试高频)

mysql并发事务解决 不同隔离级别下,mysql解决并发事务的方式不同。主要由锁机制和MVCC(多版本并发控制)机制来解决并发事务问题。 1. mysql中的锁有哪些? 表级锁: 场景:表级锁适用于需要对整个表进行操作的情况,例如…...

Git GitHub基础

git是什么? Git是一个分布式版本控制系统,用于管理源代码的变更。它允许多个开发者在同一个项目上协作,同时跟踪每个修改的历史记录。 关键词: 分布式版本控制软件 软件 安装到我们电脑上的一个工具 版本控制 例如论文&…...

Rabbit MQ 高频面试题【刷题系列】

文章目录 一、公司生产环境用的什么消息中间件?二、Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点?三、解耦、异步、削峰是什么?四、消息队列有什么缺点?五、RabbitMQ一般用在什么场景?六、简单说RabbitMQ有哪些角…...

Ubantu22.04系统docker部署Open WebUI+Ollama【教程】

Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台,旨在完全离线运行。它支持各种 LLM 运行器,如 Ollama 和 OpenAI 兼容的 API,并内置了 RAG 推理引擎,使其成为强大的 AI 部署解决方案。 1.docker拉取镜像 &#x…...

知识图谱科研文献推荐系统vue+django+Neo4j的知识图谱

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站,有好处! 📑 编号:D030 📑 vuedjangoneo4jmysql 前后端分离架构、图数据库 📑 文献知识图谱&#…...

我的世界开发模组的心得体会

最头疼的问题 本人也是小白,也就跟着ai学学怎么开发模组,不会的上网搜搜,但是目前最令我头疼的就是运行rundata和runcilent时的模块冲突,解决办法就是使用以下的build.gradle代码,不要接受人工智能的建议,…...

HTML:自闭合标签简单介绍

1. 什么是自结束标签&#xff1f; 定义&#xff1a;自结束标签&#xff08;Self-closing Tag&#xff09;是指 不需要单独结束标签 的 HTML 标签&#xff0c;它们通过自身的语法结构闭合。语法形式&#xff1a; 在 HTML5 中&#xff1a;直接写作 <tag>&#xff0c;例如 …...