vue 源码详解(一):原型对象和全局 API的设计

1. 从 new Vue() 开始

我们在实际的项目中使用 Vue 的时候 , 一般都是在 main.js 中通过 new Vue({el : '#app , ...options}) 生成根组件进行使用的, 相关的配置都通过 options 传入。 Vue 的原型对象会帮我们初始化好很多属性和方法, 我们可以通过 this.property 直接调用即可; 而 Vue 这个类也通过类的静态方法初始化了一些全局的 api, 我们可以通过类名直接调用, 比如 Vue.component()Vue 的原型对象和全局 API 是通过混入的方式融入 Vue 中的。

如下面代码所示, import Vue from './instance/index' 引入 Vue 的构造函数,在用户调用之前, Vue 先做了一些初始化工作, 具体做了哪些工作看 vue/src/core/instance/index.js(点击跳转)中的代码(下边第二段):

  1. function Vue (options) { ... } 定义了 Vue 构造函数, 我们调用 new Vue 时,只会执行一句代码, 即 this._init(options);
  2. 定义完构造函数,依次调用 initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) , 从而将 Vue 的初始化函数、状态初始化函数、事件初始化函数、生命周期初始化函数、渲染函数混入到 Vue 的原型对象。这才使得每个组件都有了便捷的功能。初始化函数具体都做了什么工作, 且看后续的分析。

vue/src/core/index.js :

import Vue from './instance/index' // 1. 引入 Vue 构造函数
import { initGlobalAPI } from './global-api/index' // 2. 引入初始化全局 API 的依赖
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component' initGlobalAPI(Vue) // 3. 初始化全局 API Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
}) Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode && this.$vnode.ssrContext
}
}) // expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
value: FunctionalRenderContext
}) Vue.version = '__VERSION__' export default Vue

vue/src/core/instance/index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index' function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options) // 1. Vue 实例初始化
} initMixin(Vue) // 2
stateMixin(Vue) // 3
eventsMixin(Vue) // 4
lifecycleMixin(Vue) // 5
renderMixin(Vue) // 6 export default Vue

注释 1 处, new Vue() 时, 只执行了一个初始化工作 this._init(options) ; 值得注意的是, 在定义完成构造函数后,此时尚未有 new Vue 的调用, 即在实例创建之前, 会执行注释 2 3 4 5 6 处的初始化工作, 让后初始化全局 API ,至此准备工作已经就绪, 通过调用 new Vue 生成 Vue 实例时,会调用 this._init(options) 。接下来,探索一下 Vue 生成实例前, 依次做了哪些工作。

1.1 initMixin (vue\src\core\instance\init.js)

let uid = 0

export function initMixin (Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this // 1. vm 即 this, 即 Vue 的实例对象
// a uid
vm._uid = uid++ // 每个 Vue 实例对象都可以看成一个组件, 每个组件有一个 _uid 属性来标记唯一性 let startTag, endTag
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag)
} // a flag to avoid this being observed
vm._isVue = true
// merge options
// 合并参数, options 是我们调用 `new Vue({ el : 'app'chuand, ...args})` 时传入的参数
// 合并完成后将合并结果挂载到当前 `Vue` 实例
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions( // 合并完成后将合并结果挂载到当前 `Vue` 实例
// 这个函数会检查当前 Vue 实例的否早函数和其父类、祖先类上的 options 选项, 并能监听是否发生了变化, 将 祖先类、父类和当前 Vue 实例的 options 合并到一起
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm) // 1. 初始化声明周期
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created') /* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
} if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}

1.1.1 initLifecycle

上边代码给每个实例标记一个唯一的 _uid 属性, 然后标记是否为 Vue 实例, 将用户传入的参数和 Vue 自有参数合并后,挂载到 Vue$options 属性 。

export function initLifecycle (vm: Component) {
const options = vm.$options // locate first non-abstract parent
// 这个注释已经很明了了, 就是查找当前 vue 实例的第一个非抽象父组件
// 找到后会将当前的组件合并到父组件的 `$children` 数组里
// 从而建立了组件的父子关系
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
} vm.$parent = parent
vm.$root = parent ? parent.$root : vm vm.$children = []
vm.$refs = {} vm._watcher = null
vm._inactive = null // 这俩先忽略, 俺也不知道干嘛的
vm._directInactive = false // 这俩先忽略, 俺也不知道干嘛的
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}

