vue2源码分析-vue入口文件global-api分析

news2025/1/11 14:43:36

文章背景

  • vue项目开发过程中,首先会有一个初始化的流程,以及我们会使用到很多全局的api,如 this.$set this.$delete this.$nextTick,以及初始化方法extend,initUse, initMixin , initExtend, initAssetRegisters 等等那它们是怎么实现,让我们一起来探究下吧

源码目录

  • 在这里插入图片描述

global-api代码初始化

  • 代码路径 src\vue-2.6.14\src\core\global-api\index.js

代码分析

vue挂载的全局属性

  • vue.util 公共方法
    • warn 代码警告处理
    • extend 将源数据中的值拷贝到目标数据中
    • mergeOptions 合并选项
    • defineReactive 定义响应式数据
  • set set 方法,修改对象的某个属性,以更新视图
  • del del 方法,删除数组的某个索引下的数据 或者 对象的某个属性
  • nextTick nextTick方法
  • observable 调用observer类的observe方法,将对象变为响应式对象,并且返回该响应式对象
  • options vue的一些选项

初始化方法

  • extend
    • extend函数此处用于扩展,将源数据中的值拷贝到目标数据中,数据类型兼容到了 对象和数组
  • initUse
    • 初始化插件
  • initMixin
    • 初始化混入 逻辑有点多,后期详细补充
  • initExtend
    • 构造一个vue的子类
  • initAssetRegisters(Vue)
    • 注册或者获取全局组件、指令、过滤器
/* @flow */

import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'

import {
  warn,
  extend,
  nextTick,
  mergeOptions,
  defineReactive
} from '../util/index'
export function initGlobalAPI(Vue: GlobalAPI) {
  // note 初始化全局api sjf-step2
  debugger
  const configDef = {}
  configDef.get = () => config
  // 初始化vue的全局config配置
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      // set在修改对象的字段时触发,此处表示在设置 Vue的config属性时触发
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
    //在开发环境中,不允许替换整个Vue.config 对象,只允许设置单独的字段
  }
  Object.defineProperty(Vue, 'config', configDef)
  // 
  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {//将全局方法,挂载到Vue的util方法上
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set// set 方法,修改对象的某个属性,以更新视图
  Vue.delete = del // del 方法,删除数组的某个索引下的数据 或者 对象的某个属性
  Vue.nextTick = nextTick // nextTick方法
  // set delete nextTick 除了可以使用 this.$set,this.$delete,this.$nextTick调用,亦可以在Vue实例上直接调用

  // 2.6 explicit observable API
  Vue.observable = (obj) => {
    observe(obj)// 调用observer类的observe方法,将对象变为响应式对象
    return obj//并且返回该响应式对象
  }

  Vue.options = Object.create(null)
  //调用 Object.create(null) 创建一个没有原型链的空对象
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // ASSET_TYPES = ['component','directive','filter'] 
  // 遍历ASSET_TYPES数组,给Vue.options添加三个属性,分别是components,directives,filters

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.

  Vue.options._base = Vue
  // 将vue赋值给Vue.options._base,用于在weex的多实例场景下,标识“基础”构造函数,以扩展所有纯对象组件
  extend(Vue.options.components, builtInComponents)
  // 将builtInComponents对象的属性,复制到Vue.options.components上
  // extend函数此处用于扩展
  initUse(Vue)//初始化插件
  initMixin(Vue)//初始化混入 逻辑有点多,后期详细补充
  initExtend(Vue)//构造一个vue的子类 
  initAssetRegisters(Vue) // 注册或者获取全局组件、指令、过滤器
}


extend 扩展数据

  • 代码路径 src\vue-2.6.14\src\shared\util.js
  • extend函数此处用于扩展,将源数据中的值拷贝到目标数据中,数据类型兼容到了 对象和数组
  • 入参说明
    • to 目标值,_from 源数据
  • 核心代码说明
    • const key in _from
    • _from 可以是一个数组 ,也可以是一个对象
    • 如果是数组,则key是数组的索引
    • 如果是对象,则key是对象的键值
export function extend (to: Object, _from: ?Object): Object {
  // sjf-note-best-code
  // 将源数据中的值拷贝到目标数据中,数据类型兼容到了 对象和数组
  for (const key in _from) {
    // const key in _from 
    // _from 可以是一个数组 ,也可以是一个对象
    // 如果是数组,则key是数组的索引
    // 如果是对象,则key是对象的键值
    to[key] = _from[key]
  }
  //当我们还在苦逼地用 Array.isArray 判断数据类型是否是数组来区分代码写两套逻辑时, vue源代码已经实现了,一个代码同时兼容了数组和对象的拷贝,简直不要太优秀啊
  return to
}
  • 优秀代码设计思想
    • 当我们还在苦逼地用 Array.isArray 判断数据类型是否是数组来区分代码写两套逻辑时, vue源代码已经实现了,一个代码同时兼容了数组和对象的拷贝,简直不要太优秀啊

initUse 初始化插件

  • 代码路径 src\vue-2.6.14\src\core\global-api\use.js
  • 入参说明
  • plugin 参数 有可能是函数也有能是对象
    • 当是对象时需要访问install属性并且使用apply修改this为plugin后调用
    • 当是函数时则直接调用
  • 核心代码说明
  • installedPlugins
    • 定义 installedPlugins 参数,
    • 读取vue实例上的_installedPlugins属性,如果读取不到则赋值为空数组
    • 缓存插件,将插件添加到已安装插件的数组中
  • 防止插件的重复加载
    • 如果插件已经安装,则将插件直接返回
import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // _installedPlugins 函数内部的变量 用来存储已经注册的插件
    // _installedPlugins 不存在时,初始化为一个空数组
    // vue源码中并没有声明 _installedPlugins 并且此处是非箭头函数
    if (installedPlugins.indexOf(plugin) > -1) {
      // 防止插件的重复加载
      return this
    }
    const args = toArray(arguments, 1)
    // toArray 方法将参数转化为数组
    // arguments 是一个类数组对象,有length属性,但是没有数组的方法
    // 通过toArray方法将arguments转化为数组,并且去掉第一个参数
    args.unshift(this)//再将this添加到args数组的头部
    if (typeof plugin.install === 'function') {
      // 如果插件有install方法,则调用install方法
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      // 如果插件是一个函数,则直接调用
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)//缓存插件,将插件添加到已安装插件的数组中
    return this
  }
}

initMixin 初始化混入

  • 初始化混入 逻辑有点多,后期单独出一篇文章详细补充

initExtend 初始化vue组件对象

  • 代码路径 src\vue-2.6.14\src\core\global-api\extend.js
    函数说明
  • 基于构造函数Vue,创建了一个Vue组件对象,并初始化vue选项式api
    核心代码说明
  • 获取父类的cid,也就是组件的id,即组件的标识
  • cachedCtors,定义一个缓存对象
    • 如果缓存对象中已经有了当前类的id,则表明在此操作前已经创建过了
    • 通过id访问到缓存对象中的子类,并直接返回
    • 这样做可以提高性能,避免重复的子类创建
  • 获取到组件的name属性
    • 如果当前传入的组件选项中有name属性则直接读取,否则访问Super(父类的)name属性
    • name是组件的标识,一般确实永不到,但是组件递归的时候必须要写,否则无法调用子组件
  • 调用validateComponentName校验name属性是否合法
  • sub类的处理
    • 通过函数的方式创建一个sub类,并将父类的原型链继承到子类中(原型链继承方式),同时还需要修正constructor的指向
    • 将父类的options字段和传入组件对象选项进行合并,并保存在子类sub的options字段中
    • 将父类保存到子类的 super 字段中,确保子类能拿到父类
  • 初始化 props
    • props 是当前组件的props属性
    • const in props 遍历对象的属性
    • 将 组件传入的props属性代理到组件实例的_props属性上
    • 初始化 computed
    • computed 是当前组件的computed属性
    • const in props 遍历对象的属性
    • defineComputed 逻辑暂时没看,后期单独出一期文章补充
  • extend,mixin,use等api添加到子类中
  • 新增 superOptions,extendOptions,sealedOptions等选项
  • 将子类返回
/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { defineComputed, proxy } from '../instance/state'
import { extend, mergeOptions, validateComponentName } from '../util/index'

