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

闲谈Promise

预备知识

  • 回调函数:当一个函数作为参数传入另一个函数中,并且它不会立刻执行,当满足一定条件之后,才会执行,这种函数称为回调函数。比如:定时器。
  • 异步任务:与之对应的概念是同步任务,同步任务是在主线程上排队执行,当前面的任务执行完成,才会执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完成不影响下一个任务的指向。简单理解就是同步要按代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。

为什么出现Promise

存在异步任务的代码,不能保证代码按照顺序执行,但在特定的需要下,就是需要在异步中实现顺序执行,这时常使用回调函数去实现,比如下面的代码:

setTimeout(()=>{console.log('找实习');setTimeout(()=>{console.log('面试通过');setTimeout(()=>{console.log('拿offer');setTimeout(()=>{console.log('上班');},2000)},1000)},1000)},2000)

执行结果:

虽然上面的代码能实现我们想要的结果,但一些情形下,需要嵌套更多回调函数时,多层嵌套让代码可读性非常差,后期的维护或者异常处理等都变得特别繁琐,让缩进格式也变得非常麻烦。

回调函数A中嵌套回调函数B,回调函数B中嵌套回调函数C.......这种回调函数多层嵌套的情况被称为回调地狱。回调地狱就是为了实现在异步任务中代码也能顺序执行而出现的一种操作。

为解决回调地狱带来的问题,ES6新增了Promise类,让我们能更优雅的书写复杂的异步任务。

Promise

先用一段代码认识一下Promise:

new Promise((resolve,reject)=>{setTimeout(()=>{resolve('3秒后执行')},3000)
}).then(res=>{console.log(res);})
  • 是一个类,需要使用new Promise来创建实例对象。
  • promise有三种状态:pending(待定),fulfilled(已兑现),rejected(已拒绝)。fulfilled和rejected也可以称为已敲定。promise初始状态是pending,promise的状态转换有两条路:一条pending->fulfilled,一条pending->rejected。状态一旦改变,就不能再发生变化。
  • 调用resolve()方法,pending状态会变成fulfilled状态,并且触发then()中第一个回调方法;调用reject()方法,pending状态会变成rejected状态,并且触发then()中第二个回调方法。
  • promise有三个实例方法,then、catch、finally。其中catch和finally实际都是在调用then方法,只是它们往then中传递的参数不一样。
    catch : then(undefined,reject的回调方法)
    finally : then(A回调,A回调)
  • promise的三个实例方法和六个静态方法都是返回一个新的promise。六个静态方法为:resolve、reject、race、all、allSettled、any。其中race、all、allSettled、any将一个 Promise 可迭代对象作为输入(也可以简单理解成:传递数组作为参数)。

promise的实例方法

下面会对三个实例方法进行重写,对于相同的代码进行了提取:

      // 将方法放到任务队列中的方法function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}
then方法

​then() 方法最多接受两个参数:用于 Promise 兑现和拒绝情况的回调函数。它返回一个 Promise 对象。

手写then方法:

    then(onFulfilled, onRejected) {// 参数判断onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected = typeof onRejected === "function" ? onRejected : (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}
catch方法

catch() :在 promise 被拒绝时调用catch()里的函数;返回一个 Promise 对象。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。

 catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);
}
finally方法

finally() :用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数,无论是兑现还是拒绝,都是调用同一个函数。

 finally(onFinally) {return this.then(onFinally, onFinally);}

promise的静态方法

resolve方法

Promise.resolve() :将给定的值转换为一个 Promise。如果这个值本身就是promise,则直接返回这个值(就算这个promise的状态是已拒绝的);其它值就封装成promise,返回的 Promise 将会以该值兑现。

static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});
}
reject方法

reject():返回一个已拒绝的 Promise 对象,拒绝原因 是给定的参数。

static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});
}
race方法

race():等待第一个敲定;返回一个 Promise,这个返回的 promise 会随着第一个 promise 的敲定而敲定。

static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});
}
allSettled方法

 allSettled():等待全部敲定;当数组中的每一个都已敲定时,返回的 Promise 将被兑现,并带有描述每个 Promise 结果的对象数组。

   static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}
all方法