如上, 初始化声明周期的时候, 会简历当前组件与其他组件的父子关系, 如果找到父组件, 会将 $root 指针指向父组件,找不到的话, 指向当前 Vue 实例。接下来 vm.$children = [] 初始化子组件列表, vm.$refs = {} 初始化引用列表, vm._watcher = null 初始化观察者列表, 此时还没有观察者,无法检测数据变化, vm._isMounted = false 标记当前组件尚未挂载到 DOM, vm._isDestroyed = false 标记当前组件并不是一个被销毁的实例,这与垃圾回收有关系的, vm._isBeingDestroyed = false 标记当前组件是否正在销毁工作。

至此, 声明周期的初始化已经完成了。

1.1.2 initEvents

vue/src/core/instance/events.js :

1.2 stateMixin : 状态初始化

vue/src/core/instance/state.js :

export function stateMixin (Vue: Class<Component>) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
const dataDef = {}
dataDef.get = function () { return this._data }
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
)
}
propsDef.set = function () {
warn(`$props is readonly.`, this)
}
}
Object.defineProperty(Vue.prototype, '$data', dataDef) // 1
Object.defineProperty(Vue.prototype, '$props', propsDef) // 2 Vue.prototype.$set = set // 3
Vue.prototype.$delete = del // 4 Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
}

上边代码对实例的状态做了初始化。 在注释 1 2 两个地方分别给 Vue 原型对象增加了 $data $props 两个属性, 这两个属性的值分别是当前 vm_data _props 属性, 并且设置这两个属性是不可以修改的。

注释 3 4 处为 vm 添加了 setdelete 方法, setdelete 是干嘛的就不用介绍了吧, Vue 对象本身也有 Vue.setVue.delete 这两个方法, 都是来源于下边 set 这个函数, 他的作用体现在下边代码注释的 1 2 处:

参数 target 为对象或者数组, target 有一个 __ob__ 属性, 这个属性的来源是在 Observer 这个类中的构造函数,其中有一句是 def(value, '__ob__', this) , value 是待观测的对象, 也就是我们写代码时传入的 data中的属性, 然后我们传入的 data 其实都被代理到 __ob__ 这个属性上了,以后我们操作 data 中的数据或者访问 data 中的数据都会被代理到 __ob__ 这个属性。

之后又在原型对象挂载了 $watcher 方法, 该方法的返回值是一个销毁 watcher 的方法。 至于 watcher 是个啥, 以及 watcher 的作用,后边再谈。

/**
* Set a property on an object. Adds the new property and
* triggers change notification if the property doesn't
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val) // 1
ob.dep.notify() // 2
return val
}

vue\src\core\util\lang.js :

/**
* Define a property.
*/
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}

1.3 事件初始化

其实就是在 Vue 原型对象上挂载了一些方法 ($on $once $off $emit) , 基于发布订阅模式,实现了一个事件响应系统, 与 nodejs 中的 eventEmitter 是极其相似的。这就是我们常用的事件总线机制的来源。

简单解析一下下面的代码 :

$on 是事件的订阅, 通过他的参数 (event: string | Array<string>, fn: Function) 可知, 可以一次订阅多个事件,他们共享一个处理函数, 然后将所有的处理函数以键值对的形式({eventName : handler[]})存储在 vm._events 对象中,等待事件发布。一旦事件发布, 就会根据事件类型( eventName )去事件处理函数列表(handler[])中,读取处理函数并执行。

$emit 是事件的发布, 生产环境中对事件名称(也就是类型),进行了大小写转换, 不用区分事件名称的大小写了, 当然我们编码不能这样粗狂的去写哈。 然后 cbs 是根据事件名称读取的处理函数的列表, const args = toArray(arguments, 1) 是处理事件的参数, 函数 toArray$emit 函数的参数除掉第一个以后, 最终传入了我们的订阅函数中。 即

vm.$emit('render', 'a',124) 代码最终调用结果是 vm._events['render'] 列表中所有的函数都以 ('a', 123) 为参数运行一次。

