Promise 重写 (第一部分)

学习关键语句:
promise 重写

写在前面

重新学习了怎么重写 promise , 我觉得最重要的就是要有思路,不然有些 A+ 规范是完全想不到的

开始

重写函数的过程中, 最重要的是有思路
我们从哪里获取重写思路? 从正常的代码中
我们先看正常的代码

const p1 = new Promise((resolve, reject) => {resolve();console.log("1");
}).then(() => {console.log("2");
});

我们的思路就来了:

  1. promise 是一个类
  2. 初始化的时候传入的是一个函数, 函数里面有两个参数, resolve, reject
  3. 这说明类的构造器中有这两个函数声明
  4. 实例化后的每一个 promise 都有自己的状态, 说明有实例属性 state

想到这里马上开始重写, 完成以上 1\2\3\4 点

// 1. promise 是一个类
class MyPromise {constructor(executor) {// 4. 实例化后的每一个 promise 都有自己的状态, 说明有实例属性 statethis.state = "pending";// 3. 这说明类的构造器中有这两个函数声明let resolve = () => {};let reject = () => {};// 2. 初始化的时候传入的是一个函数, 函数里面有两个参数, resolve, rejectexecutor(resolve, reject);}
}
let p = new MyPromise((resolve, reject) => {});

接下来我们知道 resolve 和 reject 函数中有参数 value 和 reason
所以我们马上就又有思路了:

  1. 执行器函数中调用 resolve 会传入参数, reject 同样也是
  2. 需要一个 then 方法来链式调用, 所以 then 方法是一个原型方法
  3. then 方法中有两个参数, 一个成功回调一个失败回调

完成以上 5\6\7 点

class MyPromise {constructor(executor) {this.state = "pending";// 5. 执行器函数中调用 resolve 会传入参数, reject 同样也是let resolve = (value) => {};let reject = (reason) => {};executor(resolve, reject);}// 6. 需要一个 then 方法来链式调用, 所以 then 方法是一个原型方法// 7. then 方法中有两个参数, 一个成功回调一个失败回调then(onFulfilled, onRejected) {}
}
let p = new MyPromise((resolve, reject) => {});

我们知道只有在构造器函数中调用了 resolve 或者 reject 方法后才会进入 then 方法, 同时我们也知道一旦调用 resolve 或者 reject 方法就会改变 promise 实例的状态, 所以我们接下来的思路就是:

  1. 调用 resolve 或者 reject 方法后改变状态
  2. 成功或者失败回调需要拿到对应的 resolve 和 reject 函数中的参数, 这需要我们在实例上新增相应的属性存储
  3. 在 resolve 和 reject 方法中要赋值给实例属性
  4. 在 then 方法中判断状态调用方法

完成以上 8\9\10\11 点

class MyPromise {constructor(executor) {this.state = "pending";// 9. 成功或者失败回调需要拿到对应的 resolve 和 reject 函数中的参数, 这需要我们在实例上新增相应的属性存储this.value = undefined;this.reason = undefined;let resolve = (value) => {// 8. 调用 resolve 或者 reject 方法后改变状态this.state = "fulfilled";// 10. 在 resolve 和 reject 方法中要赋值给实例属性this.value = value;};let reject = (reason) => {// 8. 调用 resolve 或者 reject 方法后改变状态this.state = "rejected";// 10. 在 resolve 和 reject 方法中要赋值给实例属性this.reason = reason;};executor(resolve, reject);}then(onFulfilled, onRejected) {// 11. 在 then 方法中判断状态调用方法if (this.state === "fulfilled") {onFulfilled(this.value);}if (this.state === "rejected") {onRejected(this.reason);}}
}
let p = new MyPromise((resolve, reject) => {});

好完成到这里的时候, 不涉及异步的操作我们已经完成了, 现在我们尝试使用一下我们重写的 promise

const p1 = new MyPromise2((resolve, reject) => {resolve(2);console.log("1");
}).then((value) => {console.log(value);
});

在这里插入图片描述

接下来考虑异步的问题, 即如果执行器函数中存在异步的话, 进入 then 方法时状态其实还是 pending , 所以需要考虑当进入 then 方法时状态为 pending 就是异步, 我们要将异步时候的回调函数保存起来当真正执行的时候再调用 , 由于同一个 promise 可以多次调用 then 方法 , 所以需要用一个数组来保存所有可能的回调函数 , 我们从这出发 , 给出以下思路:

  1. 在实例上添加属性来保存成功回调和失败回调
  2. 在 then 方法中遇到 pending 状态就是有异步情况 , 真正的执行回调将不在这里发生 , 存到实例的属性上

完成以上 12\13 点

class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;// 12. 在实例上添加属性来保存成功回调和失败回调this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;};let reject = (reason) => {this.state = "rejected";this.reason = reason;};executor(resolve, reject);}then(onFulfilled, onRejected) {if (this.state === "fulfilled") {onFulfilled(this.value);}if (this.state === "rejected") {onRejected(this.reason);}// 13. 在 then 方法中遇到 pending 状态就是有异步情况, 真正的执行回调将不在这里发生, 存到实例的属性上if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {onFulfilled(this.value);});this.onRejectedCallbacks.push(() => {onRejected(this.reason);});}}
}
let p = new MyPromise((resolve, reject) => {});

那么真正的回调函数执行到底在哪里呢? 我们来看一下一个出现了异步的情况代码, 看以下代码:

由于构造器函数中出现一个定时器, 所以会在 1 秒后才打印出 2

const p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve(2);}, 1000);console.log("1");
}).then((value) => {console.log(value);
});

上面这个代码中能执行回调函数的地方只有在 resolve 函数中了, 所以我们就又有思路了:

  1. 在 resolve 和 reject 方法中执行回调函数

完成以上 14 点

class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;// 14. 在 resolve 和 reject 方法中执行回调函数this.onFulfilledCallbacks.forEach((fn) => fn());};let reject = (reason) => {this.state = "rejected";this.reason = reason;// 14. 在 resolve 和 reject 方法中执行回调函数this.onRejectedCallbacks.forEach((fn) => fn());};executor(resolve, reject);}then(onFulfilled, onRejected) {if (this.state === "fulfilled") {onFulfilled(this.value);}if (this.state === "rejected") {onRejected(this.reason);}if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {onFulfilled(this.value);});this.onRejectedCallbacks.push(() => {onRejected(this.reason);});}}
}
let p = new MyPromise((resolve, reject) => {});

我们知道 promise 实例是可以链式调用的, 这意味每一个方法的最后都会返回一个新的 promise 来提供给下一次调用 promise 原型方法, 所以我们就考虑到在 then 方法中直接返回一个新的 promise 对象, 同时, 由于执行器函数执行是同步代码, 所以我们可以直接将 then 中的代码放入到新的 promise 中, 但是我们知道链式调用 then 方法时我们可以拿到上一次 then 返回的值, 所以在成功回调返回的值, 我们将再次使用新的 promise 中的 resolve 方法去调用, 以下就是这次的思路:

  1. 在 then 方法中新声明一个 promise 对象并返回, 将 then 方法中代码放入执行器函数中
  2. 将第一次 then 的回调的结果赋值给一个变量, 使用新声明的 promise 来调用 resolve 和 reject 方法

完成以上 15\16 点

class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;this.onFulfilledCallbacks.forEach((fn) => fn());};let reject = (reason) => {this.state = "rejected";this.reason = reason;this.onRejectedCallbacks.forEach((fn) => fn());};executor(resolve, reject);}then(onFulfilled, onRejected) {// 15. 在 then 方法中新声明一个 promise 对象并返回, 将 then 方法中代码放入执行器函数中const p2 = new MyPromise((resolve, reject) => {let x;if (this.state === "fulfilled") {// 16. 将第一次 then 的回调的结果赋值给一个变量, 使用新声明的 promise 来调用 resolve 方法x = onFulfilled(this.value);resolve(x);}if (this.state === "rejected") {// 16. 将第一次 then 的回调的结果赋值给一个变量, 使用新声明的 promise 来调用 resolve 方法x = onRejected(this.reason);reject(x);}if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {// 16. 将第一次 then 的回调的结果赋值给一个变量, 使用新声明的 promise 来调用 resolve 方法x = onFulfilled(this.value);resolve(x);});this.onRejectedCallbacks.push(() => {// 16. 将第一次 then 的回调的结果赋值给一个变量, 使用新声明的 promise 来调用 resolve 方法x = onRejected(this.reason);reject(x);});}});return p2;}
}
let p = new MyPromise((resolve, reject) => {});

好的这样就已经完成了链式调用的问题了, 但是现在又有一个新的问题了, 那就是如果第一次的回调函数返回的是一个 promise 对象呢? 那就需要对 x 进行判断和处理, 我们思路如下:

  1. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断
  2. 由于 p2 此时没有初始化完毕所以无法调用, 使用定时器包裹延后执行
  3. 由于 resolvePromise 中可能会抛出错误, 所以使用 try.catch 包裹
  4. 同样的, 在构造器中, 对执行器函数使用 try.catch 包裹

完成以上 17\18\19\20 点

class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;this.onFulfilledCallbacks.forEach((fn) => fn());};let reject = (reason) => {this.state = "rejected";this.reason = reason;this.onRejectedCallbacks.forEach((fn) => fn());};// 20. 同样的, 在构造器中, 对执行器函数使用 try.catch 包裹try {executor(resolve, reject);} catch (err) {reject(err);}}then(onFulfilled, onRejected) {const p2 = new MyPromise((resolve, reject) => {let x;if (this.state === "fulfilled") {// 18. 由于 p2 此时没有初始化完毕所以无法调用, 使用定时器包裹延后执行setTimeout(() => {// 19. 由于 resolvePromise 中可能会抛出错误, 所以使用 try.catch 包裹try {x = onFulfilled(this.value);// 17. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "rejected") {// 18. 由于 p2 此时没有初始化完毕所以无法调用, 使用定时器包裹延后执行setTimeout(() => {// 19. 由于 resolvePromise 中可能会抛出错误, 所以使用 try.catch 包裹try {x = onRejected(this.reason);// 17. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {// 18. 由于 p2 此时没有初始化完毕所以无法调用, 使用定时器包裹延后执行setTimeout(() => {// 19. 由于 resolvePromise 中可能会抛出错误, 所以使用 try.catch 包裹try {x = onFulfilled(this.value);// 17. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});this.onRejectedCallbacks.push(() => {// 18. 由于 p2 此时没有初始化完毕所以无法调用, 使用定时器包裹延后执行setTimeout(() => {// 19. 由于 resolvePromise 中可能会抛出错误, 所以使用 try.catch 包裹try {x = onRejected(this.reason);// 17. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});}});return p2;}
}
// 17. 声明一个 resolvePromise 专门处理判断 x 的问题, 根据 promise A+ 规范, 返回的 promise 不能与新增的 promise 相同, 在 resolvePromise 方法中需要传入 p2 进行判断
function resolvePromise(p2, x, resolve, reject) {if (p2 === x) {return reject(new Error("Error: p2 is x"));}
}
let p = new MyPromise((resolve, reject) => {});

接下来我们要继续完善 resolvePromise 中的逻辑, 我们需要判断 x 到底是不是 promise 对象, 我们使用是否是带 then 属性的对象来判断, 是就是, 不是就不是, 当是 promise 对象后, 直接调用 x 的 then 方法, 在成功回调和失败回调中分别调用 resolvePromise 传入的 resolve 和 reject

  1. x 需要是个对象或者函数
  2. x 为普通数据类型就直接 resolve
  3. 获取 x 的 then 属性, 是函数才是 promise 对象
  4. 获取 x.then 可能会报错, 使用 try.catch 包裹

完成以上 21\22\23\24 点

class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;this.onFulfilledCallbacks.forEach((fn) => fn());};let reject = (reason) => {this.state = "rejected";this.reason = reason;this.onRejectedCallbacks.forEach((fn) => fn());};try {executor(resolve, reject);} catch (err) {reject(err);}}then(onFulfilled, onRejected) {const p2 = new MyPromise((resolve, reject) => {let x;if (this.state === "fulfilled") {setTimeout(() => {try {x = onFulfilled(this.value);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "rejected") {setTimeout(() => {try {x = onRejected(this.reason);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {x = onFulfilled(this.value);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});this.onRejectedCallbacks.push(() => {setTimeout(() => {try {x = onRejected(this.reason);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});}});return p2;}
}
function resolvePromise(p2, x, resolve, reject) {if (p2 === x) {return reject(new Error("Error: p2 is x"));}// 21. x 需要是个对象或者函数if ((typeof x === "object" && x !== null) || typeof x === "function") {// 24. 获取 x.then 可能会报错, 使用 try.catch 包裹try {// 23. 获取 x 的 then 属性, 是函数才是 promise 对象let then = x.then;if (typeof then === "function") {then.call(x,(y) => {resolve(y);},(r) => {reject(r);});} else {resolve(x);}} catch (err) {reject(err);}} else {// 22. x 为普通数据类型就直接 resolveresolve(x);}
}
let p = new MyPromise((resolve, reject) => {});

好, 这样看起来已经很完美了, 返回的是 promise 对象我们也能解决了, 但是紧接着又有一个问题, 要是里面的 promise 再返回一个 promise 呢? 我们就想到用递归来解决这个问题, 同时我们还要解决一个问题, 那就是一个 promise 的执行器函数中只会执行遇到的第一个 resolve 或者 reject 方法, 我们用新增变量来避免重复调用

  1. 当每次返回的都是 promise 对象时, 我们递归调用判断 x 的类型的函数
  2. 声明一个变量来防止构造器函数中重复执行 resolve 和 reject 方法
  3. 每次进入 then 方法时, 如果没有参数, 就直接返回一个返回参数的函数
class MyPromise {constructor(executor) {this.state = "pending";this.value = undefined;this.reason = undefined;this.onFulfilledCallbacks = [];this.onRejectedCallbacks = [];let resolve = (value) => {this.state = "fulfilled";this.value = value;this.onFulfilledCallbacks.forEach((fn) => fn());};let reject = (reason) => {this.state = "rejected";this.reason = reason;this.onRejectedCallbacks.forEach((fn) => fn());};try {executor(resolve, reject);} catch (err) {reject(err);}}then(onFulfilled, onRejected) {// 27. 每次进入 then 方法时, 如果没有参数, 就直接返回一个返回参数的函数onFulfilled =typeof onFulfilled === "function" ? onFulfilled : (value) => value;onRejected =typeof onRejected === "function"? onRejected: (reason) => {throw reason;};const p2 = new MyPromise((resolve, reject) => {let x;if (this.state === "fulfilled") {setTimeout(() => {try {x = onFulfilled(this.value);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "rejected") {setTimeout(() => {try {x = onRejected(this.reason);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);}if (this.state === "pending") {this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {x = onFulfilled(this.value);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});this.onRejectedCallbacks.push(() => {setTimeout(() => {try {x = onRejected(this.reason);resolvePromise(p2, x, resolve, reject);} catch (err) {reject(err);}}, 0);});}});return p2;}
}
function resolvePromise(p2, x, resolve, reject) {// 26. 声明一个变量来防止构造器函数中重复执行 resolve 和 reject 方法let called = false;if (p2 === x) {return reject(new Error("Error: p2 is x"));}if ((typeof x === "object" && x !== null) || typeof x === "function") {try {let then = x.then;if (typeof then === "function") {then.call(x,(y) => {if (called) return;called = true;// 25. 当每次返回的都是 promise 对象时, 我们递归调用判断 x 的类型的函数resolvePromise(p2, y, resolve, reject);},(r) => {if (called) return;called = true;reject(r);});} else {resolve(x);}} catch (err) {if (called) return;called = true;reject(err);}} else {resolve(x);}
}
let p = new MyPromise((resolve, reject) => {});

总结

ok 这样就重写完了最基本的 promise , 之后我们再来补充上 promise 上面的方法包括 catch \ race \ all 等等

我觉得虽然挺难的 , 但是如果你有思路的话,顺着思路写下来其实是没问题的

结束

学习之路漫漫啊~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/143998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

『GitHub项目圈选02』一款可实现视频自动翻译配音为其他语言的开源项目

🔥🔥🔥本周GitHub项目圈选****: 主要包含视频翻译、正则填字游戏、敏感词检测、聊天机器人框架、AI 换脸、分布式数据集成平台等热点项目。 1、pyvideotrans pyvideotrans 是一个视频翻译工具,可将一种语言的视频翻译为另一种语…

学习c#的第十三天

目录 C# 多态性 静态多态性 函数重载 运算符重载 动态多态性 virtual 和 abstract 抽象方法和虚方法的区别 重载(overload)和重写(override) 隐藏方法 C# 多态性 多态是同一个行为具有多个不同表现形式或形态的能力。 多态性意味着有多重形式。在面向对象编程范式中…

Postman的Cookie鉴权

近期在复习Postman的基础知识,在小破站上跟着百里老师系统复习了一遍,也做了一些笔记,希望可以给大家一点点启发。 一)什么是Cookie 定义:存储在客户端的一小段文本信息,格式为键值对的形式. 二&#xff09…

Leetcode刷题详解——岛屿数量

1. 题目链接:200. 岛屿数量 2. 题目描述: 给你一个由 1(陆地)和 0(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上…

汽车OBD2蓝牙诊断仪解决方案程序开发

1、因TL718已经为你建立了物理层、数据链层和部分应用层的协议,所以只要OBD2标准应用层协议文本,ISO15031-5 或 SAE J1979(这两个协议是相同的内容)。 2、TL718诊断接口 1 套或用TL718芯片自建电路。3、家用PC机电脑一台。4、安…

计算机网络——物理层-编码与调制(数字基带信号、模拟基带信号、码元、常用编码、基本调制方法、混合调制)

目录 编码与调制 数字基带信号 模拟基带信号 码元 常用编码 不归零编码 归零编码 曼彻斯特编码 差分曼彻斯特编码 编码习题 基本调制方法 调幅 调频 调相 混合调制 QAM-16 编码与调制 在计算机网络中,计算机需要处理和传输用户的文字、图片、音频…

深度学习AI识别人脸年龄

以下链接来自 落痕的寒假 GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise https://download.csdn.net/download/luohenyj/10993309 import cv2 as cv import time import argparsedef getFaceBox(net, frame, conf_threshold0.7):frameOpencvDn…

结构工程师软件 Naviate Core MEP for Revit 3.4 Crk

Naviate Fabrication - 先进的建模和制造命令,可提高 VDC 设计师、细节设计师和承包商的生产力和收入。 Naviate MEP - 通过 MEP 工程师和设计师的建模和参数提高效率 导航架构 Naviate Architecture 完全集成到 Revit 平台中,增强了 BIM 提供的协作可能…

C++标准模板(STL)- 类型支持 (属性查询,获取类型的对齐要求)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

使用JAVA pdf转word

使用spire.pdf 非常简单。 查看 https://mvnrepository.com/artifact/e-iceblue/spire.pdf 注意&#xff0c;这个包在 e-iceblue 下。 下面开始撸代码 先来pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://mav…

大数据-之LibrA数据库系统告警处理(ALM-12047 网络读包错误率超过阈值)

告警解释 系统每30秒周期性检测网络读包错误率&#xff0c;并把实际错误率和阈值&#xff08;系统默认阈值0.5%&#xff09;进行比较&#xff0c;当检测到网络读包错误率连续多次&#xff08;默认值为5&#xff09;超过阈值时产生该告警。 用户可通过“系统设置 > 阈值配置…

微服务架构演进

系统架构演变 没有最好的架构&#xff0c;只有最合适的架构&#xff1b;架构发展过程&#xff1a;单体架构》垂直架构》SOA 面向服务架构》微服务架构&#xff1b;推荐看看《淘宝技术这十年》&#xff1b; 单体架构 互联网早期&#xff0c;一般的网站应用流量较小&#xff0…

keepalived安装配置(服务器主备、负载均衡)

系统拓扑 安装keepalived 主备服务器上都需要安装 在线安装 yum install -y keepalived 离线安装 # todo 服务器准备 虚拟机ip&#xff1a;192.168.11.56 主服务器&#xff1a;192.168.11.53 备服务器&#xff1a;192.168.11.54 配置文件修改 keepalived安装之后&…

接口

文章目录 概述语法使用特性接口的继承抽象类和接口的区别 概述 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘…所有符合USB协议的设备 电源插座插孔上&#xff0c;可以插&#xff1a;电脑、电视机、电饭煲…所有符合规范的设备 通过上述例子可以看出&#xff…

【Rust】快速教程——从hola,mundo到所有权

前言 学习rust的前提如下&#xff1a; &#xff08;1&#xff09;先把Rust环境装好 &#xff08;2&#xff09;把VScode中关于Rust的插件装好 \;\\\;\\\; 目录 前言先写一个程序看看Rust的基础mut可变变量let重定义覆盖变量基本数据类型复合类型&#xff08;&#xff09;和 [ …

pg_bouncer在使用中的坑勿踩

目录 简介 环境信息 问题配置 问题配置 启动pgbouncer 链接逻辑图 测试存在问题 pgadmin4 Idea JAVA调用 ​编辑 dbeaver 建议&#xff1a; 简介 前面文章说过关于pg_bouncer的安装讲解&#xff0c;这里讲一下在使用中的坑&#xff0c;在进行配置的时候需要注意。 …

系列三、双亲委派机制

一、概述 当一个类收到了类加载的请求&#xff0c;它首先不会尝试自己去加载这个类&#xff0c;而是把这个请求委派给父类去完成&#xff0c;每一层的类加载器都是如此&#xff0c;因此所有的请求都应该传送到启动类加载器中&#xff0c;只有当父类加载器反馈自己无法完成这个…

PowerPoint技巧:如何将一张图片同时加到全部幻灯片里?

想把一张图片加到PPT每一张幻灯片的同一个位置&#xff0c;如果一张一张的添加就太耗时间了&#xff0c;一起来看看如何利用母版快速设置同时添加吧。 首先&#xff0c;打开需要编辑的PPT&#xff0c;在菜单栏依次点击【视图】→【幻灯片母版】&#xff1b; 打开母版后&#x…

vue3实现数据大屏内数据向上滚动,鼠标进入停止滚动 vue3+Vue3SeamlessScroll

1.效果图 2.npm下载依赖及main.js文件配置 npm install vue3-seamless-scroll --saveimport vue3SeamlessScroll from vue3-seamless-scroll;app.use(vue3SeamlessScroll) 3.html代码 <!-- scrollFlag为true时再渲染,vue3只要涉及到传值子页面需要加flag判断&#xff0c;否…

【BIM入门实战】Revit图元的选择方式,总有一款适合你

Revit图元的五种常见选择方式,总有一款适合你。 文章目录 一、直接单击二、加选和减选三、连续框选四、按类别选择五、全选过滤选择操作可以在三维视图、平面视图等多种视图中进行。 一、直接单击 直接单击,即可选中某一个图元,如选择一个扶手。 二、加选和减选 按住ctrl键…