vue-router 源码解析(三)-实现路由守卫

news2025/1/17 17:52:40

文章目录

  • 基本使用
  • 导语
  • 初始化路由守卫
  • useCallbacks 发布订阅模式管理路由守卫
  • push 开始导航
  • resolve返回路由记录匹配结果
  • navigate 开始守卫
  • 抽取路由记录
  • guardToPromiseFn 用Promise包装守卫方法
  • extractComponentsGuards 从组件中抽取守卫
  • beforeRouteLeave 守卫收集
  • composition 守卫收集
    • onBeforeRouteLeave
    • onBeforeRouteUpdate

基本使用

Navigation triggered.
Call beforeRouteLeave guards in deactivated components.
Call global beforeEach guards.
Call beforeRouteUpdate guards in reused components.
Call beforeEnter in route configs.
Resolve async route components.
Call beforeRouteEnter in activated components.
Call global beforeResolve guards.
Navigation is confirmed.
Call global afterEach hooks.
DOM updates triggered.
Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.

导语

  • 在上一文中,解释了如何由routes配置转换出一条条matcher,而本文将开启导航部分,当开发者调用push方法后,路由守卫是如何被收集以及触发的(包括动态路由)
    在这里插入图片描述

初始化路由守卫

const router = VueRouter.createRouter({
  history: VueRouter.createWebHashHistory(), // 创建对应的路由对象
  routes,
})

export function createRouter(options: RouterOptions): Router {
	// routes转换成matcher,并返回相关API
	const matcher = createRouterMatcher(options.routes, options)
	// createWebHashHistory创建出的路由对象
	const routerHistory = options.history
	//...
	
    // useCallbacks 具有发布订阅功能的hook
	const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
	const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
	const afterGuards = useCallbacks<NavigationHookAfter>()
	// ...
	const router: Router = {
	//...
	push,
	replace,
	go,
	back: () => go(-1),
	forward: () => go(1),
	// 因为useCallbacks是发布订阅模式,所以调用router创建路由守卫时会收集对应回调
	beforeEach: beforeGuards.add,
	beforeResolve: beforeResolveGuards.add,
	afterEach: afterGuards.add,

    onError: errorHandlers.add,
	 install(app: App) {
		// 创建 RouterLink、RouterView 这俩全局组件
		app.component('RouterLink', RouterLink)
		app.component('RouterView', RouterView)
		//...
		// 初始化完成后,会首次调用push
		if (
			isBrowser &&
			// 在浏览器环境下,避免多个app中使用时造成重复push
			!started &&
			currentRoute.value === START_LOCATION_NORMALIZED // 等于默认信息,说明是首次导航
		) {
		started = true
		// push执行后调用navigate,一系列路由守卫的执行会变成Promise调用(包括动态路由),当一系列Promise解析完成后,才会调用routerHistory.push|routerHistory.replace改变页面url
		push(routerHistory.location).catch(err => {
		  if (__DEV__) warn('Unexpected error when starting the router:', err)
		})
		}
	 }
	}
	return router
}

useCallbacks 发布订阅模式管理路由守卫

export function useCallbacks<T>() {
  let handlers: T[] = []

  function add(handler: T): () => void {
    handlers.push(handler)
    return () => {
      const i = handlers.indexOf(handler)
      if (i > -1) handlers.splice(i, 1)
    }
  }

  function reset() {
    handlers = []
  }

  return {
    add, // 收集
    list: () => handlers,
    reset,
  }
}

push 开始导航

  function push(to: RouteLocationRaw) {
    return pushWithRedirect(to)
  }

pushWithRedirect

  • 会兼容push和redirect两种调用
  • 总体流程为:resolve匹配要跳转的记录->判断重定向(是则重新调用pushWithRedirect,设置replace属性为true)->判断是否跳转相同路由->开始导航->触发路由守卫->完成导航
  • 对于重复跳转的路由,返回 NAVIGATION_DUPLICATED 类型的错误即可,不再走后续流程