$off 是将事件的订阅函数从订阅列表中删除, 它提供了两个参数 (event?: string | Array<string>, fn?: Function), 两个参数都是可选的, 并且不能只穿第二参数。 如果实参列表为空, 则当前 vm 上订阅的所有事件和事件的处理函数都将被删除;如果第二参数为空, 则当前 vmvm._events[event] 中所有的处理函数将被清空; 如果第二个参数 fn 不为空, 则只将 vm._events[event] 事件处理列表中的 fn 函数删除。

$once 表示事件处理只执行一次, 多次发布事件,也只会执行一次处理函数。这个函数有点小技巧。先建立一个 on 函数, 然后把事件处理函数 fn 挂载到这个函数对象上, 函数也是对象,可以有自己的属性,这个没有疑问吧。 on 函数中只有两句代码 vm.$off(event, on), 让 vm 解除 on 函数的订阅, 这就可以保证以后不会再执行 on 函数了; 下一句fn.apply(vm, arguments) 调用 fn , 这保证了 fn 被执行了一次。 哈哈哈, 666.

事件的初始化就这样讲完了。

export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
} Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
} Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
} Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
}

1.4 lifecycleMixin 生命周期初始化

代码如下, 在 Vue 的原型对象上增加了三个方法 _update $forceUpdate $destroy, 依次来看下都做了什么事吧。

vm._update 通过 __patch__ 函数把虚拟节点 vnode 编译成真实 DOM. 并且, 组件的更新也是在这里完成虚拟节点到真实 DOM 的转换。并且父组件更新后, 子组件也会更新。

vm.$forceUpdate 若果当前组件上有观察者, 则直接更细组件。

vm.$destroy 销毁组件, 如果当前组件正在走销毁的流程,则直接返回, 等待继续销毁。 否则, 会触发 beforeDestroy 这个声明周期, 并将当前组件标记为正在销毁的状态。 然后将当前组件从父组件中删除, 然后销毁所有的 watcher, 销毁 vm._data__ob__ , 标记组件状态为 已销毁,重新生成真实 DOM , 触发 destroyed 生命周期方法, 移除当前组件订阅的事件和事件的处理函数, 将当前组件对父组件的引用清空。

vue/src/core/instance/lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
} Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
} Vue.prototype.$destroy = function () {
const vm: Component = this
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
if (vm._watcher) {
vm._watcher.teardown()
}
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
}

1.5 renderMixin 渲染函数初始化

也是向 Vue 的原型对象挂载一些方法。

installRenderHelpers(Vue.prototype) 向 vm 增加了模板的解析编译所需要的一些方法;

$nextTick 即我们在写代码时常用的 this.$nextTick() , 它返回一个 Promise 实例 p, 我们可以在 pthen 函数中访问到更新到 DOM 元素的数据, 也可以向 this.nextTick 传递一个回调函数 ff 也可以访问更新到 DOM 元素的数据。

_render 方法生成虚拟节点。详见后边的代码。

vue/src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype) Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
} Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
} // set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
let vnode
try {
// There's no need to maintain a stack because all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// set parent
vnode.parent = _parentVnode
return vnode
}
}

2. Vue 全局 API

Vue 中全局 API 一共有一下 12 个。全局 API 是通过构造函数 Vue 直接调用的, 有一些方法在实例上也做了同步, 可以通过实例对象去调用。 比如常用的 Vue.nextTick , 可以通过 this.$nextTick 进行调用。下面就依次分析一下每个全局 API 的使用和实现思路吧。

  • Vue.extend
  • Vue.nextTick
  • Vue.set
  • Vue.delete
  • Vue.directive
  • Vue.filter
  • Vue.component
  • Vue.use
  • Vue.mixin
  • Vue.compile
  • Vue.observable
  • Vue.version

src/core/global-api/index.js

2.1 Vue.extend