all():两种情况:

  • 当所有输入的 Promise 都被兑现时,返回的 Promise 也将被兑现(即使传入的是空数组),并返回一个包含所有兑现值的数组。
  • 如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
    static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {// 对数组中的每一项使用resolve,可以保证每一项都是promise,所以每一项都可以使用thenQPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}
any方法

any():两种情况:

  • 当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。
  • 当所有输入 Promise 都被拒绝(包括空数组)时,它会以一个包含拒绝原因数组的AggregateError 拒绝。
  static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}

手写Promise

手写promise的完整代码

// 三个状态const PENDING = "pending";const FULFILLED = "fulfilled";const REJECTED = "rejected";// 定义QPromise类class QPromise {// 初始状态state = PENDING;// 初始原因result = undefined;// 定义实例属性#handlers = [];// 构造函数constructor(fn) {const resolve = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = FULFILLED;this.result = result;// 调用成功回调this.#handlers.forEach(({ onFulfilled }) => {onFulfilled(this.result);});}};const reject = (result) => {// 状态不可逆if (this.state === PENDING) {// 修改状态this.state = REJECTED;this.result = result;// 调用失败回调this.#handlers.forEach(({ onRejected }) => {onRejected(this.result);});}};try {// 执行fn(resolve, reject);} catch (error) {reject(error);}}// then :将方法放到微任务队列中then(onFulfilled, onRejected) {// 参数判断onFulfilled =typeof onFulfilled === "function" ? onFulfilled : (x) => x;onRejected =typeof onRejected === "function"? onRejected: (x) => {throw x;};const p = new QPromise((resolve, reject) => {// 执行方法if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === REJECTED) {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});} else if (this.state === PENDING) {// 保存回调函数this.#handlers.push({onFulfilled: () => {runAsynctask(() => {try {const x = onFulfilled(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},onRejected: () => {runAsynctask(() => {try {const x = onRejected(this.result);judgmentFn(x, p, resolve, reject);} catch (error) {reject(error);}});},});}});return p;}// 实例方法catch(onRejected) {// 内部调用then方法return this.then(undefined, onRejected);}// 实例方法finally(onFinally) {return this.then(onFinally, onFinally);}// 静态方法static resolve(value) {// 如果是promise 直接返回if (value instanceof QPromise) {return value;}// 其他值,就直接封装成promise返回return new QPromise((res) => {res(value);});}static reject(value) {// 不管是什么值(包括 Promise 对象),都重新封装成promise返回return new QPromise((undefined, reject) => {reject(value);});}// 等待第一个敲定static race(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 等待第一个敲定(promise状态的敲定)promises.forEach((p) => {QPromise.resolve(p).then((res) => {resolve(res);},(err) => {reject(err);});});});}// 全部状态为成功,则返回一个数组// 若有一个失败,则处理失败static all(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 处理全部兑现// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = res;// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {// 处理第一个拒绝reject(err);});});});}// 等待全部状态敲定,不管是失败还是成功状态,都放在一个数组中返回static allSettled(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && resolve(promises);// 等待全部敲定// 记录结果const results = [];// 记录兑现次数let count = 0;promises.forEach((p, index) => {QPromise.resolve(p).then((res) => {// 用索引来添加元素,保证结果顺序和promise数组的顺序一致results[index] = { status: FULFILLED, value: res };// 判断全部兑现count++;// 通过兑现的次数进行判断,保证获取到所有的结果count === promises.length && resolve(results);},(err) => {results[index] = { status: REJECTED, reason: err };count++;count === promises.length && resolve(results);});});});}// 返回第一个状态兑现(成功)的结果// 全部拒绝时,返回拒绝的结果数组static any(promises) {// 返回promisereturn new QPromise((resolve, reject) => {// 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError("Argument is not iterable"));}// 空数组直接兑现promises.length === 0 && reject(new AggregateError(promises,'All promises were rejected'))// 等待结果const errors=[]let count=0promises.forEach((p,index)=>{QPromise.resolve(p).then(res=>{// 第一个兑现resolve(res)},err=>{// 全部拒绝errors[index]=errcount++count===promises.length && reject(new AggregateError(errors,'All promises were rejected'))})})});}}// 异步函数function runAsynctask(callback) {if (typeof queueMicrotask === "function") {queueMicrotask(callback);} else if (typeof MutationObserver === "function") {// 创建观察器const obs = new MutationObserver(callback);// 创建元素,添加监听const divNode = document.createElement("div");// 参数1 观察的dom节点// 参数2 观察的选项(childList 观察子节点改变)obs.observe(divNode, { childList: true });// 修改元素内容divNode.innerText = "999999";} else {setTimeout(callback, 0);}}// 对then不同的返回值进行不同操作function judgmentFn(x, p, resolve, reject) {if (x === p) {throw new TypeError("Chaining cycle detected for promise #<Promise>");}if (x instanceof QPromise) {x.then((res) => resolve(res),(err) => reject(err));} else {resolve(x);}}