function pushWithRedirect(
  to: RouteLocationRaw | RouteLocation,
  redirectedFrom?: RouteLocation // 重定向路由信息位置
): Promise<NavigationFailure | void | undefined> {
	// 返回路由匹配结果
	const targetLocation: RouteLocation = (pendingLocation = resolve(to))
	// 当前位置
    const from = currentRoute.value //currentRoute始终指向当前页面路由信息,只有在最终完成导航,页面url改变后finalizeNavigation中再重新被赋值
    const data: HistoryState | undefined = (to as RouteLocationOptions).state
    const force: boolean | undefined = (to as RouteLocationOptions).force
    
    // 是否是重定向
    const replace = (to as RouteLocationOptions).replace === true
    // 重定向再次调用pushWithRedirect
    if (shouldRedirect)
      // 调用pushWithRedirect处理重定向路径
      return pushWithRedirect(
        assign(locationAsObject(shouldRedirect), {
          state:
            typeof shouldRedirect === 'object'
              ? assign({}, data, shouldRedirect.state)
              : data,
          force,
          replace,
        }),
        // keep original redirectedFrom if it exists
        redirectedFrom || targetLocation
      )


    // 如果配置了redirect重定向,返回重定向的路由信息
    const shouldRedirect = handleRedirectRecord(targetLocation)
	
	// 当重定向再次调用时,redirectedFrom会有值,为上次的targetLocation
    const toLocation = targetLocation as RouteLocationNormalized
    toLocation.redirectedFrom = redirectedFrom
	
	// 当守卫阻碍时,给出阻碍的原因
	let failure: NavigationFailure | void | undefined
	
    // 判断是否跳转当前路由记录
    if (!force && isSameRouteLocation(stringifyQuery, from, targetLocation)) {
      failure = createRouterError<NavigationFailure>(
        ErrorTypes.NAVIGATION_DUPLICATED,  // 重复跳转
        { to: toLocation, from }
      )
      // trigger scroll to allow scrolling to the same anchor
      handleScroll(
        from,
        from,
        // this is a push, the only way for it to be triggered from a
        // history.listen is with a redirect, which makes it become a push
        true,
        // This cannot be the first navigation because the initial location
        // cannot be manually navigated to
        false
      )
    }
	// 如果是重复跳转,返回 NAVIGATION_DUPLICATED 重复跳转的错误,结束流程
	return (failure ? Promise.resolve(failure): navigate(toLocation, from)
		//....
}

resolve返回路由记录匹配结果

  • 调用matcher.resolve返回当前路由的匹配记录及其所有上层链路放进matched属性中,当前路由的匹配记录会被放进数组最后一个
  // 路由匹配,并返回解析结果
 function resolve(
   rawLocation: Readonly<RouteLocationRaw>,
   currentLocation?: RouteLocationNormalizedLoaded
 ): RouteLocation & { href: string } {
   currentLocation = assign({}, currentLocation || currentRoute.value)
    if (typeof rawLocation === 'string') {
      // 返回完整的pathname、query、hash、path
      const locationNormalized = parseURL(
        parseQuery,
        rawLocation,
        currentLocation.path
      )
      // 返回路由记录的匹配结果
      const matchedRoute = matcher.resolve(
        { path: locationNormalized.path },
        currentLocation
      )

      const href = routerHistory.createHref(locationNormalized.fullPath)


      // locationNormalized is always a new object
      return assign(locationNormalized, matchedRoute, {
        params: decodeParams(matchedRoute.params),
        hash: decode(locationNormalized.hash),
        redirectedFrom: undefined,
        href,
      })
    }
	// 处理rawLocation为对象的情况
	// ...

}

matcher.resolve

  • 通过之前addRoute方法创建好的一条条matcher去匹配path,返回匹配的matcher及其上层链路所有matcher
function resolve(
  location: Readonly<MatcherLocationRaw>,
  currentLocation: Readonly<MatcherLocation>
): MatcherLocation {
    let matcher: RouteRecordMatcher | undefined
    let params: PathParams = {}
    let path: MatcherLocation['path']
    let name: MatcherLocation['name']
    
	// ... 其他根据name匹配等情况
	// 根据path进行匹配的情况
	if ('path' in location) {
	    // no need to resolve the path with the matcher as it was provided
	    // this also allows the user to control the encoding
	    path = location.path
	
	    if (__DEV__ && !path.startsWith('/')) {
	      warn(
	        `The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/router.`
	      )
	    }
	    // 通过每个matcher的正则匹配对应的记录
	    matcher = matchers.find(m => m.re.test(path))
	    // matcher should have a value after the loop
	
	    if (matcher) {
	      params = matcher.parse(path)!
	      name = matcher.record.name
	    }
	    // location is a relative path
	}
    // 根据当前路由匹配结果,获取该路由的所有上层链路,根据parent属性一路向上查找
    const matched: MatcherLocation['matched'] = []
    let parentMatcher: RouteRecordMatcher | undefined = matcher
    while (parentMatcher) {
      // 父路由在数组开头,当前路由记录在末尾
      matched.unshift(parentMatcher.record)
      parentMatcher = parentMatcher.parent
    } 
    return {
      name,
      path,
      params,
      matched,
      // 合并链路上的所有meta
      meta: mergeMetaFields(matched),
    }
}

navigate 开始守卫

function pushWithRedirect(
    to: RouteLocationRaw | RouteLocation,
    redirectedFrom?: RouteLocation
  ): Promise<NavigationFailure | void | undefined> {
	
	// ...
	
	return (failure ? 
		Promise.resolve(failure) : 
		navigate(toLocation, from))
		// 处理守卫中重定向
		.catch((error: NavigationFailure | NavigationRedirectError) =>
	        isNavigationFailure(error)
	          ? // navigation redirects still mark the router as ready
	          isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT)
	            ? error // 返回给下一个then
	            : markAsReady(error) // also returns the error
	          : // reject any unknown error
	          triggerError(error, toLocation, from)
	      )
	     // 
		.then((failure: NavigationFailure | NavigationRedirectError | void) => {
	        if (failure) {
	          // 处理在守卫中进行的重定向,由catch返回
	          if (
	            isNavigationFailure(failure, ErrorTypes.NAVIGATION_GUARD_REDIRECT)
	          ) {
	
	            return pushWithRedirect( // 重定向
	              assign(
	                {
	                  // preserve an existing replacement but allow the redirect to override it
	                  replace,
	                },
	                locationAsObject(failure.to), // 跳转守卫中返回的路径
	                {
	                  state:
	                    typeof failure.to === 'object'
	                      ? assign({}, data, failure.to.state)
	                      : data,
	                  force,
	                }
	              ),
	              // preserve the original redirectedFrom if any
	              redirectedFrom || toLocation
	            )
	          }
	        } else {
	          // 整个守卫过程没有被阻断
	          // finalizeNavigation 会调用 routerHistory.push 或 routerHistory.replace 更改路由记录
	          failure = finalizeNavigation(
	            toLocation as RouteLocationNormalizedLoaded,
	            from,
	            true,
	            replace,
	            data
	          )
	        }
	        // 导航最后触发 afterEach 守卫,从这里可以看出,路由变化后(页面地址更新)才会触发afterEach
	        triggerAfterEach(
	          toLocation as RouteLocationNormalizedLoaded,
	          from,
	          failure
	        )
	        return failure
	      })
}

抽取路由记录

根据路由记录中的matched会抽取三种类型路由记录

  • 存放要去到的路由记录中,新记录中不存在的旧记录
  • 存放要去到的路由记录中,新记录中存在的旧记录
  • 存放要去到的路由记录中,旧记录中没有的新记录
 function navigate(
   to: RouteLocationNormalized,
   from: RouteLocationNormalizedLoaded
 ): Promise<any> {
   let guards: Lazy<any>[]
   // 抽取 旧路由->新路由 中三种记录:旧记录离开、旧记录更新、新记录
   const [leavingRecords, updatingRecords, enteringRecords] =
     extractChangingRecords(to, from)
   // ...
}

extractChangingRecords

  • 抽离出三种记录,后续根据记录等类型触发相应的路由守卫,这里也说明了为什么resolve中要去拿到上层路由记录,导航时上层链路中的守卫都需要触发
// 抽取 旧路由->新路由 中三种记录:旧记录离开、旧记录更新、新记录
function extractChangingRecords(
  to: RouteLocationNormalized,
  from: RouteLocationNormalizedLoaded
) {
  const leavingRecords: RouteRecordNormalized[] = []  // 存放要去到的路由记录中,新记录中不存在的旧记录
  const updatingRecords: RouteRecordNormalized[] = [] // 存放要去到的路由记录中,新记录中存在的旧记录
  const enteringRecords: RouteRecordNormalized[] = [] // 存放要去到的路由记录中,旧记录中没有的新记录

  const len = Math.max(from.matched.length, to.matched.length)
  for (let i = 0; i < len; i++) {
    // 拿到要离开路由的记录
    const recordFrom = from.matched[i]
    if (recordFrom) {
      // 查找要去到路由记录中,是否已有路由记录
      // 如果有则把之前的路由记录放进 updatingRecords 只需更新
      if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))
        updatingRecords.push(recordFrom)
      // 如果要去到的路由记录中没有之前的记录,则把之前的记录放进 leavingRecords
      else leavingRecords.push(recordFrom)
    }
    const recordTo = to.matched[i]
    if (recordTo) {
      // the type doesn't matter because we are comparing per reference
      // 如果要去到的路由的是新记录,则把新记录放进 enteringRecords
      if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
        enteringRecords.push(recordTo)
      }
    }
  }

  return [leavingRecords, updatingRecords, enteringRecords]
}

guardToPromiseFn 用Promise包装守卫方法

  • 当守卫中开发者没有调用next,会自动调用next
export function guardToPromiseFn(
  guard: NavigationGuard,  // 守卫回调方法
  to: RouteLocationNormalized,
  from: RouteLocationNormalizedLoaded,
  record?: RouteRecordNormalized,// 记录
  name?: string  // name为注册路由时的名称
): () => Promise<void> {

}

extractComponentsGuards 从组件中抽取守卫

export function extractComponentsGuards(
  matched: RouteRecordNormalized[],  // 给定要抽取的路由记录
  guardType: GuardType,
  to: RouteLocationNormalized,
  from: RouteLocationNormalizedLoaded
) {

	const guards: Array<() => Promise<void>> = []
	
	for (const record of matched) {
		for (const name in record.components) { // 取出路由记录中对应的组件		
			let rawComponent = record.components[name]
			if (__DEV__) { // dev 环境下的一些动态组件检查
				 if ('then' in rawComponent) { 
				 	  warn(
			            `Component "${name}" in record with path "${record.path}" is a ` +
			            `Promise instead of a function that returns a Promise. Did you ` +
			            `write "import('./MyPage.vue')" instead of ` +
			            `"() => import('./MyPage.vue')" ? This will break in ` +
			            `production if not fixed.`
			          )
			          // 动态路由使用 () => import('./MyPage.vue') 而非 import('./MyPage.vue')
			          const promise = rawComponent
			          rawComponent = () => promise
				 }else if ( 
				    // 使用了 defineAsyncComponent() 的检查
			          (rawComponent as any).__asyncLoader &&
			          // warn only once per component
			          !(rawComponent as any).__warnedDefineAsync
			        ) {
			          ; (rawComponent as any).__warnedDefineAsync = true
			          warn(
			            `Component "${name}" in record with path "${record.path}" is defined ` +
			            `using "defineAsyncComponent()". ` +
			            `Write "() => import('./MyPage.vue')" instead of ` +
			            `"defineAsyncComponent(() => import('./MyPage.vue'))".`
			          )
			        }
				 
			}
			// 当路由组件挂载后,会在router-view组件中,将组件根据name存入对应record的instances中
			// 当路由记录的instances中没有组件时,说明组件还没挂载或者被卸载了,跳过update 和 leave相关守卫
			if (guardType !== 'beforeRouteEnter' && !record.instances[name]) continue
		}

	}
}

beforeRouteLeave 守卫收集

  • 拿到所有离开记录的守卫
  • 将守卫返回值变成Promise返回存入数组中返回,兼容动态路由和普通路由方式
function navigate(
  to: RouteLocationNormalized,
  from: RouteLocationNormalizedLoaded
): Promise<any> {
  let guards: Lazy<any>[]
  // 抽取 旧路由->新路由 中三种记录:旧记录离开、旧记录更新、新记录
  const [leavingRecords, updatingRecords, enteringRecords] =
    extractChangingRecords(to, from)
  // 拿到所有离开记录的守卫
  // 抽取组件中 beforeRouteLeave 守卫,将守卫返回值变成Promise返回存入数组中返回,兼容动态路由和普通路由方式
  guards = extractComponentsGuards(
    leavingRecords.reverse(),
    'beforeRouteLeave',
    to,
    from
  )
  // 对于setup中使用的onBeforeRouteLeave路由守卫,会被收集进 leaveGuards 中,和选项式进行合并
  for (const record of leavingRecords) {
    record.leaveGuards.forEach(guard => {
      guards.push(guardToPromiseFn(guard, to, from))
    })
  }

  // 如果导航被取消,产生一个 Promise 包装的 NavigationFailure
  const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(
    null,
    to,
    from
  )

  guards.push(canceledNavigationCheck)
  
  // 之后执行守卫
}

composition 守卫收集

registerGuard

  • 收集composition守卫的方法,之前在序列化记录的过程中,会专门创建 leaveGuards和updateGuards 两个Set属性来存放对应守卫
function registerGuard(
  record: RouteRecordNormalized,
  name: 'leaveGuards' | 'updateGuards',
  guard: NavigationGuard
) {
  const removeFromList = () => {
    record[name].delete(guard)
  }

  onUnmounted(removeFromList)
  onDeactivated(removeFromList)

  onActivated(() => {
    record[name].add(guard)
  })

  record[name].add(guard) // 将守卫添加进记录的leaveGuards|updateGuards属性中
}

onBeforeRouteLeave

  • 在setup中调用onBeforeRouteLeave时,会根据router-view组件中匹配到的路由记录,将守卫注入到leaveGuards属性中,进行导航时从记录上的该属性取出即可
export function onBeforeRouteLeave(leaveGuard: NavigationGuard) { // leaveGuard即为传递的路由回调
  // matchedRouteKey在router-view组件中provide,返回当前匹配的路由记录
  const activeRecord: RouteRecordNormalized | undefined = inject(
    matchedRouteKey,
    // to avoid warning
    {} as any
  ).value

  if (!activeRecord) {
    __DEV__ &&
      warn(
        'No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.vue?'
      )
    return
  }

  registerGuard(activeRecord, 'leaveGuards', leaveGuard)
}

onBeforeRouteUpdate

  • 同上
export function onBeforeRouteUpdate(updateGuard: NavigationGuard) {
  // 在router-view组件中provide,
  const activeRecord: RouteRecordNormalized | undefined = inject(
    matchedRouteKey,
    // to avoid warning
    {} as any
  ).value

  registerGuard(activeRecord, 'updateGuards', updateGuard)
}

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

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

相关文章

error: failed to push some refs to ... 就这篇,一定帮你解决

目录 一、问题产生原因 二、解决办法 三、如果还是出问题&#xff0c;怎么办&#xff1f;&#xff08;必杀&#xff09; 一、问题产生原因 当你直接在github上在线修改了代码&#xff0c;或者是直接向某个库中添加文件&#xff0c;但是没有对本地库同步&#xff0c;接着你想…

【数据结构初阶】第三节.顺序表详讲

文章目录 前言 一、顺序表的概念 二、顺序表功能接口概览 三、顺序表基本功能的实现 四、四大功能 1、增加数据 1.1 头插法&#xff1a; 1.2 尾插法 1.3 指定下标插入 2、删除数据 2.1 头删 2.2 尾删 2.3 指定下标删除 2.4 删除首次出现的指定元素 3、查找数据…

JAVA-线程池技术

目录 概念 什么是线程&#xff1f; 什么是线程池&#xff1f; 线程池出现背景 线程池原理图 JAVA提供线程池 线程池参数 如果本篇博客对您有一定的帮助&#xff0c;大家记得留言点赞收藏哦。 概念 什么是线程&#xff1f; 是操作系统能够进行运算调度的最小单位。&am…

ChatGPT的解释

概念 ChatGPT&#xff0c;美国OpenAI研发的聊天机器人程序,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然 语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人 类一样来聊天交流&am…

干货 | PCB拼板,那几条很讲究的规则!

拼板指的是将一张张小的PCB板让厂家直接给拼做成一整块。一、为什么要拼板呢&#xff0c;也就是说拼板的好处是什么&#xff1f;1.为了满足生产的需求。有些PCB板太小&#xff0c;不满足做夹具的要求&#xff0c;所以需要拼在一起进行生产。2.提高SMT贴片的焊接效率。只需要过一…

如何使用python画一个爱心

1 问题 如何使用python画一个爱心。 2 方法 桌面新建一个文本文档&#xff0c;文件后缀改为.py&#xff0c;输入相关代码ctrls保存&#xff0c;关闭&#xff0c;最后双击运行。 代码清单 1 from turtle import * def curvemove(): for i in range(200): right(1) …

Vue2笔记03 脚手架(项目结构),常用属性配置,ToDoList(本地存储,组件通信)

Vue脚手架 vue-cli 向下兼容可以选择较高版本 初始化 全局安装脚手架 npm install -g vue/cli 创建项目&#xff1a;切换到项目所在目录 vue create xxx 按照指引选择vue版本 创建成功 根据指引依次输入上面指令即可运行项目 也可使用vue ui在界面上完成创建&…

Python学习-----无序序列2.0(集合的创建、添加、删除以及运算)

目录 前言&#xff1a; 什么是集合 集合的三大特性 1.集合的创建 &#xff08;1&#xff09;直接创建 &#xff08;2&#xff09;强制转换 2.集合的添加 &#xff08;1&#xff09;add&#xff08;&#xff09;函数 &#xff08;2&#xff09;update() 函数 3.集合元…