vue 源码详解(一):原型对象和全局 `API`的设计的更多相关文章

  1. vue 源码详解(二): 组件生命周期初始化、事件系统初始化

    vue 源码详解(二): 组件生命周期初始化.事件系统初始化 上一篇文章 生成 Vue 实例前的准备工作 讲解了实例化前的准备工作, 接下来我们继续看, 我们调用 new Vue() 的时候, 其内部 ...

  2. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  3. saltstack源码详解一

    目录 初识源码流程 入口 1.grains.items 2.pillar.items 2/3: 是否可以用python脚本实现 总结pillar源码分析: @(python之路)[saltstack源 ...

  4. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

  5. 源码详解系列(六) ------ 全面讲解druid的使用和源码

    简介 druid是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,druid还扩展 ...

  6. 源码详解系列(七) ------ 全面讲解logback的使用和源码

    什么是logback logback 用于日志记录,可以将日志输出到控制台.文件.数据库和邮件等,相比其它所有的日志系统,logback 更快并且更小,包含了许多独特并且有用的特性. logback ...

  7. 源码详解系列(八) ------ 全面讲解HikariCP的使用和源码

    简介 HikariCP 是用于创建和管理连接,利用"池"的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制.连接可靠性测试.连接泄露控制.缓存语句等功能,另外,和 dr ...

  8. Mybatis源码详解系列(四)--你不知道的Mybatis用法和细节

    简介 这是 Mybatis 系列博客的第四篇,我本来打算详细讲解 mybatis 的配置.映射器.动态 sql 等,但Mybatis官方中文文档对这部分内容的介绍已经足够详细了,有需要的可以直接参考. ...

  9. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

随机推荐

  1. 怎么用git将自己的源代码提交到git服务器上

    在git服务器上新建仓库 在本地初始化本地仓库 初始化 git init 添加远程仓库地址 git remote add origin XXX.git 同步 git pull origin maste ...

  2. k8s结合jumpserver做kubectl权限控制 用户在多个namespaces的访问权限 rbac权限控制

    圈子太小,做人留一面,日后好相见. 其实这个文章就是用户用jumpserver登录到k8s master节点 然后执行kubectl的时候只有自己namespaces的所有权限. 背景 1,k8s 有 ...

  3. 巧用SpringBoot扩展点EnvironmentPostProcessor

    我们的项目是单体项目,使用的是springboot的框架,随着对接的外部服务越来越多,配置文件越来越臃肿..我们将对接的外部服务的代码单独抽离出来形成service依赖,之后以jar包的形式引入,这时 ...

  4. Spring缓存的注解关键词解释

    Spring缓存的注解关键词解释 @Cacheable支持缓存 @Cacheable可以标记在一个方法上,也可以标记在一个类上. 1.当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表 ...

  5. 自己动手模拟spring的IOC

    我们这里是模拟spring,主要模拟spring中的IOC功能,所以在此我们一样要在service层中定义dao的实例,当然不用new出来,我们就通过spring的IOC把这里的dao层注入进来.不要 ...

  6. 使用过redis做异步队列么,你是怎么用的?有什么缺点?

    Redis设计主要是用来做缓存的,但是由于它自身的某种特性使得它可以用来做消息队列. 它有几个阻塞式的API可以使用,正是这些阻塞式的API让其有能力做消息队列: 另外,做消息队列的其他特性例如FIF ...

  7. 职场人都该了解<荷花定律>

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员.我有什么:职场规划指导,技能提升方法,讲不完的职场故事,个人成长经验. 荷花定律 ,听起来很新奇的一个 ...

  8. ARTS第十三周(阅读Tomcat源码)

    1.Algorithm:每周至少做一个 leetcode 的算法题2.Review:阅读并点评至少一篇英文技术文章3.Tip:学习至少一个技术技巧4.Share:分享一篇有观点和思考的技术文章 考研真 ...

  9. Chirp Z-Transform

    Chirp Z-Transform 其实不是什么特别难的东西. 用于解决等比数列/类等比数列多点求值. \(b_i=\sum_{j=0}^{n}a_jc^{ij}\) 注意到 \(ij=\binom{ ...

  10. Springboot中Rest风格请求映射如何开启并使用

    问题引入 因为前端页面只能请求两种方式:GET请求和POST请求,所以就需要后台对其进行处理 解决办法:通过springmvc中提供的HiddenHttpMethodFilter过滤器来实现 而由于我 ...