如果本文对你有帮助,希望能得到你的点赞或收藏或关注,这是对我最好的鼓励;

如你有问题或疑惑,欢迎在评论区写下,必将努力解答;

如本文有误区,希望你不吝赐教,让我们共勉!

相关文章:

闲谈Promise

预备知识 回调函数&#xff1a;当一个函数作为参数传入另一个函数中&#xff0c;并且它不会立刻执行&#xff0c;当满足一定条件之后&#xff0c;才会执行&#xff0c;这种函数称为回调函数。比如&#xff1a;定时器。异步任务&#xff1a;与之对应的概念是同步任务&#xff0…...

【C++堆(优先队列)】1882. 使用服务器处理任务|1979

本文涉及知识点 C堆(优先队列) LeetCode1882. 使用服务器处理任务 给你两个 下标从 0 开始 的整数数组 servers 和 tasks &#xff0c;长度分别为 n​​​​​​ 和 m​​​​​​ 。servers[i] 是第 i​​​​​​​​​​ 台服务器的 权重 &#xff0c;而 tasks[j] 是处理…...

VBA高级应用30例应用3Excel中的ListObject对象:选择表的一部分

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…...

C语言-变量

全局变量可以定义在头文件中吗&#xff1f; 在C和C编程中&#xff0c;全局变量可以定义在头文件中&#xff0c;但通常不建议这样做&#xff0c;因为这可能导致多个源文件&#xff08;.c 或 .cpp 文件&#xff09;包含同一个头文件时&#xff0c;发生多重定义错误&#xff08;m…...

linux下位机出现使用TCP socket为0的问题

问题现象&#xff1a;下位机做TCP服务器&#xff0c;上位机来连接下位机的TCP服务&#xff0c;中间会有主动断开&#xff08;上位机主动关闭socket&#xff09;和异常断开&#xff08;网线断开&#xff09;的情况&#xff0c;出现异常的时候&#xff0c;上位机连接下位机的TCP …...

论文笔记:Prototypical Verbalizer for Prompt-based Few-shot Tuning

论文来源&#xff1a;ACL 2022 论文地址&#xff1a;https://arxiv.org/pdf/2203.09770.pdfhttps://arxiv.org/pdf/2203.09770.pdf 论文代码&#xff1a;https://github.com/thunlp/OpenPrompthttps://github.com/thunlp/OpenPrompt Abstract 基于提示的预训练语言模型&#…...

nn.functional.softmax(X, dim=-1)

dim-1表示在最后一个维度&#xff08;大概率是一行&#xff09;应用Softmax函数&#xff0c;将值标准化为概率分布。 实例 假设我们有一个张量X&#xff0c;形状为&#xff08;2&#xff0c;3&#xff09;&#xff0c;内容如下&#xff1a; import torch import torch.nn.…...

【动态规划】子数组系列(上)

1. 最大子数组和 53. 最大子数组和 状态表示&#xff1a;以 i 位置为结尾时的所有子数组中的最大和 状态转移方程&#xff1a; i 位置为结尾的子数组又可以分为长度为 1 的和大于 1 的&#xff0c;长度为 1 就是 nums[i] &#xff0c;长度不为 1 就是 dp[i - 1] nums[i]&…...

字节青训营入门算法题:飞行棋分组

链接&#xff1a;飞行棋分组&#x1f517;&#x1f517; 题目 现在有一堆飞行棋棋子&#xff0c;每个棋子上标有数字序号。需要将这些棋子分成若干组&#xff0c;每组包含5个棋子&#xff0c;且组内所有棋子的数字序号必须相同。需要判断是否可以完成这样的分组。 解答 为了…...