澳大利亚访问学者申请流程总结

澳大利亚访问学者申请需要注意些什么&#xff1f;下面知识人网小编整理澳大利亚访问学者申请流程总结。1、取得wsk英语成绩&#xff0c;现在都是先买票再上车了。2、联系外导&#xff0c;申请意向接收函(email)。3、向留学基金委CSC提出申请。4、获批后&#xff0c;申请正式邀请…

Python 循环语句

Python的循环语句&#xff0c;程序在一般情况下是按顺序执行的。编程语言提供了各种控制结构&#xff0c;允许更复杂的执行路径。循环语句允许我们执行一个语句或语句组多次&#xff0c;下面是在大多数编程语言中的循环语句的一般形式&#xff1a;Python 提供了 for 循环和 whi…

【C++】类与对象理解和学习(上)

专栏放在【C知识总结】&#xff0c;会持续更新&#xff0c;期待支持&#x1f339;类是什么&#xff1f;类是对对象进行描述的&#xff0c;是一个模型一样的东西&#xff0c;限定了类有哪些成员&#xff0c;定义出一个类并没有分配实际的内存空间来存储它&#xff08;实例化后才…

视觉感知(二):车位线检测

1. 简介 本期为大家带来车位线检测相关知识点,以及算法工程落地的全流程演示。车位线检测是自动泊车领域必不可缺的一环,顾名思义就是采用环视鱼眼相机对路面上的车位线进行检测,从而识别出车位进行泊车。 较为常规的做法是使用四颗鱼眼相机环视拼接然后在鸟瞰图上做停车位…

如何利用状态机编程实现启保停控制(含Stateflow模型介绍)

状态机的介绍这里不再赘述,概念也很简单没有过多的复杂理论。下面我们直接给出具体实现过程。有限自动状态机详细讲解请参看下面的文章链接: PLC面向对象编程系列之有限状态机(FSM)详解_RXXW_Dor的博客-CSDN博客_有限状态机 plc实现编写PLC控制机器动作类程序时,当分支比较…

【自然语言处理】主题建模:BERTopic(实战篇)

主题建模&#xff1a;BERTopic&#xff08;实战篇&#xff09;BERTopic 是基于深度学习的一种主题建模方法。201820182018 年底&#xff0c;Devlinetal.Devlin\ et\ al.Devlin et al. 提出了 Bidirectional Encoder Representations from Transformers (BERT)[1]^{[1]}[1]。BER…

web自动化测试入门篇05——元素定位的配置管理

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

Window 安装 Docker

1.开启Hyper-v 2.确定后重启 3.双击安装包进行安装 4.安装完后系统重启 5.打开Docker软件提示&#xff1a;按下图操作后重启Docker 6.设置docker镜像仓库 { “experimental”: false, “features”: { “buildkit”: true }, “registry-mirrors”: [ “https://docker.mirr…

界面组件Telerik UI for WPF R1 2023——让导航栏变得更智能!

Telerik UI for WPF拥有超过100个控件来创建美观、高性能的桌面应用程序&#xff0c;同时还能快速构建企业级办公WPF应用程序。UI for WPF支持MVVM、触摸等&#xff0c;创建的应用程序可靠且结构良好&#xff0c;非常容易维护&#xff0c;其直观的API将无缝地集成Visual Studio…

大数据Kylin(一):基础概念和Kylin简介

文章目录 基础概念和Kylin简介 一、​​​​​​​OLTP与OLAP 1、​​​​​​​​​​​​​​OLTP 2、​​​​​​​​​​​​​​OLAP 3、​​​​​​​​​​​​​​OLTP与OLAP的关系 二、​​​​​​​​​​​​​​数据分析模型 1、星型模型 2、雪花模型 …

推进行业生态发展完善,中国信通院第八批RPA评测工作正式启动

随着人工智能、云计算、大数据等新兴数字技术的高速发展&#xff0c;数字劳动力应用实践步伐加快&#xff0c;以数字生产力、数字创造力为基础的数字经济占比逐年上升。近年来&#xff0c;机器人流程自动化&#xff08;Robotic Process Automation&#xff0c;RPA&#xff09;成…

【GPLT 二阶题目集】L2-012 关于堆的判断

将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种&#xff1a; x is the root&#xff1a;x是根结点&#xff1b; x and y are siblings&#xff1a;x和y是兄弟结点&#xff1b; x is the parent of y&#xff1a;x是y的父结…