vue3源码分析(三)—— 响应式系统(reactivity)

2023-05-16

系列文章目录

  1. 目录分析
  2. 初始化流程
  3. 响应式系统
  4. shared工具函数

文章目录

  • 系列文章目录
  • 前言
  • 一、定义响应式数据
    • 1、reactive(target)
    • 2、createReactiveObject
      • 2.1 入参
      • 2.2 响应式创建过程
      • 2.3 proxy handler
      • 2.4 get 做了什么?
      • 2.5 set 做了什么?
  • 二、依赖收集与派发更新
    • 1、依赖收集——track
    • 2、activeEffect
    • 3、effect
      • 3.1 effect如何被定义
      • 3.2 effect都在什么时候创建呢?
    • 3、依赖更新派发——trigger
  • 三、reactivity模块其他API
  • 总结


前言

无论在vue2或者vue3中,实现一个vue的响应式系统(改变数据,自动渲染组件),大致分为两部分:

  • 定义响应式数据
  • 依赖处理:收集和派发

packages/reactivity文件夹下的src中就是实现响应式逻辑的源码


一、定义响应式数据

  • vue2中,只需要在组件的data中定义数据,这些数据自动就会变成响应式的数据。原理是在组件初始化时,遍历data中的属性,依次利用Object.defineProperty中的getter setter进行拦截操作。
  • 而从vue3的setup API可知,我们需要调用 reactive 方法,手动定义一个响应式数据。
    如以下代码示例:
<template>
    <p>{{obj.count}}</p>
</template>

import {reactive} from 'vue'
const obj = reactive({count: 0})
obj.count++      // obj.count --> 1

reactive 方法背后的实现原理是什么呢???下面通过分析源码看下~~

1、reactive(target)

reactive方法主要作用:接收一个普通对象target然后返回该普通对象的响应式代理
定位到[runtime-core/reactivity/reactive.ts]源码:

export function reactive(target: object) {
  // 如果target是一个只读的响应式数据,则直接返回。因为已经是响应式了
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 创建一个响应式对象
  return createReactiveObject(
    target,   // 传入的响应式对象
    false,   // 是否只读
    mutableHandlers, // proxy handle
    mutableCollectionHandlers,  // 集合数据(Map,WeakMap,Set,WeakSet)的 proxy handle
    reactiveMap  // 全局缓存的一个map结构,存储所有的proxy
  )
}

reactive作为定义响应式数据的入口API,将一个target普通对象包装成响应式的对象,里面核心实现方法是:createReactiveObject(如下)

2、createReactiveObject

方法主要作用:创建一个响应式数据对象

/**
 * 创建响应式对象
 */
function createReactiveObject(
  target: Target,
  isReadonly: boolean, 
  baseHandlers: ProxyHandler<any>, 
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any> // 一个缓存weakmap,key是 target,value是响应式对象
) {
  // 判断一:若不是对象,则直接返回,特别注意null也直接返回, reactive(null) => null
  if (!isObject(target)) { 
    return target
  }
  
  // 判断二:若已经是proxy对象,并且。。。
  if (target[ReactiveFlags.RAW] &&!(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
    return target
  }

  // 判断三:从proxyMap数据中拿到key为target的响应式对象,若存在,则直接返回已创建过的响应式对象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // 判断四:类型不是object array map set weakmap weakset 都在白名单之外,不创建代理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

 // 通过所有判断,用有效的tagert创建new Proxy代理对象
  const proxy = new Proxy( 
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 存入缓存map 中
  proxyMap.set(target, proxy)
  return proxy
}

2.1 入参

  • target 传入的要被创建响应式的对象,同reactive(见上)的target
  • isReadonly 是否只读 ,通过reactive创建的为false非只读(见上)
  • baseHandlers 普通对象(Object,Array)的proxy handler(get,set,deleteProperty,has, ownKeys)。通过reactive创建的为mutableHandlers(见[baseHandlers.ts]),为之后new Proxy(target, handlers)传入
// proxy handlers
export const mutableHandlers: ProxyHandler<object> = { 
  get, //  拦截对象属性的读取,指向了方法 createGetter, 创建 get 劫持
  set, // 拦截对象属性的设置
  deleteProperty,  // 拦截delete proxy[propKey]操作
  has, // 拦截propKey in proxy操作
  ownKeys // 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
}
  • collectionHandlers !!!集合数据(Map,WeakMap,Set,WeakSet)的proxy handler 。通过reactive创建的为mutableCollectionHandlers(见[collectionHandlers.ts]),为之后new Proxy(target, handlers)传入

  • proxyMap 存储所有的proxy的WeakMap(Map与WeakMap区别可自行百度)结构数据,key是 target对象, value是响应式对象。reactive方法传入的是reactiveMap

// reactiveMap全局对象,存储所有的proxy对象
export const reactiveMap = new WeakMap<Target, any>()

2.2 响应式创建过程

首先,判断入参target是否为有效参数

  • isObject判断target是否为为对象,若不是对象,直接返回target
  • 若target已经是proxy对象,直接返回target
  • existingProxy = proxyMap.get(target)若从全局proxyMap对象中能拿到该对象的代理对象,则直接返回拿到的代理对象
  • 检查target的类型,若不是object array map set weakmap weakset 中的,则直接返回target

接着,创建proxy对象

  • 通过new Proxy(target, handlers)为target创建代理
  • 并且根据target类型,选择合适的handers:TargetType.COLLECTION ? collectionHandlers : baseHandlers
  • Map,Set,WeakMap,WeakSet 集合(TargetType.COLLECTION)类型,在proxy中使用的是collectionHandlers
  • Object,Array在proxy中使用的是 baseHandlers,下面具体看一下

最后,全局存储target对应的Proxy对象: proxyMap.set(target, proxy),并返回:return proxy

2.3 proxy handler

从上可知,reactive方法创建响应式对象时,传入的baseHandlers具体为mutableHandlers
mutableHandlers是从packages/src/reactivity/baseHandlers.ts文件引入而来,baseHandlers.ts文件定义了所有不同的baseHandlers,对应不同的数据处理

import {
  mutableHandlers,
  readonlyHandlers,
  ...
} from './baseHandlers'

baseHandlers.ts中的 mutableHandlers为例:new Proxy(target, mutableHandlers)

export const mutableHandlers: ProxyHandler<object> = {
  get, //  拦截对象属性的读取,指向了方法 createGetter, 创建 get 劫持
  set, // 拦截对象属性的设置
  deleteProperty,  // 拦截delete proxy[propKey]操作
  has, // 拦截propKey in proxy操作
  ownKeys // 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
}

通过以上分析可知,vue3将一个普通对象变为响应式对象,核心是通过new Proxy(ES6语法)创建代理,拦截set,get,deleteProperty,has,ownKeys操作监听。

2.4 get 做了什么?

访问对象属性会触发get函数

const get = createGetter()
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // ....
    // 当获取target自身属性时,receiver --》 reactiveMap.get(target)拿到代理对象
    if (key === ReactiveFlags.RAW && receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
            ? shallowReactiveMap
            : reactiveMap
        ).get(target)
    ) {
      return target
    }

    // 数组操作
    const targetIsArray = isArray(target)
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 关键代码
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // 非数组操作:关键代码-利用 Reflect 反射来获取原始值  
    const res = Reflect.get(target, key, receiver)

	// 关键代码2:调用track,收集依赖。若只读,不会变更,无需追踪
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key) 
    }
    //返回target属性值
    return res
  }
}

以上可知,get函数大致做了2件事:

  • track依赖收集(类似于vue2的getter中的dep.depend())。把组件渲染期间 依赖的property记录下来,之后触发依赖项setter时,会通知更新,使得组件重新渲染
  • get最终就是通过 Reflect.get 要拿到代理对象属性值res

思考为什么用 Reflect.get,而不是直接 target[key] 返回呢?

Reflect.get(target, key, receiver)
  • target (取值的目标对象)、 key (获取的值的键值)、receiver(如果target对象中指定了getter,receiver则为getter调用时的this值)。
  • 返回值:属性的值
var target = {
  foo: 1,
  get baz() {
    return this.foo;
  },
};
const observed = reactive(target)

此时,如果用target[key]取值,那么 this.foo 中的 this 就指向的是 target,而不是 observed,此时 this.foo 就不能收集到 foo 的依赖了,如果 observed.foo = 20 改变了 foo 的值,那么是无法触发依赖回调的,所以需要利用 Reflect.get 中的第三个参数receiver 将 getter 里的 this 指向代理对象observed。

2.5 set 做了什么?

设置对象属性会触发set函数

const set =  createSetter()
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
  
    let oldValue = (target as any)[key]
    if (!shallow) {
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 非数组类型,若旧值是ref类型,新值不是ref类型,那么直接复制给oldValue.value。ref数据在set value时就已经trigger依赖了,所以直接return
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    }

    // 关键代码,设置值
    const result = Reflect.set(target, key, value, receiver)
    
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
    // 对象上没有这个key,trigger add
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
     // 对象上有这个key且value发生了变化,trigger set
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    
    return result
  }
}

以上可知,set函数大致做了2件事:

  • set就是通过 Reflect.set 要设置代理对象属性值
  • trigger 派发更新,使得组件重新渲染。

二、依赖收集与派发更新

1、依赖收集——track

track方法在 reactivity模块中effect.ts模块有定义,如下:

export function track(target: object, type: TrackOpTypes, key: unknown) {
  // activeEffect不存在,直接return
  if (!shouldTrack || activeEffect === undefined) {
    return
  }

  // targetMap 依赖管理中心,用于收集依赖和触发依赖
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // target 在 targetMap 对应的值是 depsMap    targetMap(key:target, value:depsMap(key:key, value:dep(activeEffect)))
    // set结构防止重复
    targetMap.set(target, (depsMap = new Map()))
  }

  //此时经过上面的判断,depsMap 必定有值了。然后尝试在 depsMap 中获取 key
  let dep = depsMap.get(key)
  if (!dep) {  // 判断有无当前key对应的dep,没有则创建
    //如果没有获取到dep,说明 target.key 并没有被追踪,此时就在 depsMap 中塞一个值
    depsMap.set(key, (dep = new Set()))  // 执行了这句后,targetMap.get(target) 的值也会相应的改变
  }
  
  // 这个 activeEffect 就是在 effect执行的时候的那个 activeEffect
  if (!dep.has(activeEffect)) {  
    dep.add(activeEffect)  // 将effect放到dep里面
    activeEffect.deps.push(dep)  // 双向存储
  }
}
  • 以上涉及到了2个关键对象:activeEffecttargetMap。最终将activeEffect收集到targetMap全局Map结构中,同时将dep放到activeEffect下的deps数组。
  • 在vue2中,做依赖收集时,收集的是watcher,而vue3已经没有了watcher概念,取而代之的是effect(副作用函数)。
  • targetMap 是一个全局WeakMap对象,作为一个依赖收集容器,用于存储target[key]相应的dep依赖。targetMap.get(target) 获取target对应的depsMap,depsMap内部又是一个Map,key为target中的属性,depsMap.get(key)则为Set结构存储的target[key]对应的dep,dep中则是存储了所有依赖的effects。
  • 方便在修改数据触发set时,找到对应的effect依赖并执行。
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

2、activeEffect

activeEffect同样在 reactivity模块中effect.ts模块有定义,表示正在运行中的effect:

let activeEffect: ReactiveEffect | undefined  // 当前正在运行的effect

3、effect

effect 作为 reactive 的核心,主要负责收集依赖,更新依赖。

3.1 effect如何被定义

看下源码方法定义:

export function effect<T = any>(
  fn: () => T,  // 接收一个回调函数
  options: ReactiveEffectOptions = EMPTY_OBJ  // 配置{lazy:false,...}
): ReactiveEffect<T> {
  if (isEffect(fn)) {  // 若fn已经是一个effect对象
    fn = fn.raw  // effect对象中:effect.raw = fn
  }
  // 创建`effect`,执行createReactiveEffect后得到的是一个 function 即 reactiveEffect
  const effect = createReactiveEffect(fn, options)

  // 非lazy时,立即执行effect()。computed effect为lazy模块
  if (!options.lazy) {
    effect()
  }
  return effect
}
  • isEffect(fn)判定fn是否已经是一个effect,若是fn.raw就是原始的回调函数,fn = fn.raw (effect.raw = fn)
  • createReactiveEffect(fn, options)返回一个effect函数
  • options.lazy 若为false,非惰性更新,则立即执行一次。computed effect为true

下面看看effect如何被创建的:

function createReactiveEffect<T = any>(
  fn: () => T,  // 回调函数
  options: ReactiveEffectOptions  // 传入的配置
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    // 没有激活,说明调用了effect stop函数
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    if (!effectStack.includes(effect)) {
      cleanup(effect)  // 清除effect依赖
      try {  
        enableTracking() // 重新进行依赖收集
        effectStack.push(effect)  // 将当前effect压入effectStack栈
        activeEffect = effect  // 当前正在运行的effect
        return fn()  // 执行回调
      } finally { // 最后将effect出栈,恢复之前的状态
        effectStack.pop() 
        resetTracking()  // 重置依赖
        activeEffect = effectStack[effectStack.length - 1]   // 重置activeEffect
      }
    }
  } as ReactiveEffect
  effect.id = uid++  // 自增ID,唯一标识
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true  // 用于标识方法是否经历过effect,是否是effect
  effect.active = true  // effect是否激活,默认false。调用stop函数之后,修改为false
  effect.raw = fn   // 传入的fn
  effect.deps = []  // 持有当前effect的dep数组,在track时收集dep,dep就是追踪列表中对应的key,即 targetMap.get(target).get(key)
  effect.options = options
  return effect
}
  • 生成一个挂载了一系列属性:id,allowRecurse,_isEffect,active,raw,deps, options的reactiveEffect方法。
  • effectStack是一个作为全局变量的effect栈,类似于vue2中存储watcher的栈:const effectStack: ReactiveEffect[] = []
  • 在执行effect()时,若effectStack没有当前effect,首先清空effect.deps数组中的依赖,每次effect 运行时都会重新收集依赖。(在上面track那里,曾给deps数组塞值:activeEffect.deps.push(dep))
  • effectStack.push(effect) ,将当前effect压入effectStack栈
  • activeEffect = effect , 给activeEffect赋值,表示当前正在运行的effect,activeEffect 主要为了在收集依赖的时候使用
  • 执行回调fn(),最后将effect出栈,恢复之前的状态,重置activeEffect

3.2 effect都在什么时候创建呢?

1、mountComponent
2、computed
3、watcher、watchEffect

3、依赖更新派发——trigger

依赖收集完毕,接下来target的属性值修改会触发trigger,拿到相应的依赖并执行effect

export function trigger(
  target: object,
  type: TriggerOpTypes, // set | add | delete | clear
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)  //targetMap上面讲到,是全局的依赖收集器
  if (!depsMap) {  /* targetMap中没有该值,说明没有收集该effect,无需追踪*/
    return
  }

  const effects = new Set<ReactiveEffect>()
  
  /* 将合规的effect添加进effects set集合中*/
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) { // 若是clear
    depsMap.forEach(add)  // 触发对象所有的effect
  } else if (key === 'length' && isArray(target)) {  // 若数组的length发生变化
    depsMap.forEach((dep, key) => { 
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else { // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const run = (effect: ReactiveEffect) => {
    ...
    // 如果 scheduler 存在则调用 scheduler,计算属性拥有 scheduler
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  // 关键代码,所有的effects会执行内部run方法
  effects.forEach(run)
}
  • 首先校验一下target有没有被收集依赖,若没有收集依赖,则return
  • 根据不同的操作clear add delete set,将合规的effect加入到effects set集合中
  • 遍历effects set集合,执行effect函数

以上,整个响应式系统已经形成了一个基本的闭环。

三、reactivity模块其他API

上一篇大致也介绍了vue源码各目录存放的API,那响应式模块都在runtime-core/reactive目录中。
打开packages/reactivity/src/index.ts,可看到导出的API
主要是四个部分:

  • ref 响应式的关键入口,作用同reactive
  • reactive 响应式的关键入口 (本文重点讲述)
  • computed 同vue2 的computed选项
  • effect 作用同vue2的watcher(Vue3中已经没有了watcher概念,由effect取而代之)
export {
  ref,
  shallowRef,
  isRef,
  toRef,
  toRefs,
  unref,
  proxyRefs,
  customRef,
  triggerRef,
  Ref,
  ToRefs,
  UnwrapRef,
  ShallowUnwrapRef,
  RefUnwrapBailTypes
} from './ref'
export {
  reactive,           // --> 创建响应式对象的入口方法
  readonly,
  isReactive,
  isReadonly,
  isProxy,
  shallowReactive,
  shallowReadonly,
  markRaw,
  toRaw,
  ReactiveFlags,
  DeepReadonly,
  UnwrapNestedRefs
} from './reactive'
export {
  computed,
  ComputedRef,
  WritableComputedRef,
  WritableComputedOptions,
  ComputedGetter,
  ComputedSetter
} from './computed'
export {
  effect,
  stop,
  trigger,
  track,
  enableTracking,
  pauseTracking,
  resetTracking,
  ITERATE_KEY,
  ReactiveEffect,
  ReactiveEffectOptions,
  DebuggerEvent
} from './effect'
export { TrackOpTypes, TriggerOpTypes } from './operations'

总结

vue2响应式原理:

  • 在 Vue2 里内部通过 Object.defineProperty API 劫持数据的变化,深度遍历 data 函数里的对象,给对象里每一个属性设置 getter、setter。
  • 触发 getter 会通过 Dep 类做依赖收集操作,收集当前 Dep.target, 也就是 watcher。
  • 触发 setter,执行 dep.notify 通知收集到的各类 watcher 更新,如 computed watcher、user watcher 、渲染 watcher。

Vue3响应式原理:

  • Vue3 用 ES6的Proxy 重构了响应式,new Proxy(target, handler)
  • Proxy 的 get handle 里 执行track() 用来跟踪收集依赖(收集 activeEffect,也就是 effect )
  • Proxy 的 set handle 里执行 trigger() 用来触发响应(执行收集的 effect)
  • effect 副作用函数 代替了 watcher
    在这里插入图片描述
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue3源码分析(三)—— 响应式系统(reactivity) 的相关文章

随机推荐

  • exe应用程序无法启动,因为应用程序的并行配置不正确

    问题 xff1a exe应用程序无法启动 xff0c 因为应用程序的并行配置不正确 有关详细信息 xff0c 请参阅应用程序事件日志 xff0c 或使用命令行 sxstrace exe 工具 原因查找 xff1a 1 xff09 开始 所有
  • TortoiseSVN is locked in another working copy

    TortoiseSVN提交报错 TortoiseSVN is locked in another working copy 原因 xff1a 可能是因为打开了多个commit会话 xff0c 然后又去修改了提交文件的内容 xff0c 导致文
  • Java对接企业微信

    最近需要对接企业微信 xff0c 例如将风险测评结果推送给企业微信中对应的用户 xff0c 然后用户对结果进行查看与确认操作 xff0c 所以这里就涉及到两方面 xff1a 1 xff09 将外部系统内容推送到企业微信 xff1b 2 xf
  • 微众银行面试

    机缘巧合 xff0c 其实并没有换工作的想法 xff0c 却收到了微众的面试邀请 xff0c 就想着去看看当是增长见识吧 xff0c 因为已经好久没准备面试的事情了 xff0c 而且微众毕竟作为腾讯系的看起来好像也不错 说实话那边离地铁站是
  • #TP4056#--3.7V锂电池充放电电路(实践日志篇)

    成就更好的自己 本篇为小型电源的实践日志 xff0c 内附各种充电应用电路 xff0c 并开源TP4056应用电路AD的原理图和PCB xff1b 先放一点锂电池常识性的知识 xff1a 锂电池是一类由锂金属或锂合金为负极材料 使用非水电解
  • ROS四旋翼无人机快速上手指南(1):无人机系统硬件概述与指南简介

    成就更好的自己 ROS无人机快速上手指南旨在于让使用此无人机开发平台的比赛参赛人员 xff0c 算法设计人员 xff0c 无人机爱好者更加快速的了解底层控制运作原理 xff0c 从而缩短开发周期 xff0c 减少掉坑次数 xff0c 快速验
  • ROS四旋翼无人机快速上手指南(2):Ubuntu18.04与ROS系统

    成就更好的自己 目录 Jetson版Ubuntu以及ROS的安装 xff1a ROS特性及Nano开发问题 PX4与Gazebo仿真环境 ROS与MATMAB仿真 Jetson版Ubuntu以及ROS的安装 xff1a ROS机器人系统运行
  • ROS四旋翼无人机快速上手指南(4):阿木实验室PX4功能包飞行控制分析与讲解(重点章节)

    成就更好的自己 这一章详细讲解一下阿木实验室 AMOV 的开源项目px4 command功能包 xff0c 此功能包通过mavlink协议直接控制烧录px4固件的自驾仪 xff0c 还融合了来自各个传感器的位姿 xff0c 距离等信息 xf
  • ROS四旋翼无人机快速上手指南(5):快速部署上层算法的操作与思路

    成就更好的自己 经过本系列上一篇文章关于PX4 command飞行控制功能包的分析 xff0c 相信大家对于飞整个流程有个大概的了解 xff0c 本章将在此基础上详细讲解一下应用级算法构建的思路与操作方法 关于PX4 command飞行控制
  • USB系列-LibUSB使用指南(1)-Windows下的报错与踩坑

    成就更好的自己 时隔一年再次开始撰写博客 xff0c 这一年的时间经历了很多 xff0c 现在终于稳定下来 以后很长一段时间都能够稳定的学习和更新 时间将会聚焦于USB和PCIe的开发进行 xff0c 能和大家共同进步真的很高兴 本篇为US
  • rosdep init和rosdep update出现问题解决,以及ros编程问题

    如果你在执行 rosdep init 过程中出现以下错误 ERROR cannot download default sources list from https raw githubusercontent com ros rosdist
  • linux内核体系结构

    本节介绍了linux内核的编制模式和体制结构 xff0c 然后详细描述linux内核代码目录中组织形式以及子目录各个代码文件的主要功能以及基本调用的层次关系 一个完整可用的操作系统主要由4部分组成 xff1a 硬件 操作系统内核 操作系统服
  • 基于OpenLTE的4G移动通信网络实验指导书

    基于本人本科毕业设计的成果 xff0c 设计了一套基于开源SDR项目 OpenLTE的实验指导书 xff0c 可以指引读者通过平台源码 平台提供的实验和结合实验对3GPP规范的解读分析来更直观 更多元立体的学习无线通信技术 xff0c 而不
  • 一行代码实现数组中数据频次值

    问题 xff1a 一行代码实现统计数组中每个name出现的次数 数组示例如下 xff1a 期望结果 xff1a 39 哈哈 39 2 39 哈哈1 39 1 39 哈哈2 39 2 span class token keyword var
  • mac bash_profile报错导致所有命令失效解决办法

    项目场景 xff1a 搭建flutter环境时 xff0c 在mac下需要配置环境变量 问题描述 xff1a 配置环境变量 xff0c 需要修改 bash profile文件 xff0c 修改文件保存退出后 发现文件有报错 xff0c 导致
  • 我理解的“大前端”

    前言 随着业务场景越来越复杂 xff0c 前端技术也越来越丰富 xff0c 这几年也迎来爆发期 xff0c 大前端 的概念逐渐涌现 本图根据本人理解整理 xff0c 如有不足 xff0c 欢迎指正 xff0c 感谢 一 终端 PC端 PC端
  • 前端获取用户地理定位的几种方式(Geolocation API,微信,腾讯地图)

    文章目录 前言一 Geolocation API二 微信 SDK1 引入jssdk2 获取签名 xff0c 注入配置3 调用JS SDK api 获取位置 三 第三方服务 xff08 腾讯地图服务 xff09 1 引入js文件2 获取定位
  • H5 软键盘自动搜索功能

    业务场景 xff1a 通常APP中的顶部搜索栏 xff0c 都配一个搜索按钮 同时输入文字软键盘弹起 xff0c 回车键自动变成搜索键 xff0c 点击软键盘中的搜索能进行搜索功能 xff0c 如下图taobao所示 xff1a 思考 软键
  • 基于vue-cli3构建自己的UI库

    文章目录 前言一 创建项目二 编写组件1 button组件2 引入字体图标icon文件3 引入Button组件看效果 三 修改目录结构1 packages文件夹2 打包修改2 demo展示 四 将UI库部署到npm上五 项目使用自己的UI库
  • vue3源码分析(三)—— 响应式系统(reactivity)

    系列文章目录 目录分析初始化流程响应式系统shared工具函数 文章目录 系列文章目录前言一 定义响应式数据1 reactive target 2 createReactiveObject2 1 入参2 2 响应式创建过程2 3 proxy