# 执行 rpm -qa | grep qq 查询软件安装情况时报错 数据库损坏 db3 error(-30974)

执行 rpm -qa | grep qq 查询软件安装情况时报错 数据库损坏 db3 error(-30974) 一、问题描述&#xff1a; 在 linux 系统上&#xff0c;使用包管理工具 rpm 查询某一个软件安装情况&#xff0c;如&#xff1a;执行 rpm -qa | grep qq 时&#xff0c;报错 数据库损坏 db3 err…...

离线服务器上复现G3SR论文实验

代码地址:https://github.com/AllminerLab/Code-for-G3SR-master 论文地址:https://ieeexplore.ieee.org/abstract/document/9741079/ 因为直接按照作者的方法操作会出现问题,故笔者在这里记录一下的实验过程。 实验环境 python=3.6 pytorch pytorch的下载命令需要自行前往…...

Android 未来可能支持 Linux 应用,Linux 终端可能登陆 Android 平台

近日&#xff0c;根据 android authority 的消息&#xff0c;Google 正在开发适用于 Android 的 Linux 终端应用&#xff0c;而终端应用可以通过开发人员选项启用&#xff0c;并将 Debian 安装在虚拟机中。 在几周前&#xff0c;Google 的工程师开始为 Android 开发新的 Termi…...

PostgreSQL学习笔记十四:PL/Python自定义函数

在 PostgreSQL 中可以使用 PL/Python 语言来创建自定义函数。以下是一个示例步骤&#xff1a; 一、创建自定义函数 连接到 PostgreSQL 数据库&#xff0c;可以使用 psql 命令行工具或者通过数据库管理工具。 执行以下 SQL 语句创建一个简单的 PL/Python 函数&#xff1a; C…...

计算机毕业设计 | springboot商城售后管理系统 购物平台(附源码)

1&#xff0c;绪论 1.1 开发背景 在数字化时代的推动下&#xff0c;产品售后服务管理机构面临着信息化和网络化的挑战。传统的手工管理和纸质档案已经无法满足管理人员和读者的需求。为了提高产品售后服务管理机构的管理效率和服务质量&#xff0c;开发和实现一个基于Java的售…...

(全网独家)面试要懂运维真实案例:HDFS重新平衡(HDFS Balancer)没触发问题排查

在面试时&#xff0c;面试官为了考察面试者是否真的有经验&#xff0c;经常会问运维集群时遇到什么问题&#xff0c;解决具体流程。下面是自己遇到HDFS Balancer没执行&#xff0c;花了半天时间进行排查&#xff0c;全网独家的案例和解决方案。 目录 使用CDH自带重新平衡操作…...

【数据结构笔记】搜索树

二叉搜索树 任一节点x的左/右子树中&#xff0c;所有非空节点均不大于&#xff08;不小于&#xff09;x 必须是所有的非空节点&#xff0c;仅左右孩子不够&#xff08;左孩子的右孩子可能很大&#xff09;一棵二叉树是二叉搜索树当且仅当中序遍历序列是单调非降序列 两棵二叉…...

如何使用UART(STM32 HAL库)

UART &#xff08;通用异步收发器&#xff09;是在 USART &#xff08;通用同步异步收发器&#xff09;基础上裁剪掉了同步通信功能&#xff0c;只剩下异步通信功能。关于通信和串口的基本知识&#xff0c;可参见文章《串口通信简介-CSDN博客》和《数据通信的一些基础概念-CSDN…...

星巴克英语

用流利的英文点星巴克 一杯咖啡 英文中文英文中文barista咖啡师coffee maker家用咖啡机cup sleeve杯套coffee stirrer咖啡棒coffee cup lid咖啡杯盖子straw吸管latte art咖啡拉花for here内用to go外带 例句&#xff1a; Could I have a cup sleeve for my coffee , please…...

权重衰减与暂退法——paddle部分

权重衰减与暂退法——paddle部分 本文部分为paddle框架以及部分理论分析&#xff0c;torch框架对应代码可见权重衰减与暂退法torch import paddle print("paddle version:",paddle.__version__)paddle version: 2.6.1当我们谈论机器学习模型的性能时&#xff0c;经…...

