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

分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

2022年11月23日首发于掘金,现在同步到公众号。

11. 前言

大家好,我是若川。推荐点右上方蓝字若川视野把我的公众号设为星标。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推荐关注我写的专栏《学习源码整体架构系列》,目前是掘金关注人数(4.6k+人)第一的专栏,写有20余篇源码文章。


我们开发业务时经常会使用到组件库,一般来说,很多时候我们不需要关心内部实现。但是如果希望学习和深究里面的原理,这时我们可以分析自己使用的组件库实现。有哪些优雅实现、最佳实践、前沿技术等都可以值得我们借鉴。

相比于原生 JS 等源码。我们或许更应该学习,正在使用的组件库的源码,因为有助于帮助我们写业务和写自己的组件。

如果是 Vue 技术栈,开发移动端的项目,大多会选用 vant 组件库,目前(2022-11-20)star 多达 20.5k,最新版本是 v4.0.0-rc7。我们可以挑选 vant 组件库学习,我会写一个组件库源码系列专栏[1],欢迎大家关注。

  • vant 4 即将正式发布,支持暗黑主题,那么是如何实现的呢

  • 跟着 vant4 源码学习如何用 vue3+ts 开发一个 loading 组件,仅88行代码

  • 分析 vant4 源码,如何用 vue3 + ts 开发一个瀑布流滚动加载的列表组件?

这次我们来学习倒计时组件,`countdown`[2]

学完本文,你将学到:

1. 如何开发一个更优雅的毫秒级渲染的倒计时组件
2. 学会使用 requestAnimationFrame
3. 等等

22. 准备工作

看一个开源项目,我们可以先看 README.md[3] 再看 github/CONTRIBUTING.md[4]

2.1 克隆源码

You will need Node.js >= 14[5] and pnpm[6].

# 推荐克隆我的项目
git clone https://github.com/lxchuan12/vant-analysis
cd vant-analysis/vant# 或者克隆官方仓库
git clone git@github.com:vant-ui/vant.git
cd vant# 安装依赖,如果没安装 pnpm,可以用 npm i pnpm -g 安装,或者查看官网通过其他方式安装
pnpm i# 启动服务
pnpm dev

执行 pnpm dev 后,这时我们打开倒计时组件 http://localhost:5173/#/zh-CN/count-down

33. 倒计时组件可谓是十分常用

在各种电商类或者其他的移动端页面中,倒计时真的是太常见了。我们自己也基本能够快速的写一个倒计时组件。代码实现参考这里,主要是 JavaScript。码上掘金倒计时初步代码@若川[7]

代码中,我直接使用的 setInterval 和每秒钟执行一次。把倒计时的时候减去1s,当倒计时毫秒数不足时用 clearInterval 清除停止定时器。

但如果要实现毫秒级的倒计时这种方法行不通。 另外 setInterval 这种做法,并不是最优的。 那么,vant 倒计时组件中,是如何处理毫秒级和实现倒计时呢。

带着问题我们直接找到 countdown demo 文件:vant/packages/vant/src/count-down/demo/index.vue。为什么是这个文件,我在之前文章跟着 vant4 源码学习如何用 vue3+ts 开发一个 loading 组件,仅88行代码分析了其原理,感兴趣的小伙伴点击查看。这里就不赘述了。

44. 利用 demo 调试源码

组件源码中的 TS 代码我不会过多解释。没学过 TS 的小伙伴,推荐学这个TypeScript 入门教程[8]。 另外,vant 使用了 @vue/babel-plugin-jsx[9] 插件来支持 JSX、TSX

a5c311d5a99453468f26db714afef15a.png
倒计时组件
// vant/packages/vant/src/count-down/demo/index.vue
<script setup lang="ts">
import VanGrid from '../../grid';
import VanGridItem from '../../grid-item';
import VanCountDown, { type CountDownInstance } from '..';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site';
import { showToast } from '../../toast';const t = useTranslate({'zh-CN': {reset: '重置',pause: '暂停',start: '开始',finished: '倒计时结束',millisecond: '毫秒级渲染',customStyle: '自定义样式',customFormat: '自定义格式',manualControl: '手动控制',formatWithDay: 'DD 天 HH 时 mm 分 ss 秒',},
});const time = ref(30 * 60 * 60 * 1000);
const countDown = ref<CountDownInstance>();// 开始
const start = () => {countDown.value?.start();
};
// 暂停
const pause = () => {countDown.value?.pause();
};
// 重置
const reset = () => {countDown.value?.reset();
};
const onFinish = () => showToast(t('finished'));
</script><template><!-- 基本使用 --><demo-block :title="t('basicUsage')"><van-count-down :time="time" /></demo-block><!-- 自定义渲染 --><demo-block :title="t('customFormat')"><van-count-down :time="time" :format="t('formatWithDay')" /></demo-block><!-- 毫秒级渲染 --><demo-block :title="t('millisecond')"><van-count-down millisecond :time="time" format="HH:mm:ss:SS" /></demo-block><!-- 自定义样式--><demo-block :title="t('customStyle')"><van-count-down :time="time"><template #default="currentTime"><span class="block">{{ currentTime.hours }}</span><span class="colon">:</span><span class="block">{{ currentTime.minutes }}</span><span class="colon">:</span><span class="block">{{ currentTime.seconds }}</span></template></van-count-down></demo-block><!-- 手动控制 --><demo-block :title="t('manualControl')"><van-count-downref="countDown"millisecond:time="3000":auto-start="false"format="ss:SSS"@finish="onFinish"/><van-grid clickable :column-num="3"><van-grid-item icon="play-circle-o" :text="t('start')" @click="start" /><van-grid-item icon="pause-circle-o" :text="t('pause')" @click="pause" /><van-grid-item icon="replay" :text="t('reset')" @click="reset" /></van-grid></demo-block>
</template>

demo 文件中,我们可以看出 import VanCountDown, { type CountDownInstance } from '..';,引入自 vant/packages/vant/src/count-down/index.ts。我们继续来看入口 index.ts

55. 入口 index.ts

主要就是导出一下类型和变量等。

// vant/packages/vant/src/count-down/index.ts
import { withInstall } from '../utils';
import _CountDown from './CountDown';export const CountDown = withInstall(_CountDown);
// 默认导出
// import xxx from 'vant'
export default CountDown;
export { countDownProps } from './CountDown';
export type { CountDownProps } from './CountDown';
export type {CountDownInstance,CountDownThemeVars,CountDownCurrentTime,
} from './types';declare module 'vue' {export interface GlobalComponents {VanCountDown: typeof CountDown;}
}

withInstall 函数在之前文章5.1 withInstall 给组件对象添加 install 方法 也有分析,这里就不赘述了。

我们可以在这些文件,任意位置加上 debugger 调试源码。

截两张调试图。

调试 Countdown setup

55a26164d6cc349dd8ed40981ec6ffec.png
调试 setup

调试 useCountDown

3926d794d63b8cced88398df5997cf6a.png
调试 useCountDown

我们跟着调试,继续分析 Countdown

66. 主文件 Countdown

// vant/packages/vant/src/count-down/CountDown.tsx
import { watch, computed, defineComponent, type ExtractPropTypes } from 'vue';// Utils
import {truthProp,makeStringProp,makeNumericProp,createNamespace,
} from '../utils';
import { parseFormat } from './utils';// Composables
import { useCountDown } from '@vant/use';
import { useExpose } from '../composables/use-expose';const [name, bem] = createNamespace('count-down');export const countDownProps = {time: makeNumericProp(0),format: makeStringProp('HH:mm:ss'),autoStart: truthProp,millisecond: Boolean,
};export type CountDownProps = ExtractPropTypes<typeof countDownProps>;export default defineComponent({name,props: countDownProps,emits: ['change', 'finish'],setup(props, { emit, slots }) {// 代码省略,下文叙述},
});

6.1 setup 部分

这一部分主要使用了useCountDown

setup(props, { emit, slots }) {// useCountDown 组合式 APIconst { start, pause, reset, current } = useCountDown({// 传入的时间毫秒数,+ 是字符串转数字time: +props.time,// 毫秒级渲染millisecond: props.millisecond,// 回调事件,onChange, onFinishonChange: (current) => emit('change', current),onFinish: () => emit('finish'),});// 格式化时间const timeText = computed(() => parseFormat(props.format, current.value));// 重置,重新开始const resetTime = () => {reset(+props.time);if (props.autoStart) {start();}};watch(() => props.time, resetTime, { immediate: true });// 导出 start、pause、resetuseExpose({start,pause,reset: resetTime,});return () => (// 有传入插槽,使用插槽,支持自定义样式,传入解析后的时间对象<div role="timer" class={bem()}>{slots.default ? slots.default(current.value) : timeText.value}</div>);
},

6.2 useExpose 暴露

import { getCurrentInstance } from 'vue';
import { extend } from '../utils';// expose public api
export function useExpose<T = Record<string, any>>(apis: T) {const instance = getCurrentInstance();// 合并到 getCurrentInstance().proxy 上if (instance) {extend(instance.proxy as object, apis);}
}

通过 ref 可以获取到 Countdown 实例并调用实例方法,详见组件实例方法[10]

Vant 中的许多组件提供了实例方法,调用实例方法时,我们需要通过 ref 来注册组件引用信息,引用信息将会注册在父组件的 $refs 对象上。注册完成后,我们可以通过 this.$refs.xxx 或者

const xxxRef = ref();
xxxRef.value.xxx();

访问到对应的组件实例,并调用上面的实例方法。

77. useCountDown 组合式 API

7.1 parseTime 解析时间

// vant/packages/vant-use/src/useCountDown/index.ts
import {ref,computed,onActivated,onDeactivated,onBeforeUnmount,
} from 'vue';
import { raf, cancelRaf, inBrowser } from '../utils';export type CurrentTime = {days: number;hours: number;total: number;minutes: number;seconds: number;milliseconds: number;
};export type UseCountDownOptions = {time: number;// 毫秒millisecond?: boolean;onChange?: (current: CurrentTime) => void;onFinish?: () => void;
};const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;// 解析时间
function parseTime(time: number): CurrentTime {const days = Math.floor(time / DAY);const hours = Math.floor((time % DAY) / HOUR);const minutes = Math.floor((time % HOUR) / MINUTE);const seconds = Math.floor((time % MINUTE) / SECOND);const milliseconds = Math.floor(time % SECOND);return {total: time,days,hours,minutes,seconds,milliseconds,};
}

以上这大段代码,parseTime 是主要函数,解析时间,生成天数、小时、分钟、秒、毫秒的对象。

7.2 useCountDown 真实逻辑

真实逻辑这一段可以不用细看。可以调试时再细看。

主要就是利用 Date.now() 会自己走的原理。

初始化开始:结束时间 = 当前时间戳 + 剩余时间
获取:剩余时间 = 结束时间 - 当前时间戳
加上自己定时器逻辑循环
剩余时间就是真实流逝的时间
如果是毫秒级渲染,就直接赋值剩余时间
如果不是,那就判断是同一秒才赋值

设计的十分巧妙,看到这里,我们可能感慨:不得不佩服。

// 简化版 一
const useCountDown = (options) => {let endTime;let remain = options.time;const getCurrentRemain = () => Math.max(endTime - Date.now(), 0);const start = () => {endTime = Date.now() + remain;}const setRemain = (value) => {remain = value;};return {start,}
}
const { start } = useCountDown({time: 3 * 1000});
start();

码上掘金倒计时简化版二

码上掘金倒计时简化版二@若川[11]

// vant/packages/vant-use/src/useCountDown/index.ts
function isSameSecond(time1: number, time2: number): boolean {return Math.floor(time1 / 1000) === Math.floor(time2 / 1000);
}export function useCountDown(options: UseCountDownOptions) {let rafId: number;let endTime: number;let counting: boolean;let deactivated: boolean;const remain = ref(options.time);const current = computed(() => parseTime(remain.value));const pause = () => {counting = false;cancelRaf(rafId);};const getCurrentRemain = () => Math.max(endTime - Date.now(), 0);const setRemain = (value: number) => {remain.value = value;options.onChange?.(current.value);if (value === 0) {pause();options.onFinish?.();}};const microTick = () => {rafId = raf(() => {// in case of call reset immediately after finishif (counting) {setRemain(getCurrentRemain());if (remain.value > 0) {microTick();}}});};const macroTick = () => {rafId = raf(() => {// in case of call reset immediately after finishif (counting) {const remainRemain = getCurrentRemain();if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) {setRemain(remainRemain);}if (remain.value > 0) {macroTick();}}});};const tick = () => {// should not start counting in server// see: https://github.com/vant-ui/vant/issues/7807if (!inBrowser) {return;}if (options.millisecond) {microTick();} else {macroTick();}};const start = () => {if (!counting) {endTime = Date.now() + remain.value;counting = true;tick();}};const reset = (totalTime: number = options.time) => {pause();remain.value = totalTime;};// 组件被卸载之前被调用onBeforeUnmount(pause);// 激活onActivated(() => {if (deactivated) {counting = true;deactivated = false;tick();}});onDeactivated(() => {if (counting) {pause();deactivated = true;}});// 返回方法和当前时间对象return {start,pause,reset,current,};
}

我们继续来看 rafcancelRaf,是如何实现的。

88. raf、cancelRaf、inBrowser 实现

// 判断是不是浏览器环境,你可能会问,为啥要判断?因为 SSR (服务端渲染)不是浏览器环境。
export const inBrowser = typeof window !== 'undefined';// Keep forward compatible
// should be removed in next major version
export const supportsPassive = true;export function raf(fn: FrameRequestCallback): number {return inBrowser ? requestAnimationFrame(fn) : -1;
}export function cancelRaf(id: number) {if (inBrowser) {cancelAnimationFrame(id);}
}// double raf for animation
export function doubleRaf(fn: FrameRequestCallback): void {raf(() => raf(fn));
}

上文代码,主要一个 APIrequestAnimationFrame、cancelAnimationFrame

我们这里简单理解为 window.requestAnimationFrame() 中的回调函数,每 16.67ms 执行一次回调函数即可。

也就是类似 setTimeout、clearTimeout

const timeId = setTimeout( () => {// 16.67ms 执行一次console.log('16.67ms 执行一次');
}, 16.67);clearTimeout(timeId);

也可以自行搜索这个 API 查阅更多资料。比如 MDN 上的解释。

mdn window.requestAnimationFrame[12]

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

回调函数执行次数通常是每秒 60 次,但在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。

备注: 若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用 window.requestAnimationFrame()

99. 支持格式化时间,默认 HH:mm:ss

9.1 parseFormat 处理格式化

再来看看,组件中,是如何格式化时间的。这个值得我们参考。我们很多时候可能都是写死天数、小时等文案。不支持自定义格式化。

// vant/packages/vant/src/count-down/utils.ts
import { padZero } from '../utils';
import { CurrentTime } from '@vant/use';export function parseFormat(format: string, currentTime: CurrentTime): string {const { days } = currentTime;let { hours, minutes, seconds, milliseconds } = currentTime;// 有 DD 参数,补零替换,没有则小时数加上天数if (format.includes('DD')) {format = format.replace('DD', padZero(days));} else {hours += days * 24;}// 有 HH 参数,补零替换,没有则分钟数加上小时数if (format.includes('HH')) {format = format.replace('HH', padZero(hours));} else {minutes += hours * 60;}// 有 mm 参数,补零替换,没有则秒数加上分钟数if (format.includes('mm')) {format = format.replace('mm', padZero(minutes));} else {seconds += minutes * 60;}// 有 mm 参数,补零替换,没有则毫秒数加上秒数if (format.includes('ss')) {format = format.replace('ss', padZero(seconds));} else {milliseconds += seconds * 1000;}// 毫秒数 默认补三位数,按照格式最终给出对应的位数if (format.includes('S')) {const ms = padZero(milliseconds, 3);if (format.includes('SSS')) {format = format.replace('SSS', ms);} else if (format.includes('SS')) {format = format.replace('SS', ms.slice(0, 2));} else {format = format.replace('S', ms.charAt(0));}}// 最终返回格式化的数据return format;
}

9.2 padZero 补零

// vant/packages/vant-compat/node_modules/vant/src/utils/format.ts
// 补零操作
export function padZero(num: Numeric, targetLength = 2): string {let str = num + '';while (str.length < targetLength) {str = '0' + str;}return str;
}

行文自此,我们就分析完了毫秒级渲染的倒计时组件的实现。

1010. 总结

我们来简单总结下。通过 demo 文件调试,入口文件,主文件,useCountDown 组合式 API,插槽等。 分析了自定义格式、毫秒级渲染、自定义样式(利用插槽)等功能的实现。

其中毫秒级渲染,主要就是利用 Date.now() 和 (window.requestAnimationFrame)每 16.67ms 执行一次回调函数。

大致流程如下:

初始化开始:结束时间 = 当前时间戳 + 剩余时间
获取:剩余时间 = 结束时间 - 当前时间戳
加上自己定时器逻辑循环(`window.requestAnimationFrame`)每 16.67ms 执行一次回调函数
剩余时间就是真实流逝的时间
如果是毫秒级渲染,就直接赋值剩余时间
如果不是,那就判断是同一秒才赋值

看完这篇源码文章,再去看 CountDown 组件文档[13],可能就会有豁然开朗的感觉。再看其他组件,可能就可以猜测出大概实现的代码了。

如果是使用 reactTaro 技术栈,感兴趣也可以看看 taroify CountDown 组件的实现 文档[14],源码[15]

如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力

参考资料

[1]

组件库源码系列专栏: https://juejin.cn/column/7140264842954276871

[2]

countdown: https://vant-contrib.gitee.io/vant/v4/#/zh-CN/count-down

[3]

README.md: https://github.com/youzan/vant

[4]

github/CONTRIBUTING.md: https://github.com/youzan/vant/blob/main/.github/CONTRIBUTING.md

[5]

Node.js >= 14: https://nodejs.org

[6]

pnpm: https://pnpm.io

[7]

码上掘金倒计时初步代码@若川: https://code.juejin.cn/pen/7167966535649230883

[8]

TypeScript 入门教程: http://ts.xcatliu.com/

[9]

@vue/babel-plugin-jsx: https://www.npmjs.com/package/@vue/babel-plugin-jsx

[10]

组件实例方法: https://vant-contrib.gitee.io/vant/v4/#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa

[11]

码上掘金倒计时简化版二@若川: https://code.juejin.cn/pen/7168892330752081928

[12]

mdn window.requestAnimationFrame: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

[13]

CountDown 组件文档: https://vant-contrib.gitee.io/vant/#/zh-CN/count-down

[14]

文档: https://taroify.gitee.io/taroify.com/components/countdown

[15]

源码: https://github.com/mallfoundry/taroify/tree/main/packages/core/src/countdown

53f0207f060aeb90963528965a6fe078.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,持续组织了一年多源码共读活动,帮助5000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

299afe5f7ddfd85e6f3a274dd71a52ee.jpeg

扫码加我微信 lxchuan12、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 lxchuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

相关文章:

分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

2022年11月23日首发于掘金&#xff0c;现在同步到公众号。11. 前言大家好&#xff0c;我是若川。推荐点右上方蓝字若川视野把我的公众号设为星标。我倾力持续组织了一年多源码共读&#xff0c;感兴趣的可以加我微信 lxchuan12 参与。另外&#xff0c;想学源码&#xff0c;极力推…...

事件驱动型架构

事件驱动型架构是一种软件设计模式&#xff0c;其中微服务会对状态变化&#xff08;称为“事件”&#xff09;作出反应。事件可以携带状态&#xff08;例如商品价格或收货地址&#xff09;&#xff0c;或者事件也可以是标识符&#xff08;例如&#xff0c;订单送达或发货通知&a…...

20222023华为OD机试 - 不含 101 的数(Python)

不含 101 的数 题目 小明在学习二进制时,发现了一类不含 101 的数, 也就是将数字用二进制表示,不能出现 101 。 现在给定一个正整数区间 [l,r],请问这个区间内包含了多少个不含 101 的数? 输入 输入一行,包含两个正整数 l l l, r r r...

杭州电子科技大学2023年MBA招生考试成绩查询和复查申请的通知

根据往年的情况&#xff0c;2023杭州电子大学MBA考试初试成绩可能将于2月21日公布&#xff0c;最早于20号出来&#xff0c;为了广大考生可以及时查询到自己的分数&#xff0c;杭州达立易考教育为大家汇总了信息。根据教育部和浙江省教育考试院关于硕士研究生招生考试工作的统一…...

电子技术——CS和CE放大器的高频响应

电子技术——CS和CE放大器的高频响应 在绘制出MOS和BJT的高频响应模型之后&#xff0c;我们对MOS和BJT的高频响应有了进一步的认识。现在我们想知道的是在高频响应中 fHf_HfH​ 的关系。 高频响应分析对电容耦合还是直接耦合都是适用的&#xff0c;因为在电容耦合中高频模式下…...

2023年数学建模美赛D题(Prioritizing the UN Sustainability Goals):SDGs 优先事项的选择

正在写&#xff0c;不断更新&#xff0c;别着急。。。 4. SDGs 优先事项的选择 4.1 基于SDG密度分布图选择优先事项 虽然每个可持续发展目标的接近度矩阵和中心性度量的结果是通用的&#xff0c;并创建了基本的可持续发展目标网络&#xff0c;但由于各国在网络的不同部分取得…...

springboot实现项目启动前的一些操作

在服务启动时&#xff0c;做一些操作&#xff0c;比如加载配置&#xff0c;初始化数据&#xff0c;请求其他服务的接口等。 有三种方法&#xff1a; 第一种是实现CommandLineRunner接口 第二种是实现ApplicationRunner接口 第三种是使用注解&#xff1a;PostConstruct 三者使用…...

详解JavaScript的形参,实参以及传参

文章目录 前言一、参数是什么&#xff1f;二、形参和实参 1.形参 2.实参三、传参 1.参数传递的对应关系2.两个传参的例子 总结前言 编程初学者在接触JavaScript这门语言时&#xff0c;很难搞懂里面的逻辑&#xff0c;这就会导致入门慢&#xff0c;入门难。这种难度一般…...

Vue中的diff算法

diff算法介绍 diff算法是一种高效对比算法。diff算法在组件更新即响应式数据监控到数据的改变&#xff0c;重新生成虚拟DOM树的时候调用&#xff0c;然后通过diff算法计算出前后虚拟dom树的差异点&#xff0c;更新dom时只更新变化的部分。 直接比较和修改两个数的复杂度为什么…...

【面试题】前端春招第二面

不容错过的一些面试题小细节&#xff0c;话不多说&#xff0c;直接看题~大厂面试题分享 面试题库后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库HTML/CSS/Javascript/ES篇&#xff08;1&#xff09;标准盒模型和怪异盒…...

Pytorch 基础之张量数据类型

学习之前&#xff1a;先了解 Tensor&#xff08;张量&#xff09; 官方文档的解释是&#xff1a; 张量如同数组和矩阵一样, 是一种特殊的数据结构。在PyTorch中, 神经网络的输入、输出以及网络的参数等数据, 都是使用张量来进行描述。 说白了就是一种数据结构 基本数据类型…...

Java 基础面试题——常见类

目录1.String 为什么是不可变的&#xff1f;2.字符串拼接用“” 和 StringBuilder 有什么区别?3.String、StringBuffer 和 StringBuilder 的区别是什么?4.String 中的 equals() 和 Object 中的 equals() 有何区别&#xff1f;5.Object 类有哪些常用的方法&#xff1f;6.如何获…...

Windows 系统从零配置 Python 环境,安装CUDA、CUDNN、PyTorch 详细教程

文章目录1 配置 python 环境1.1 安装 Anaconda1.2 检查环境安装成功1.3 创建虚拟环境1.4 进入/退出 刚刚创建的环境1.5 其它操作1.5.1 查看电脑上所有已创建的环境1.5.2 删除已创建的环境2 安装 CUDA 和 CUDNN2.1 查看自己电脑支持的 CUDA 版本2.2 安装 CUDA2.3 安装 CUDNN2.4 …...

[REDIS]redis的一些配置文件

修改配置文件 vim /etc/redis/redis.conf目录 protected-mode tcp-backlog timeout tcp-keepalive daemonize pidfile loglevel databases 设置密码 maxclients maxmemory maxmemory-policy maxmemory-samples 默认情况下 bind127.0.0.1 只能接受本机的访问请求。在不写的情况…...

Java反序列化漏洞——CommonsCollections4.0版本—CC2、CC4

一、概述4.0版本的CommonsCollections对之前的版本做了一定的更改&#xff0c;那么之前的CC链反序列化再4版本中是否可用呢。实际上是可用的&#xff0c;比如CC6的链&#xff0c;引入的时候因为⽼的Gadget中依赖的包名都是org.apache.commons.collections &#xff0c;⽽新的包…...

下载网上压缩包(包含多行json)并将其转换为字典的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...

【郭东白架构课 模块一:生存法则】11|法则五:架构师为什么要关注技术体系的外部适应性?

你好&#xff0c; 我是郭东白。 前四条法则分别讲了目标、资源、人性和技术周期&#xff0c;这些都与架构活动的外部环境有关。那么今天我们来讲讲在架构活动内部&#xff0c;也就是在架构师可控的范围内&#xff0c;应该遵守哪些法则。今天这节课&#xff0c;我们就先从技术体…...

Mindspore安装

本文用于记录搭建昇思MindSpore开发及使用环境的过程&#xff0c;并通过MindSpore的API快速实现了一个简单的深度学习模型。 什么是MindSpore? 昇思MindSpore是一个全场景深度学习框架&#xff0c;旨在实现易开发、高效执行、全场景覆盖三大目标。 安装步骤 鉴于笔者手头硬…...

C++010-C++嵌套循环

文章目录C010-C嵌套循环嵌套循环嵌套循环举例题目描述 输出1的个数题目描述 输出n行99乘法表题目描述 求s1!2!...10!作业在线练习&#xff1a;总结C010-C嵌套循环 在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 嵌套循环 循环可以指挥计算机重复去…...

设计模式之迭代器模式与命令模式详解和应用

目录1 迭代器模式1.1 目标1.2 内容定位1.3 迭代器模式1.4 迭代器模式的应用场景1.5 手写字定义的送代器1.6 迭代器模式在源码中的体现1.7 迭代器模式的优缺点2 命令模式2.1 定义2.2 命令模式的应用场景2.3 命令模式在业务场景中的应用2.4 命令模式在源码中的体现2.5 命令模式的…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

Git 命令全流程总结

以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结&#xff0c;按操作场景分类整理&#xff1a; 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...

更新 Docker 容器中的某一个文件

&#x1f504; 如何更新 Docker 容器中的某一个文件 以下是几种在 Docker 中更新单个文件的常用方法&#xff0c;适用于不同场景。 ✅ 方法一&#xff1a;使用 docker cp 拷贝文件到容器中&#xff08;最简单&#xff09; &#x1f9f0; 命令格式&#xff1a; docker cp <…...