分析 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
。
// 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
。
调试 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,};
}
我们继续来看 raf
和 cancelRaf
,是如何实现的。
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));
}
上文代码,主要一个 API
,requestAnimationFrame、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],可能就会有豁然开朗的感觉。再看其他组件,可能就可以猜测出大概实现的代码了。
如果是使用 react
、Taro
技术栈,感兴趣也可以看看 taroify
CountDown
组件的实现 文档[14],源码[15]。
如果看完有收获,欢迎点赞、评论、分享支持。你的支持和肯定,是我写作的动力。
参考资料
[1]
组件库源码系列专栏: https://juejin.cn/column/7140264842954276871
[2]countdown
: https://vant-contrib.gitee.io/vant/v4/#/zh-CN/count-down
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
················· 若川简介 ·················
你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,持续组织了一年多源码共读活动,帮助5000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。
扫码加我微信 lxchuan12、拉你进源码共读群
今日话题
目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 lxchuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~
相关文章:
分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊
2022年11月23日首发于掘金,现在同步到公众号。11. 前言大家好,我是若川。推荐点右上方蓝字若川视野把我的公众号设为星标。我倾力持续组织了一年多源码共读,感兴趣的可以加我微信 lxchuan12 参与。另外,想学源码,极力推…...
事件驱动型架构
事件驱动型架构是一种软件设计模式,其中微服务会对状态变化(称为“事件”)作出反应。事件可以携带状态(例如商品价格或收货地址),或者事件也可以是标识符(例如,订单送达或发货通知&a…...
20222023华为OD机试 - 不含 101 的数(Python)
不含 101 的数 题目 小明在学习二进制时,发现了一类不含 101 的数, 也就是将数字用二进制表示,不能出现 101 。 现在给定一个正整数区间 [l,r],请问这个区间内包含了多少个不含 101 的数? 输入 输入一行,包含两个正整数 l l l, r r r...
杭州电子科技大学2023年MBA招生考试成绩查询和复查申请的通知
根据往年的情况,2023杭州电子大学MBA考试初试成绩可能将于2月21日公布,最早于20号出来,为了广大考生可以及时查询到自己的分数,杭州达立易考教育为大家汇总了信息。根据教育部和浙江省教育考试院关于硕士研究生招生考试工作的统一…...
电子技术——CS和CE放大器的高频响应
电子技术——CS和CE放大器的高频响应 在绘制出MOS和BJT的高频响应模型之后,我们对MOS和BJT的高频响应有了进一步的认识。现在我们想知道的是在高频响应中 fHf_HfH 的关系。 高频响应分析对电容耦合还是直接耦合都是适用的,因为在电容耦合中高频模式下…...
2023年数学建模美赛D题(Prioritizing the UN Sustainability Goals):SDGs 优先事项的选择
正在写,不断更新,别着急。。。 4. SDGs 优先事项的选择 4.1 基于SDG密度分布图选择优先事项 虽然每个可持续发展目标的接近度矩阵和中心性度量的结果是通用的,并创建了基本的可持续发展目标网络,但由于各国在网络的不同部分取得…...
springboot实现项目启动前的一些操作
在服务启动时,做一些操作,比如加载配置,初始化数据,请求其他服务的接口等。 有三种方法: 第一种是实现CommandLineRunner接口 第二种是实现ApplicationRunner接口 第三种是使用注解:PostConstruct 三者使用…...
详解JavaScript的形参,实参以及传参
文章目录 前言一、参数是什么?二、形参和实参 1.形参 2.实参三、传参 1.参数传递的对应关系2.两个传参的例子 总结前言 编程初学者在接触JavaScript这门语言时,很难搞懂里面的逻辑,这就会导致入门慢,入门难。这种难度一般…...
Vue中的diff算法
diff算法介绍 diff算法是一种高效对比算法。diff算法在组件更新即响应式数据监控到数据的改变,重新生成虚拟DOM树的时候调用,然后通过diff算法计算出前后虚拟dom树的差异点,更新dom时只更新变化的部分。 直接比较和修改两个数的复杂度为什么…...
【面试题】前端春招第二面
不容错过的一些面试题小细节,话不多说,直接看题~大厂面试题分享 面试题库后端面试题库 (面试必备) 推荐:★★★★★地址:前端面试题库HTML/CSS/Javascript/ES篇(1)标准盒模型和怪异盒…...
Pytorch 基础之张量数据类型
学习之前:先了解 Tensor(张量) 官方文档的解释是: 张量如同数组和矩阵一样, 是一种特殊的数据结构。在PyTorch中, 神经网络的输入、输出以及网络的参数等数据, 都是使用张量来进行描述。 说白了就是一种数据结构 基本数据类型…...
Java 基础面试题——常见类
目录1.String 为什么是不可变的?2.字符串拼接用“” 和 StringBuilder 有什么区别?3.String、StringBuffer 和 StringBuilder 的区别是什么?4.String 中的 equals() 和 Object 中的 equals() 有何区别?5.Object 类有哪些常用的方法?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对之前的版本做了一定的更改,那么之前的CC链反序列化再4版本中是否可用呢。实际上是可用的,比如CC6的链,引入的时候因为⽼的Gadget中依赖的包名都是org.apache.commons.collections ,⽽新的包…...
下载网上压缩包(包含多行json)并将其转换为字典的解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结与归纳,不仅形成深入且独到的理…...
【郭东白架构课 模块一:生存法则】11|法则五:架构师为什么要关注技术体系的外部适应性?
你好, 我是郭东白。 前四条法则分别讲了目标、资源、人性和技术周期,这些都与架构活动的外部环境有关。那么今天我们来讲讲在架构活动内部,也就是在架构师可控的范围内,应该遵守哪些法则。今天这节课,我们就先从技术体…...
Mindspore安装
本文用于记录搭建昇思MindSpore开发及使用环境的过程,并通过MindSpore的API快速实现了一个简单的深度学习模型。 什么是MindSpore? 昇思MindSpore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景覆盖三大目标。 安装步骤 鉴于笔者手头硬…...
C++010-C++嵌套循环
文章目录C010-C嵌套循环嵌套循环嵌套循环举例题目描述 输出1的个数题目描述 输出n行99乘法表题目描述 求s1!2!...10!作业在线练习:总结C010-C嵌套循环 在线练习: 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 命令模式的…...
【QA】[Vue/复选框全选] v-model绑定每一项的赋初值问题
发生场景:不只是复选框的状态改变,还有的功能要用到复选框的选中状态,比如:购物车计算总价,合计等等。 引入:复选框 checkbox 在使用时,需要用v-model绑定布尔值,来获取选中状态&…...
python基于django+vue微信小程序的校园二手闲置物品交易
在大学校园里,存在着很多的二手商品,但是由于信息资源的不流通以及传统二手商品信息交流方式的笨拙,导致了很多仍然具有一定价值或者具有非常价值的二手商品的囤积,乃至被当作废弃物处理。现在通过微信小程序的校园二手交易平台,可以方便快捷的发布和交流任何二手商品的信息,并…...
设计模式之观察者模式
什么是观察者模式 观察者模式定义了对象之间一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都能收到通知并自动刷新。 观察者模式主要包含以下几个角色: Subject(目标):指被观察的对…...
Java Lambda表达式
目录1 Lambda表达式1.1 函数式编程思想概括1.2 Lambda表达式标准格式1.3 Lambda表达式练习1(抽象方法无参无返回值)1.4 Lambda表达式练习2(抽象方法带参无返回值)1.5 Lambda表达式练习2(抽象方法带参带返回值ÿ…...
【1237. 找出给定方程的正整数解】
来源:力扣(LeetCode) 描述: 给你一个函数 f(x, y) 和一个目标结果 z,函数公式未知,请你计算方程 f(x,y) z 所有可能的正整数 数对 x 和 y。满足条件的结果数对可以按任意顺序返回。 尽管函数的具体式子…...
java基础学习 day41(继承中成员变量和成员方法的访问特点,方法的重写)
继承中,成员变量的访问特点 a. name前什么都不加,name变量的访问采用就近原则,先在局部变量中查找,若没找到,继续在本类的成员变量中查找,若没找到,继续在直接父类的成员变量中查找,…...
【c语言进阶】深度剖析整形数据
🚀write in front🚀 📜所属专栏: 🛰️博客主页:睿睿的博客主页 🛰️代码仓库:🎉VS2022_C语言仓库 🎡您的点赞、关注、收藏、评论,是对我最大的激励…...
【信息系统项目管理师】项目管理十大知识领域记忆敲出(采购风险沟通干系人)
【信息系统项目管理师】项目管理十大知识领域记忆敲出(采购风险沟通干系人) 这里写目录标题【信息系统项目管理师】项目管理十大知识领域记忆敲出(采购风险沟通干系人)一.项目采购管理记忆敲出1.合同管理:2.规划采购管…...
[LeetCode 1237]找出给定方程的正整数解
题目描述 题目链接:[LeetCode 1237]找出给定方程的正整数解 给你一个函数 f(x, y) 和一个目标结果 z,函数公式未知,请你计算方程 f(x,y) z 所有可能的正整数 数对 x 和 y。满足条件的结果数对可以按任意顺序返回。 尽管函数的具体式子未知…...
6.2 构建 RESTful 应用接口
第6章 构建 RESTful 服务 6.1 RESTful 简介 6.2 构建 RESTful 应用接口 6.3 使用 Swagger 生成 Web API 文档 6.4 实战:实现 Web API 版本控制 6.2 构建 RESTful 应用接口 6.2.1 Spring Boot 对 RESTful 的支持 Spring Boot 提供的spring-boot-starter-web组件完全…...
推荐做幻灯片搜图网站/seo是什么服务
盘点 GitHub 上那些神级指南!本次盘点都是 GitHub 上标星 10K 的开源指南。都是由中国的开发者开源,除了技术、教程类的指南,还有一些花里胡哨的东西。本期推荐开源项目目录:1. 计算机自学指南2. 大数据入门指南3. 程序员延寿指南…...
怎么做线上营销/seo网站优化培训找哪些
2019独角兽企业重金招聘Python工程师标准>>> 1. 提前准备:mac电脑(or虚拟机),空U盘(最低8GB的)、sierra.app(app store上下载的) 2. 前往-实用工具,运行里面的“磁盘工具…...
sae wordpress 3.9/建网站的详细步骤
注意:本文是在乌班图和Windows10环境下配置,Centos与乌班图略有不同,就是Centos的MySQL配置文件路径为/etc/my.cnf,其他操作一致1. 主从同步的定义主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时&#x…...
2024免费推广网站/中小企业网站制作
一. 基础环境准备操作系统:Ubuntu16.04Server先sudo apt-get install vim openssh-server,便于后续上传源码以及调试。看一下现在openssh的版本:zjdubuntu:~$ ssh -VOpenSSH_7.2p2 Ubuntu-4ubuntu2.6, OpenSSL 1.0.2g 1 Mar …...
拖拽式网站建设费用/seo 360
2020七夕将要来临,php中文网给大家准备了七款程序员表白专用源码,让你可以一举俘获美人心,下面就来看一看这七款表白代码大全,包含html、html5、CSS、JQ编写的一些非常简单实用的表白代码,非常炫酷、浪漫!1…...
上海市工商网站官网/免费拓客软件
使用范围: OA、MIS、ERP等信息管理类的项目,暂时不考虑网站。 遇到的问题: 完成一个项目,往往需要引用很多js文件,比如jQuery.js、easyUI等。还有自己写的一些列js文件,那么这些文件如何方便的加载…...