golang获取当天最小的时间,以DateTime的string格式返回

推荐学习文档 golang应用级os框架&#xff0c;欢迎stargolang应用级os框架使用案例&#xff0c;欢迎star案例&#xff1a;基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识&#xff0c;这里有免费的golang学习笔…...

2025 - 中医学基础 - 考研 - 职称

2025 - 中医学基础 - 考研 - 职称 第1章 中医学导论 1.中医学的指导思想是&#xff08;&#xff09;( ) [单选] A&#xff0e;阴阳学说 B&#xff0e;五行学说 C&#xff0e;精气学说 D&#xff0e;整体观念 E&#xff0e;辨证论治 正确答案: D 2.中医学的理论核心是&…...

Pandas库

一、安装 Pandas是一个基于Python构建的专门进行数据操作和分析的开源软件库&#xff0c;它提供了高效的数据结构和丰富的数据操作工具。 安装 pip install pandas 二、核心数据结构 Pandas库中最常用的数据类型是Series和DataFrame&#xff1a; Series&#xff1a;一维数…...

Qt网络编程: 构建高效的HTTP文件下载器

文章目录 注意事项调用示例在使用Qt进行HTTP下载时,通常会使用QNetworkAccessManager类来管理HTTP请求和响应。这个类提供了进行网络请求的能力,包括下载文件。下面是使用Qt进行HTTP下载的一个示例,以及在实现时应考虑的一些注意事项。 注意事项 1.错误处理 始终检查QNetwo…...

Python 将Word, Excel, PDF和PPT文档转换为OFD格式

目录 使用工具 Python 将Word文档转换为OFD Python 将Excel文档转换为OFD Python 将PDF文档转换为OFD Python 将PPT文档转换为OFD OFD&#xff08;Open Fixed-layout Document&#xff09;是中国国家标准的电子文档格式&#xff0c;主要用于政府、金融等行业的正式文档传输…...

QD1-P21-P22 CSS 基础语法、注释、使用方法

本节学习&#xff1a;CSS 基础语法和注释&#xff0c;以及如何使用CSS定义的样式。 本节视频 https://www.bilibili.com/video/BV1n64y1U7oj?p21 CSS 基本语法 CSS&#xff08;层叠样式表&#xff09;的基本语法相对简单&#xff0c;由选择器和一组包含在花括号 {}​ 中的声…...

您是否也在寻找免费的 PDF 编辑器工具?10个备选PDF 编辑器工具

您是否也在寻找免费的 PDF 编辑器工具&#xff1f; 如果是&#xff0c;那么您在互联网上处于最佳位置&#xff01; 本指南中提到的所有 10 大免费 PDF 编辑器工具都易于使用&#xff0c;可以允许您添加文本、更改图像、添加图形、填写表格、添加签名等等。 因此&#xff0c;…...

C++调试方法(Vscode)(一) ——本地调试

初学者在调试一段代码的时候&#xff0c;经常出于不明原因&#xff0c;写出bug&#xff0c;导致程序崩溃。但是定位崩溃的地方时&#xff0c;往往采用简单而朴素的方法&#xff1a;即采用cout或者printf进行输出。这种方式既原始&#xff0c;又低效。一个合格的工程师应该是通过…...

C语言 | Leetcode C语言题解之第460题LFU缓存

题目&#xff1a; 题解&#xff1a; /* 数值链表的节点定义。 */ typedef struct ValueListNode_s {int key;int value;int counter;struct ValueListNode_s *prev;struct ValueListNode_s *next; } ValueListNode;/* 计数链表的节点定义。 其中&#xff0c;head是数值链表的头…...

【AI论文精读12】RAG论文综述2(微软亚研院 2409)P4-隐性事实查询L2

AI知识点总结&#xff1a;【AI知识点】 AI论文精读、项目、思考&#xff1a;【AI修炼之路】 P1&#xff0c;P2&#xff0c;P3 四、隐性事实查询&#xff08;L2&#xff09; 4.1 概述 ps&#xff1a;P2有四种查询&#xff08;L1&#xff0c;L2&#xff0c;L3&#xff0c;L4&…...

SpringBoot中间件Docker

Docker&#xff08;属于C/S架构软件&#xff09; 简介与概述 1.Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux …...