异步组件与函数式组件

在异步组件中,“异步”二字指的是,以异步的方式加载并渲染一个组件。这在代码分割、服务端下发组件等场景中尤为重要。而函数式组件允许使用一个普通函数定义组件,并使用该函数的返回值作为组件要渲染的内容。函数式组件的特点是:无状态、编写简单且直观。在 Vue.js 2 中,相比有状态组件来说,函数式组件具有明显的性能优势。但在 Vue.js 3 中,函数式组件与有状态组件的性能差距不大,都非常好。正如Vue.js RFC 的原文所述:“在 Vue.js 3 中使用函数式组件,主要是因为它的简单性,而不是因为它的性能好。”

1、异步组件要解决的问题

从根本上来说,异步组件的实现不需要任何框架层面的支持,用户完全可以自行实现。渲染 App 组件到页面的示例如下:

01 import App from 'App.vue'
02 createApp(App).mount('#app')

上面这段代码所展示的就是同步渲染。我们可以轻易地将其修改为异步渲染,如下面的代码所示:

01 const loader = () => import('App.vue')
02 loader().then(App => {
03   createApp(App).mount('#app')
04 })

这里我们使用动态导入语句 import() 来加载组件,它会返回一个 Promise 实例。组件加载成功后,会调用 createApp 函数完成挂载,这样就实现了以异步的方式来渲染页面。

上面的例子实现了整个页面的异步渲染。通常一个页面会由多个组件构成,每个组件负责渲染页面的一部分。那么,如果只想异步渲染部分页面,要怎么办呢?这时,只需要有能力异步加载某一个组件就可以了。假设下面的代码是 App.vue 组件的代码:

01 <template>
02   <CompA />
03   <component :is="asyncComp" />
04 </template>
05 <script>
06 import { shallowRef } from 'vue'
07 import CompA from 'CompA.vue'
08
09 export default {
10   components: { CompA },
11   setup() {
12     const asyncComp = shallowRef(null)
13
14     // 异步加载 CompB 组件
15     import('CompB.vue').then(CompB => asyncComp.value = CompB)
16
17     return {
18       asyncComp
19     }
20   }
21 }
22 </script>

从这段代码的模板中可以看出,页面由 <CompA /> 组件和动态组件 <component> 构成。其中,CompA 组件是同步渲染的,而动态组件绑定了 asyncComp 变量。再看脚本块,我们通过动态导入语句 import() 来异步加载 CompB 组件,当加载成功后,将 asyncComp 变量的值设置为 CompB。这样就实现了 CompB 组件的异步加载和渲染。

不过,虽然用户可以自行实现组件的异步加载和渲染,但整体实现还是比较复杂的,因为一个完善的异步组件的实现,所涉及的内容要比上面的例子复杂得多。通常在异步加载组件时,我们还要考虑以下几个方面:

  • 如果组件加载失败或加载超时,是否要渲染 Error 组件?
  • 组件在加载时,是否要展示占位的内容?例如渲染一个Loading 组件。
  • 组件加载失败后,是否需要重试?

为了替用户更好地解决上述问题,我们需要在框架层面为异步组件提供更好的封装支持,与之对应的能力如下:

  • 允许用户指定加载出错时要渲染的组件。
  • 允许用户指定 Loading 组件,以及展示该组件的延迟时间。
  • 允许用户设置加载组件的超时时长。
  • 组件加载失败时,为用户提供重试的能力。

以上这些内容就是异步组件真正要解决的问题。

2、异步组件的实现原理

2.1、封装 defineAsyncComponent 函数

异步组件本质上是通过封装手段来实现友好的用户接口,从而降低用户层面的使用复杂度,如下面的用户代码所示:

01 <template>
02   <AsyncComp />
03 </template>
04 <script>
05 export default {
06   components: {
07     // 使用 defineAsyncComponent 定义一个异步组件,它接收一个加载器作为参数
08     AsyncComp: defineAsyncComponent(() => import('CompA'))
09   }
10 }
11 </script>

在上面这段代码中,我们使用 defineAsyncComponent 来定义异步组件,并直接使用 components 组件选项来注册它。这样,在模板中就可以像使用普通组件一样使用异步组件了。可以看到,使用 defineAsyncComponent 函数定义异步组件的方式,比我们自行实现的异步组件方案要简单直接得多。

defineAsyncComponent 是一个高阶组件,它最基本的实现如下:

01 // defineAsyncComponent 函数用于定义一个异步组件,接收一个异步组件加载器作为参数
02 function defineAsyncComponent(loader) {
03   // 一个变量,用来存储异步加载的组件
04   let InnerComp = null
05   // 返回一个包装组件
06   return {
07     name: 'AsyncComponentWrapper',
08     setup() {
09       // 异步组件是否加载成功
10       const loaded = ref(false)
11       // 执行加载器函数,返回一个 Promise 实例
12       // 加载成功后,将加载成功的组件赋值给 InnerComp,并将 loaded 标记为 true,代表加载成功
13       loader().then(c => {
14         InnerComp = c
15         loaded.value = true
16       })
17
18       return () => {
19         // 如果异步组件加载成功,则渲染该组件,否则渲染一个占位内容
20         return loaded.value ? { type: InnerComp } : { type: Text, children: '' }
21       }
22     }
23   }
24 }

这里有以下几个关键点:

  • defineAsyncComponent 函数本质上是一个高阶组件,它的返回值是一个包装组件。
  • 包装组件会根据加载器的状态来决定渲染什么内容。如果加载器成功地加载了组件,则渲染被加载的组件,否则会渲染一个占位内容。
  • 通常占位内容是一个注释节点。组件没有被加载成功时,页面中会渲染一个注释节点来占位。但这里我们使用了一个空文本节点来占位。

2.2、超时与 Error 组件

异步组件通常以网络请求的形式进行加载。前端发送一个 HTTP 请求,请求下载组件的 JavaScript 资源,或者从服务端直接获取组件数据。既然存在网络请求,那么必然要考虑网速较慢的情况,尤其是在弱网环境下,加载一个组件可能需要很长时间。因此,我们需要为用户提供指定超时时长的能力,当加载组件的时间超过了指定时长后,会触发超时错误。这时如果用户配置了 Error 组件,则会渲染该组件。

首先,我们来设计用户接口。为了让用户能够指定超时时长,defineAsyncComponent 函数需要接收一个配置对象作为参数:

01 const AsyncComp = defineAsyncComponent({
02   loader: () => import('CompA.vue'),
03   timeout: 2000, // 超时时长,其单位为 ms
04   errorComponent: MyErrorComp // 指定出错时要渲染的组件
05 })
  • loader:指定异步组件的加载器。
  • timeout:单位为 ms,指定超时时长。
  • errorComponent:指定一个 Error 组件,当错误发生时会渲染它。

设计好用户接口后,我们就可以给出具体实现了,如下面的代码所示:

01 function defineAsyncComponent(options) {
02   // options 可以是配置项,也可以是加载器
03   if (typeof options === 'function') {
04     // 如果 options 是加载器,则将其格式化为配置项形式
05     options = {
06       loader: options
07     }
08   }
09
10   const { loader } = options
11
12   let InnerComp = null
13
14   return {
15     name: 'AsyncComponentWrapper',
16     setup() {
17       const loaded = ref(false)
18       // 代表是否超时,默认为 false,即没有超时
19       const timeout = ref(false)
20
21       loader().then(c => {
22         InnerComp = c
23         loaded.value = true
24       })
25
26       let timer = null
27       if (options.timeout) {
28         // 如果指定了超时时长,则开启一个定时器计时
29         timer = setTimeout(() => {
30           // 超时后将 timeout 设置为 true
31           timeout.value = true
32         }, options.timeout)
33       }
34       // 包装组件被卸载时清除定时器
35       onUmounted(() => clearTimeout(timer))
36
37       // 占位内容
38       const placeholder = { type: Text, children: '' }
39
40       return () => {
41         if (loaded.value) {
42           // 如果组件异步加载成功,则渲染被加载的组件
43           return { type: InnerComp }
44         } else if (timeout.value) {
45           // 如果加载超时,并且用户指定了 Error 组件,则渲染该组件
46           return options.errorComponent ? { type: options.errorComponent } : placeholder
47         }
48         return placeholder
49       }
50     }
51   }
52 }

整体实现并不复杂,关键点如下:

  • 需要一个标志变量来标识异步组件的加载是否已经超时,即timeout.value。
  • 开始加载组件的同时,开启一个定时器进行计时。当加载超时后,将 timeout.value 的值设置为 true,代表加载已经超时。这里需要注意的是,当包装组件被卸载时,需要清除定时器。
  • 包装组件根据 loaded 变量的值以及 timeout 变量的值来决定具体的渲染内容。如果异步组件加载成功,则渲染被加载的组件;如果异步组件加载超时,并且用户指定了 Error 组件,则渲染 Error 组件。

这样,我们就实现了对加载超时的兼容,以及对 Error 组件的支持。除此之外,我们希望有更加完善的机制来处理异步组件加载过程中发生的错误,超时只是错误的原因之一。基于此,我们还希望为用户提供以下能力。

  • 当错误发生时,把错误对象作为 Error 组件的 props 传递过去,以便用户后续能自行进行更细粒度的处理。
  • 除了超时之外,有能力处理其他原因导致的加载错误,例如网络失败等。

为了实现这两个目标,我们需要对代码做一些调整,如下所示:

01 function defineAsyncComponent(options) {
02   if (typeof options === 'function') {
03     options = {
04       loader: options
05     }
06   }
07
08   const { loader } = options
09
10   let InnerComp = null
11
12   return {
13     name: 'AsyncComponentWrapper',
14     setup() {
15       const loaded = ref(false)
16       // 定义 error,当错误发生时,用来存储错误对象
17       const error = shallowRef(null)
18
19       loader()
20         .then(c => {
21           InnerComp = c
22           loaded.value = true
23         })
24         // 添加 catch 语句来捕获加载过程中的错误
25         .catch((err) => error.value = err)
26
27       let timer = null
28       if (options.timeout) {
29         timer = setTimeout(() => {
30           // 超时后创建一个错误对象,并复制给 error.value
31           const err = new Error(`Async component timed out after ${options.timeout}ms.`)
32           error.value = err
33         }, options.timeout)
34       }
35
36       const placeholder = { type: Text, children: '' }
37
38       return () => {
39         if (loaded.value) {
40           return { type: InnerComp }
41         } else if (error.value && options.errorComponent) {
42           // 只有当错误存在且用户配置了 errorComponent 时才展示 Error 组件,同时将 error 作为 props 传递
43           return { type: options.errorComponent, props: { error: error.value } }
44         } else {
45           return placeholder
46         }
47       }
48     }
49   }
50 }

观察上面的代码,我们对之前的实现做了一些调整。首先,为加载器添加 catch 语句来捕获所有加载错误。接着,当加载超时后,我们会创建一个新的错误对象,并将其赋值给error.value 变量。在组件渲染时,只要 error.value 的值存在,且用户配置了 errorComponent 组件,就直接渲染errorComponent 组件并将 error.value 的值作为该组件的props 传递。这样,用户就可以在自己的 Error 组件上,通过定义名为 error 的 props 来接收错误对象,从而实现细粒度的控制。

2.3、延迟与 Loading 组件

异步加载的组件受网络影响较大,加载过程可能很慢,也可能很快。这时我们就会很自然地想到,对于第一种情况,我们能否通过展示 Loading 组件来提供更好的用户体验。这样,用户就不会有“卡死”的感觉了。这是一个好想法,但展示Loading 组件的时机是一个需要仔细考虑的问题。通常,我们会从加载开始的那一刻起就展示 Loading 组件。但在网络状况良好的情况下,异步组件的加载速度会非常快,这会导致Loading 组件刚完成渲染就立即进入卸载阶段,于是出现闪烁的情况。对于用户来说这是非常不好的体验。因此,我们需要为 Loading 组件设置一个延迟展示的时间。例如,当超过200ms 没有完成加载,才展示 Loading 组件。这样,对于在200ms 内能够完成加载的情况来说,就避免了闪烁问题的出现。

不过,我们首先要考虑的仍然是用户接口的设计,如下面的代码所示:

01 defineAsyncComponent({
02   loader: () => new Promise(r => { /* ... */ }),
03   // 延迟 200ms 展示 Loading 组件
04   delay: 200,
05   // Loading 组件
06   loadingComponent: {
07     setup() {
08       return () => {
09         return { type: 'h2', children: 'Loading...' }
10       }
11     }
12   }
13 })
  • delay,用于指定延迟展示 Loading 组件的时长。
  • loadingComponent,类似于 errorComponent 选项,用于配置 Loading 组件。

用户接口设计完成后,我们就可以着手实现了。延迟时间与Loading 组件的具体实现如下:

01 function defineAsyncComponent(options) {
02   if (typeof options === 'function') {
03     options = {
04       loader: options
05     }
06   }
07
08   const { loader } = options
09
10   let InnerComp = null
11
12   return {
13     name: 'AsyncComponentWrapper',
14     setup() {
15       const loaded = ref(false)
16       const error = shallowRef(null)
17       // 一个标志,代表是否正在加载,默认为 false
18       const loading = ref(false)
19
20       let loadingTimer = null
21       // 如果配置项中存在 delay,则开启一个定时器计时,当延迟到时后将 loading.value 设置为 true
22       if (options.delay) {
23         loadingTimer = setTimeout(() => {
24           loading.value = true
25         }, options.delay);
26       } else {
27         // 如果配置项中没有 delay,则直接标记为加载中
28         loading.value = true
29       }
30       loader()
31         .then(c => {
32           InnerComp = c
33           loaded.value = true
34         })
35         .catch((err) => error.value = err)
36         .finally(() => {
37           loading.value = false
38           // 加载完毕后,无论成功与否都要清除延迟定时器
39           clearTimeout(loadingTimer)
40         })
41
42       let timer = null
43       if (options.timeout) {
44         timer = setTimeout(() => {
45           const err = new Error(`Async component timed out after ${options.timeout}ms.`)
46           error.value = err
47         }, options.timeout)
48       }
49
50       const placeholder = { type: Text, children: '' }
51
52       return () => {
53         if (loaded.value) {
54           return { type: InnerComp }
55         } else if (error.value && options.errorComponent) {
56           return { type: options.errorComponent, props: { error: error.value } }
57         } else if (loading.value && options.loadingComponent) {
58           // 如果异步组件正在加载,并且用户指定了 Loading 组件,则渲染 Loading 组件
59           return { type: options.loadingComponent }
60         } else {
61           return placeholder
62         }
63       }
64     }
65   }
66 }

整体实现思路类似于超时时长与 Error 组件,有以下几个关键点:

  • 需要一个标记变量 loading 来代表组件是否正在加载。
  • 如果用户指定了延迟时间,则开启延迟定时器。定时器到时后,再将 loading.value 的值设置为 true。
  • 无论组件加载成功与否,都要清除延迟定时器,否则会出现组件已经加载成功,但仍然展示 Loading 组件的问题。
  • 无论组件加载成功与否,都要清除延迟定时器,否则会出现组件已经加载成功,但仍然展示 Loading 组件的问题。

另外有一点需要注意,当异步组件加载成功后,会卸载Loading 组件并渲染异步加载的组件。为了支持 Loading 组件的卸载,我们需要修改 unmount 函数,如以下代码所示:

01 function unmount(vnode) {
02   if (vnode.type === Fragment) {
03     vnode.children.forEach(c => unmount(c))
04     return
05   } else if (typeof vnode.type === 'object') {
06     // 对于组件的卸载,本质上是要卸载组件所渲染的内容,即 subTree
07     unmount(vnode.component.subTree)
08     return
09   }
10   const parent = vnode.el.parentNode
11   if (parent) {
12     parent.removeChild(vnode.el)
13   }
14 }

对于组件的卸载,本质上是要卸载组件所渲染的内容,即subTree。所以在上面的代码中,我们通过组件实例的vnode.component 属性得到组件实例,再递归地调用unmount 函数完成 vnode.component.subTree 的卸载。

2.4、重试机制

重试指的是当加载出错时,有能力重新发起加载组件的请求。在加载组件的过程中,发生错误的情况非常常见,尤其是在网络不稳定的情况下。因此,提供开箱即用的重试机制,会提升用户的开发体验。

异步组件加载失败后的重试机制,与请求服务端接口失败后的重试机制一样。所以,我们先来讨论接口请求失败后的重试机制是如何实现的。为此,我们需要封装一个 fetch 函数,用来模拟接口请求:

01 function fetch() {
02   return new Promise((resolve, reject) => {
03     // 请求会在 1 秒后失败
04     setTimeout(() => {
05       reject('err')
06     }, 1000);
07   })
08 }

假设调用 fetch 函数会发送 HTTP 请求,并且该请求会在 1 秒后失败。为了实现失败后的重试,我们需要封装一个 load 函数,如下面的代码所示:

01 // load 函数接收一个 onError 回调函数
02 function load(onError) {
03   // 请求接口,得到 Promise 实例
04   const p = fetch()
05   // 捕获错误
06   return p.catch(err => {
07     // 当错误发生时,返回一个新的 Promise 实例,并调用 onError 回调,
08     // 同时将 retry 函数作为 onError 回调的参数
09     return new Promise((resolve, reject) => {
10       // retry 函数,用来执行重试的函数,执行该函数会重新调用 load 函数并发送请求
11       const retry = () => resolve(load(onError))
12       const fail = () => reject(err)
13       onError(retry, fail)
14     })
15   })
16 }

load 函数内部调用了 fetch 函数来发送请求,并得到一个Promise 实例。接着,添加 catch 语句块来捕获该实例的错误。当捕获到错误时,我们有两种选择:要么抛出错误,要么返回一个新的 Promise 实例,并把该实例的 resolve 和 reject 方法暴露给用户,让用户来决定下一步应该怎么做。这里,我们将新的 Promise 实例的 resolve 和 reject 分别封装为 retry 函数和 fail 函数,并将它们作为 onError 回调函数的参数。这样,用户就可以在错误发生时主动选择重试或直接抛出错误。下面的代码展示了用户是如何进行重试加载的:

01 // 调用 load 函数加载资源
02 load(
03   // onError 回调
04   (retry) => {
05     // 失败后重试
06     retry()
07   }
08 ).then(res => {
09   // 成功
10   console.log(res)
11 })

基于这个原理,我们可以很容易地将它整合到异步组件的加载流程中。具体实现如下:

01 function defineAsyncComponent(options) {
02   if (typeof options === 'function') {
03     options = {
04       loader: options
05     }
06   }
07
08   const { loader } = options
09
10   let InnerComp = null
11
12   // 记录重试次数
13   let retries = 0
14   // 封装 load 函数用来加载异步组件
15   function load() {
16     return loader()
17       // 捕获加载器的错误
18       .catch((err) => {
19         // 如果用户指定了 onError 回调,则将控制权交给用户
20         if (options.onError) {
21           // 返回一个新的 Promise 实例
22           return new Promise((resolve, reject) => {
23             // 重试
24             const retry = () => {
25               resolve(load())
26               retries++
27             }
28             // 失败
29             const fail = () => reject(err)
30             // 作为 onError 回调函数的参数,让用户来决定下一步怎么做
31             options.onError(retry, fail, retries)
32           })
33         } else {
34           throw error
35         }
36       })
37   }
38
39   return {
40     name: 'AsyncComponentWrapper',
41     setup() {
42       const loaded = ref(false)
43       const error = shallowRef(null)
44       const loading = ref(false)
45
46       let loadingTimer = null
47       if (options.delay) {
48         loadingTimer = setTimeout(() => {
49           loading.value = true
50         }, options.delay);
51       } else {
52         loading.value = true
53       }
54       // 调用 load 函数加载组件
55       load()
56         .then(c => {
57           InnerComp = c
58           loaded.value = true
59         })
60         .catch((err) => {
61           error.value = err
62         })
63         .finally(() => {
64           loading.value = false
65           clearTimeout(loadingTimer)
66         })
67
68       // 省略部分代码
69     }
70   }
71 }

如上面的代码及注释所示,其整体思路与普通接口请求的重试机制类似。

3、函数式组件

函数式组件的实现相对容易。一个函数式组件本质上就是一个普通函数,该函数的返回值是虚拟 DOM。“在 Vue.js 3 中使用函数式组件,主要是因为它的简单性,而不是因为它的性能好。”这是因为在 Vue.js 3 中,即使是有状态组件,其初始化性能消耗也非常小。

在用户接口层面,一个函数式组件就是一个返回虚拟 DOM 的函数,如下面的代码所示:

01 function MyFuncComp(props) {
02   return { type: 'h1', children: props.title }
03 }

函数式组件没有自身状态,但它仍然可以接收由外部传入的props。为了给函数式组件定义 props,我们需要在组件函数上添加静态的 props 属性,如下面的代码所示:

01 function MyFuncComp(props) {
02   return { type: 'h1', children: props.title }
03 }
04 // 定义 props
05 MyFuncComp.props = {
06   title: String
07 }

在有状态组件的基础上,实现函数式组件将变得非常简单,因为挂载组件的逻辑可以复用 mountComponent 函数。为此,我们需要在 patch 函数内支持函数类型的 vnode.type,如下面patch 函数的代码所示:

01 function patch(n1, n2, container, anchor) {
02   if (n1 && n1.type !== n2.type) {
03     unmount(n1)
04     n1 = null
05   }
06
07   const { type } = n2
08
09   if (typeof type === 'string') {
10     // 省略部分代码
11   } else if (type === Text) {
12     // 省略部分代码
13   } else if (type === Fragment) {
14     // 省略部分代码
15   } else if (
16     // type 是对象 --> 有状态组件
17     // type 是函数 --> 函数式组件
18     typeof type === 'object' || typeof type === 'function'
19   ) {
20     // component
21     if (!n1) {
22       mountComponent(n2, container, anchor)
23     } else {
24       patchComponent(n1, n2, anchor)
25     }
26   }
27 }

在 patch 函数内部,通过检测 vnode.type 的类型来判断组件的类型:

  • 如果 vnode.type 是一个对象,则它是一个有状态组件,并且vnode.type 是组件选项对象;
  • 如果 vnode.type 是一个函数,则它是一个函数式组件。

但无论是有状态组件,还是函数式组件,我们都可以通过mountComponent 函数来完成挂载,也都可以通过patchComponent 函数来完成更新。

下面是修改后的 mountComponent 函数,它支持挂载函数式组件:

01 function mountComponent(vnode, container, anchor) {
02   // 检查是否是函数式组件
03   const isFunctional = typeof vnode.type === 'function'
04
05   let componentOptions = vnode.type
06   if (isFunctional) {
07     // 如果是函数式组件,则将 vnode.type 作为渲染函数,将 vnode.type.props 作为 props 选项定义即可
08     componentOptions = {
09       render: vnode.type,
10       props: vnode.type.props
11     }
12   }
13
14   // 省略部分代码
15 }

可以看到,实现对函数式组件的兼容非常简单。首先,在mountComponent 函数内检查组件的类型,如果是函数式组件,则直接将组件函数作为组件选项对象的 render 选项,并将组件函数的静态 props 属性作为组件的 props 选项即可,其他逻辑保持不变。当然,出于更加严谨的考虑,我们需要通过isFunctional 变量实现选择性地执行初始化逻辑,因为对于函数式组件来说,它无须初始化 data 以及生命周期钩子。从这一点可以看出,函数式组件的初始化性能消耗小于有状态组件。

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

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

相关文章

Java如何使用jwt进行登录拦截和权限认证

登录如下 登录拦截 拦截如下 权限认证

electron 设置开机自启动后 托盘图标无法显示

问题描述 electron 设置开机自启动后 托盘图标无法显示 问题解决 tray new Tray(path.join(__dirname, ./public/logo.png)); //必须是绝对路径和扩展名&#xff0c;像.png等我的问题是图标之前设置为相对路径&#xff0c;而导致无法显示。将Tray的图标路径设定为绝对路径后…

【Docker】从零开始:12.容器数据卷

【Docker】从零开始&#xff1a;12.容器数据卷 1.什么是容器数据库卷2.数据的覆盖问题3.为什么要用数据卷4.Docker提供了两种卷&#xff1a;5.两种卷的区别6.bind mount7.Docker managed volumevolume 语法volume 操作参数 1.什么是容器数据库卷 卷 就是目录或文件&#xff0c…

数据结构之栈与队列习题详解解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2.概念题…

python_selenium自动化测试框架

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

springboot程序启动成功后执行的方法

//实现该接口&#xff0c;run方法既程序启动成功后将要执行的方法 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) //package org.springframework.boot;FunctionalInterface public interface CommandLineRunner {…

nginx的n种用法(nginx安装+正向代理+反向代理+透明代理+负载均衡+静态服务器)

nginx的安装 一、安装依赖 # 一键安装四个依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel二、安装nginx yum install nginx三、检查是否安装成功 nginx -v四、启动/停止nginx /etc/init.d/nginx start /etc/init.d/nginx stop五、编辑配置文件…

重生之我是一名程序员 41 ——字符串函数(2)

哈喽啊大家晚上好&#xff01;今天呢我们延续昨天的内容&#xff0c;给大家带来第二个字符串函数——strcat函数。 首先呢&#xff0c;还是先带大家认识一下它。strcat函数是C语言中用于将两个字符串连接起来的函数&#xff0c;其函数原型为&#xff1a; char *strcat(char *…

JAVA毕业设计112—基于Java+Springboot+Vue的宠物领养社区小程序(源码+数据库)

基于JavaSpringbootVue的宠物领养社区小程序(源码数据库)112 一、系统介绍 本系统前后端分离带小程序 小程序&#xff08;用户端&#xff09;&#xff0c;后台管理系统&#xff08;管理员&#xff09; 小程序&#xff1a; 登录、注册、宠物领养、发布寻宠、发布领养、宠物社…

算法设计与实现--分治篇

什么是分治算法 分治算法是一种常见的问题解决方法&#xff0c;它将一个复杂的问题划分为多个相同或相似的子问题&#xff0c;然后递归地解决这些子问题&#xff0c;最后将子问题的解合并得到原问题的解。 分治算法通常包含三个步骤&#xff1a; 分解&#xff08;Divide&…

MongoDB的部署

MongoDB部署 基于Linux平台 前置要求 在Centos7 64位系统中安装64位的MongoDB的安装包&#xff0c;通过访问MongoDB官网https://www.mongodb.com/download-center/community进入MongoDB下载页面。Version&#xff1a;指定MongoDB版本&#xff0c;MongoDB的版本分为稳定版和开发…

5.3每日一题(不确定正负号的级数敛散性:和一个正项级数比较判定)

比较判别法和比较判别法的极限形式是对正项级数而言的&#xff0c;若一个级数和p级数比较&#xff0c;结果>0&#xff0c;则同敛散&#xff1b;若结果<0&#xff0c;则结果乘以-1 结果又同敛散了&#xff1b;所以只要比值不等于0&#xff0c;则同敛散&#xff1b; 所以当…

JVM虚拟机:G1垃圾回收器的日志分析

本文重点 本文我们将学习G1垃圾回收器的日志 使用 执行命令 java -Xms20M -Xmx20M -XX:PrintGCDetails -XX:UseG1GC 类名 分析 前面我们学习了G1垃圾回收器&#xff0c;它的回收有三种可能&#xff1a; YGC FGC MixedGC GC pause表示STW,Evacuation表示复制对象&#xff0c;…

c语言-字符函数和字符串函数详解

文章目录 1. 字符分类函数2. 字符转换函数3. strlen的使用和模拟实现4. strcpy的使用和模拟实现5. strncpy函数的使用6. strcat的使用和模拟实现7. strncat函数的使用8. strcmp的使用和模拟实现9. strncmp函数的使用10. strstr的使用和模拟实现11. strtok函数的使用12. strerro…

【尚跑】2023宝鸡马拉松安全完赛,顺利PB达成

1、赛事背景 千年宝地&#xff0c;一马当先&#xff01;10月15日7时30分&#xff0c;吉利银河2023宝鸡马拉松在宝鸡市行政中心广场鸣枪开跑。 不可忽视的是&#xff0c;这次赛事的卓越之处不仅在于规模和参与人数&#xff0c;还在于其精心的策划和细致入微的组织。为了确保每位…

Kotlin学习——kt里面的函数,高阶函数 函数式编程 扩展函数和属性

Kotlin 是一门现代但已成熟的编程语言&#xff0c;旨在让开发人员更幸福快乐。 它简洁、安全、可与 Java 及其他语言互操作&#xff0c;并提供了多种方式在多个平台间复用代码&#xff0c;以实现高效编程。 https://play.kotlinlang.org/byExample/01_introduction/02_Functio…

论文笔记:详解NEUPSL DSI

《Using Domain Knowledge to Guide Dialog Structure Induction via Neural Probabilistic 》 名词解释 Dialog Structure Induction&#xff08;DSI&#xff09;是推断给定目标导向对话的潜在对话结构&#xff08;即一组对话状态及其时间转换&#xff09;的任务。它是现代对…

基于Scapy修改ClientHello的SNI(三)

需求:修改HTTPS的ClientHello中的SNI字段 目标:修改成功,wireshark显示正常 语言:Python 三方库:Scapy 下面是一个标准的ClientHello报文,是从一个完整的HTTPS流中保存出来的,原始报文中的SNI是www.baidu.com 在上一篇文章中 记录基于scapy构造ClientHello报文的尝试…

【Docker】Docker与Kubernetes:区别与优势对比

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。   kubernetes&#xff0c;简称K8s&a…

C#调用ffmpeg从视频提取图片

微信公众号“CSharp编程大全”的文章《C#从视频提取图片&#xff1f;》介绍了基于Microsoft.DirectX.AudioVideoPlayback.Video类实现从视频提取图片的方式&#xff0c;本来是想学习并测试该类的用法&#xff0c;但实际测试过程中却没有测通。百度从视频提取图片&#xff0c;网…