export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   */
  // sjf-note-best-code
  Vue.cid = 0//初始化cid为0 可以理解为组件的标识
  let cid = 1 

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    // 基于构造函数Vue,创建了一个Vue组件对对象,并初始化vue选项式api
    extendOptions = extendOptions || {}
    // extendOptions是一个对象,如果没有传入,则初始化为空对象
    // extendOptions 是组件传入的选项
    const Super = this//将this(vue的实例)赋值给Super
    const SuperId = Super.cid

    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    //定义一个缓存对象,用生成的id作为属性键,如果cachedCtors[SuperId]存在则表明该组件的构造函数已经生成,直接返回即可
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }

    const name = extendOptions.name || Super.options.name
    // extendOptions 当前组件的选项 的 
    // Super 是 Vue的实例 options 是Vue的选项
    // 如果传入了extendOptions.name 则使用extendOptions.name 否则使用Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      //判断是否是开发环境
      validateComponentName(name)
      //校验组件名称是否合法, 如果不合法会抛出异常
    }
    const Sub = function VueComponent (options) {

      this._init(options)
      //此处的this是vue实例 ,调用了_init方法,初始化组件实例
    }
    Sub.prototype = Object.create(Super.prototype)
    // Super.prototype 是Vue的原型对象
    // Object.create(Super.prototype) 创建了一个新的对象,该对象的原型是Super.prototype
    // Object.create() 方法创建一个新对象,不会有原型链上的属性和方法
    
    Sub.prototype.constructor = Sub// consturctor指向自身 sjf-todo
    // 通过函数的方式创建一个sub类,并将父类的原型链继承到子类中(原型链继承方式),同时还需要修正constructor的指向
    Sub.cid = cid++//组件的cid自增1

    Sub.options = mergeOptions(
      //将父类的options字段和传入组件对象选项进行合并,并保存在子类sub的options字段中
      Super.options,//Vue的选项
      extendOptions//当前组件的选项
    )
    Sub['super'] = Super//将Super(根组件的实例)赋值给当前组件的super属性
// 将父类保存到子类的 super 字段中,确保子类能拿到父类
    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      //如果当前组件有props属性,则初始化props
      initProps(Sub)  
    }
    if (Sub.options.computed) {
      //如果当前组件有computed属性,则初始化computed
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use
// Vue的一些原生API 等扩展到子构造器上边
    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
// Vue的一些原生API 等扩展到子构造器上边
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }
}

function initProps (Comp) {
  const props = Comp.options.props
  // props 是当前组件的props属性
  for (const key in props) {  
    // const in props 遍历对象的属性
    proxy(Comp.prototype, `_props`, key)
    // proxy 代理数据
    // 此处是将 组件传入的props属性代理到组件实例的_props属性上
  }
}

function initComputed (Comp) {
  const computed = Comp.options.computed
  // computed 是当前组件的computed属性
  for (const key in computed) {
    // const in computed 遍历对象的属性
    defineComputed(Comp.prototype, key, computed[key])
    // defineComputed 代理数据
    // 此处是将 组件传入的computed属性代理到组件实例的_props属性上
  }
}


initAssetRegisters 注册或者获取全局组件、指令、过滤器

  • src\vue-2.6.14\src\core\global-api\assets.js
    函数说明
  • 注册或者获取全局 component 组件、directive指令、 filter 过滤器
    函数入参说明
  • id 标识名称id
  • definition 定义类型是函数或者对象
    • 当type 是directive或者filter的时候,是函数
    • 当type是components的时候是,对象
      核心代码说明
  • 判断是是否有传入 definition 参数
    • 无则表明不是注册全局组件、指令、过滤器,而直接读取,直接通过标识名称id读取返回即可
    • 有则标明是注册
  • 校验组件名是否合规
    • 非生产环境 并且type值是component组件名称,则需要校验组件名是否合规
    • 如果不合规则在 validateComponentName函数中抛出异常,下文有说明
  • 根据type类型分别做逻辑处理
  • component
  • type为 component,并且 isPlainObject 判断 definition类型是 object,则执行下边逻辑
    • 读取 name 属性,如果读不到definition的name属性,则使用标识名称id
    • 调用 vue 的extend扩展定义,并重新赋值
  • directive
    • type值为 directive ,并且 definition的类型为 function
    • 定义一个对象有bindupdate属性,赋值为definition
  • 以上逻辑处理好了directive和component选项下的definition,filter选项不需要特殊处理
  • 根据选项式api对象读取到指定id的component 组件、directive指令、 filter 过滤器并赋值为 definition
  • 将 definition返回给调用者
/* @flow */

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  /* 
  'component',
  'directive',
  'filter'
  */
//   注册或者获取全局组件、指令、过滤器
  debugger
  ASSET_TYPES.forEach(type => {//定义资源注册方法
    Vue[type] = function (
      id: string,//标识名称id
      definition: Function | Object//定义函数或者对象 
      // 当type 是directive或者filter的时候,是函数
      // 当type是components的时候是,对象
    ): Function | Object | void {
      // 
      console.log("id_definition",type,id,definition)
      if (!definition) {
        // 没有传入definition 的时候,则之直接读取并返回
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          // 非生产环境 交易案组件名称,如果不合规,则抛出异常
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id//设置 definition.name属性 
          // 值为组件传入的name || id
          definition = this.options._base.extend(definition)
          // 调用 vue 的extend扩展定义,并重新赋值
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
          // 自定义指令的处理逻辑
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

源码中使用到的小代码片段补充

ASSET_TYPES

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

proxy代理函数

  • 代码引用地址 src\vue-2.6.14\src\core\instance\state.js
  • 使用defineProperty代理数据
const sharedPropertyDefinition = {//代理数据的配置
  enumerable: true,//是否可枚举
  configurable: true,//是否可修改删除
  get: noop,//get方法,用于读取数据 noop 定义一个临时的空的函数
  set: noop//set方法,用于设置数据 noop 定义一个临时的空的函数
}

export function proxy (target: Object, sourceKey: string, key: string) {
  //  代理数据
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key] //返回数据
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val//设置数据
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
  // 通过Object.defineProperty方法,将sharedPropertyDefinition的get和set方法绑定到target对象的key属性上
}

toArray 将目标数组中的数据,从指定位置开始拷贝

  • 代码引用地址 src\vue-2.6.14\src\shared\util.js
  • 将目标数组中的数据,从指定位置开始拷贝

代码实现原理

  1. 接收字段
    • list 源数据
    • start 从源数据的哪个位置开始拷贝 默认为 0
  1. 声明一个变量i值为 list.length - start
  2. while 循环
    • 然后将 i + start
    • 则 最后的 i + start 仍然等于 list.length
    • 当i的值为0时,循环结束
  1. 则实现了将原数组从指定位置拷贝到目标数组的操作
    代码优秀设计思想分析,它凭什么优秀
  2. 第一点,一般拷贝数组,
  • 常规的操作就是 for 循环,遍历数组中的每一个数据,然后根据条件判断,将数组中满足条件的数据拿到或者将不满足条件的数据过滤掉,这样确实能够处理数据,但是却需要将每条数据都循环一遍 .
  • 优秀的写法 此处,使用了while循环,不需要循环每一条数据,在调件变量为false的时候就跳出了循环,提高了代码的执行效率
  1. 可拷贝从指定位置开始的数据
  2. 语法层面上使用了 new Array(i)
  • 创建了指定数组,并且指定了位数,同时生明其中的每个变量都为empty
  • 很多人都知道new Array 却不知道 可以传入个数参数

当我们还开为这中需求苦恼,并且写一堆js时,vue源码中已经写出来如此优秀的代码,值得参考

export function toArray (list: any, start?: number): Array<any> {
  // sjf-note-best-code
  start = start || 0
  let i = list.length - start
  const ret: Array<any> = new Array(i)
  // new Array() 创建一个空数组
  // new Array(i) 创建i个为空的数组
  // 相当于 Array.fill(0,i,undefined)

  while (i--) {
    ret[i] = list[i + start]
    //先将 i = list.length - start 
    // 然后将 i + start 
    // 则 最后的 i + start 仍然等于 list.length
    // 只不过是i 的值是从 list.length - start 到 0 递减
    // 当i的值为0时,循环结束
  }
  // while循环的优点是,即即使数组的数据没有处理完成,并且不需要break或者return false 以及 throw new Error()来结束循环,就可以自动结束循环
  return ret
  // 将处理好的数组返回
}

isPlainObject

  • 代码引用地址 src\vue-2.6.14\src\shared\util.js
  • 判断 数据类型是否是 object
export function isPlainObject (obj: any): boolean {
  return _toString.call(obj) === '[object Object]'
}

validateComponentName

  • 代码引用地址 src\vue-2.6.14\src\core\util\options.js
  • 校验组件名是否合法
export function validateComponentName (name: string) {
  if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'should conform to valid custom element name in html5 specification.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    // 
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
}

isBuiltInTag 判断组件名是不是Vue内置的

  • 引用地址 src\vue-2.6.14\src\shared\util.js
export const isBuiltInTag = makeMap('slot,component', true)
// 判断组件名是不是内置的标签,不是原生html的标签而是vue内置的标签

isReservedTag

- 判断是否是 原生html或者isSVG标签 
export const isReservedTag = (tag: string): ?boolean => {
  return isHTMLTag(tag) || isSVG(tag)
}
export const isHTMLTag = makeMap(
  'html,body,base,head,link,meta,style,title,' +
  'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
  'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
  'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
  's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
  'embed,object,param,source,canvas,script,noscript,del,ins,' +
  'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
  'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
  'output,progress,select,textarea,' +
  'details,dialog,menu,menuitem,summary,' +
  'content,element,shadow,template,blockquote,iframe,tfoot'
)
export const isSVG = makeMap(
  'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' +
  'foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' +
  'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view',
  true
)

致谢

  • 感谢vue源码给予了我学习借鉴的机会
  • 感谢日常项目中给我的锻炼
  • 这篇文章是vue初始化流程的代码,其实没什么难度,但是这是最开始的一步,无法跳过,后期会有核心代码的分析,如果感兴趣请关注

  • 感谢您百忙之中抽时间阅读我写的博客,谢谢你的肯定,也希望对您能有所帮助
  • 如果您有更好的见解请在评论区留言或者私聊我,期待与您的交流

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

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

相关文章

2024年展望Android原生开发的现状,2024网易Android高级面试题及答案

没有稳定的工作&#xff0c;只有稳定的能力。 又到了万物复苏的季节&#xff0c;在程序猿这个行当里&#xff0c;作为 Android 开发出生的&#xff0c;在经历了八年的脱发生涯后&#xff0c;有了越来越多的想法和感触 趋势 随着各类移动跨平台的兴起&#xff0c;在 ReactNati…

基于SpringBoot+Vue实现的人力资源管理系统

系统介绍&#xff1a; 基于SpringBootVue实现的人力资源管理系统是为了提高企业人力资源管理水平而开发的。主要目标是通过对员工及人力资源活动信息&#xff08;考勤、工资)等的编制来提高企业效率。 系统一共分为五大菜单项&#xff0c;分别是首页、薪资管理、权限管理、系统…

魔行观察-每日品牌监测-沪上阿姨-开店趋势

今日监测对象&#xff1a;沪上阿姨&#xff0c;监测时间段&#xff1a;2014年9月至2023年12月&#xff0c;发布时间&#xff1a;2024-03-05 数据获取地址&#xff1a;魔查查https://www.moxingdata.com/品牌基础信息 现有门店人均消费覆盖省份经营模式投资金额814416.531特许/…

工时管理软件:为什么企业需要工时跟踪?

工时跟踪对于企业经营来说&#xff0c;可能不是首要事项。工时跟踪有什么用&#xff1f; 管理学大师彼得德鲁克曾说过&#xff1a;If you can’t measure it, you can’t improve it&#xff08;如果无法衡量&#xff0c;就无法改进&#xff09;。企业经营也是同样道理&#x…

分布式系统中常用的缓存方案

1. 引言 随着互联网应用的发展和规模的不断扩大&#xff0c;分布式系统中的缓存成为了提升性能和扩展性的重要手段之一。本文将介绍几种在分布式系统中常用的缓存方案&#xff0c;包括分布式内存缓存、分布式键值存储、分布式对象存储和缓存网关等。 1.1 缓存在分布式系统中的…

FEP容量瓶多应用于制药光电光伏行业

常用规格&#xff1a;25ml、50ml、100ml、250mlFEP容量瓶也叫特氟龙容量瓶&#xff0c;容量瓶是为配制一定物质的量浓度的溶液用的精确定容器皿&#xff0c;常和移液管配合使用。广泛用于ICP-MS、ICP-OES等痕量分析以及同位素分析等高端实验。地质、电子化学品、半导体分析测试…

挑战杯 基于深度学习的动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

安防摄像头(IPC)的步进马达及IR-CUT驱动国产芯片——D6212

应用领域 安防摄像头&#xff08;IPC&#xff09;的步进马达及IR-CUT驱动。 02 功能介绍 D6212内置8路带有续流二极管的达林顿驱动管阵列和一个H桥驱动&#xff0c;单芯片即可实现2个步进电机和一个IR-CUT的直接驱动&#xff0c;使得电路应用非常简单。单个达林顿管在输入电…

android开发教程百度网盘,高并发系统基础篇

展望未来 操作系统 移动操作系统的演变过程&#xff0c;从按键交互的塞班功能机到触摸屏交互的Android/IOS智能机&#xff0c;从小屏幕手机到全面屏、刘海屏、水滴屏。任何系统无非干两件事&#xff1a;输入和输出&#xff0c;接收到外部输入信号后经过操作系统处理后输出信息…

如何减少AI中的偏见问题:八种方法避免AI偏见渗入模型

克服与避免 AI 偏见的八大方法 AI 中的算法偏见是一个普遍存在的问题&#xff0c;它虽然不可能完全消除&#xff0c;但却可以通过科学的方法积极地防止这种偏见。我们将在本文中围绕如何应对AI中的偏见问题展开深入的讨论。 您可能会回想起新闻中报道的一些存在偏见的算法示例…

深入理解RAM与ROM:计算机存储技术的核心

深入理解RAM与ROM&#xff1a;计算机存储技术的核心 在计算机科学领域&#xff0c;理解各种存储技术对于开发和维护高效、可靠的系统至关重要。本文旨在深入探讨两种基础且关键的存储形式&#xff1a;随机存取存储器&#xff08;RAM&#xff09;和只读存储器&#xff08;ROM&am…

每日一练:LeeCode-面试题 02.07. 链表相交 【链表+迭代】 LeeCode-142. 环形链表 II【链表+集合】

每日一练&#xff1a;LeeCode-面试题 02.07. 链表相交 【链表迭代】 LeeCode-142. 环形链表 II【链表集合】 LeeCode-面试题 02.07. 链表相交思路 LeeCode-142. 环形链表 II思路set集合方法 本文是力扣 每日一练&#xff1a;每日一练&#xff1a;LeeCode-面试题 02.07. 链表相交…

全自动气象站的工作原理

TH-CQX5全自动气象站是一款野外高精度监测气象数据的环境气象站设备。它结合了先进的传感器技术、自动化控制系统和远程通信技术&#xff0c;能够提供准确、实时的气象数据&#xff0c;为环境保护、气象研究、农业生产等领域提供重要的数据支持。 高精度传感器&#xff1a;全自…

PSINS工具箱笔记——函数定义

绘图函数&#xff1a; 时间进度条&#xff1a; timebar&#xff08;用起来简单&#xff09; 姿态转换&#xff1a; 欧拉角、姿态矩阵、等效旋转矩阵、姿态四元数、运载火箭使用的欧拉角之间的转换。 轨迹生成&#xff1a; seg trjsegment(seg, segtype, lasting, w, a, var…

BUUCTF-Misc-百里挑一

题目链接&#xff1a;BUUCTF在线评测 (buuoj.cn) 下载附件打开是一个流量包文件&#xff1a; 全是在传图片时候的流量&#xff0c;先把图片保存出来文件–>导出对象–>HTTP–>保存到一个文件夹 然后使用kali下的exiftool找到了一半flag exiftool *|grep flag 另外一半…

【C语言】编程题专项练习+答案

目录 1.删除有序数组中重复的数 2.用除二取余的方法&#xff0c;把任意一个十进制正数的二进制序列输出&#xff08;不考虑溢出&#xff09; 2.1如果是把任意一个十进制整数的二进制序列输出呢&#xff1f; 3.输出一个六行六列的整形矩阵&#xff0c;并输出其转置矩阵。矩阵…

保修期内经营者收取维修费用应遵循正当程序原则

↑↑↑“上海高院”头条号为您讲述精彩的法律科普内容 上海市第一中级人民法院在履行司法审判职能的同时&#xff0c;始终高度重视高质量案件工作&#xff0c;总结司法审判经验&#xff0c;努力提高司法审判质量。 在2020年全国法院系统优秀案例分析评选活动中&#xff0c;上海…

伟大音乐家的伟大不朽作品,贝多芬一生的音乐作品全集

一、音乐描述 贝多芬一生创作题材广泛&#xff0c;重要作品包括9部交响曲、1部歌剧、32首钢琴奏鸣曲、5首钢琴协奏曲、多首管弦乐序曲及小提琴、大提琴奏鸣曲等。因为其对古典音乐的重大贡献&#xff0c;以及对奏鸣曲式和交响曲套曲结构的发展和创新&#xff0c;而被后世尊称为…

【Flutter 面试题】main()和runApp()函数在Flutter的作用分别是什么?有什么关系吗?

【Flutter 面试题】main()和runApp()函数在Flutter的作用分别是什么&#xff1f;有什么关系吗&#xff1f; 文章目录 写在前面解答补充说明 写在前面 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff0c;GitChat专栏作者&#xff0c;阿里云社区专家博主&…

STM32CubeMX学习笔记11 ---RTC实时时钟

1、RTC实时时钟简介 STM32的实时时钟RTC是一个独立的定时器&#xff0c;RTC模块拥有一组连续计数的计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能&#xff0c;修改计数器的值可以重新设置系统当前的时间和日期 RTC模块和时钟配置系统&#xff08;RCC_B…