变化侦测篇
Object的变化侦测(20200812)
- 利用Object.definePrototype();中的get和set属性可以知道什么时候读取了数据,什么时候设置了数据
- 在Observer中,get属性中收集依赖,在set属性中通知依赖
- Dep就是一个依赖管理器,负责收集依赖,以及向依赖发送通知
- Dep收集以及通知的都是Watcher,由Watcher再去向外界通知。
Array的变化侦测(20200813)
- array还在data()方法返回的对象中,所以还是在getter中收集依赖。
- 重新拦截了改变数组的7个方法:push,pop,shift,unshift,splice,sort,reverse,在拦截中给依赖发送通知;所以只有对数组使用这7个方法,才会发送通知,使视图更新。(不讨论vue.set和vue.delete后续再讨论)
- 对数组进行深度侦测的方法是将数组中的每个值都变成Observer。
- 对push,unshift.splice三个方法特殊处理了添加值得操作,将添加得值也进行放到依赖收集中。
虚拟dom篇
vue中的虚拟dom(20200814)
- 虚拟dom,用一个js对象来描述一个真实的dom节点。
- 操作真实dom太耗费性能,所以使用虚拟dom,通过消耗js计算性能来减少操作真实dom。当数据发生变化时,对比前后虚拟节点的变化,通过DOM-Diff算法计算出需要更新的dom节点,再去更新需要更新的视图。
- vue中有一个VNode类来实例化出不同类型的虚拟dom节点。总共有6类虚拟节点,注释节点、文本节点、元素节点、组件节点、函数式组件节点、克隆节点。
vue中的DOM-Diff(20200814)
在vue中,DOM-Diff的过程叫作patch过程。以新的VNode为基准,去更新旧的VNode与新的VNode一致,主要要做三件事(patch.js文件中):
- 创建节点:新VNode中存在而旧VNode不存在,就在旧的VNode中创建。上面提到有6种虚拟节点,实际插入到真实dom中的只有3种,元素节点、注释节点和文本节点。会判断插入的这个节点的类型,从而调用不同的方法插入到真实dom中。
- 删除节点:新的VNode中不存在,而旧VNode中存在,就在旧的VNode中删除。删除节点比较简单,拿到需要被删除的节点的父节点,调用removeChild方法即可。
- 更新节点:新、旧VNode都存在时,以新的VNode为基准,更新旧的VNode。(patchVnode())
- 如果新、旧VNode都是静态节点,那么不需要做处理。
- 如果新VNode是文本节点,那么如果旧VNode也是文本节点,比较两者的文本内容是否相同,不同就更新文本内容就行,如果旧VNode不是文本节点,那么直接调用setTextNode,设置为文本节点,并且内容与新VNode一致。
- 如果新VNode是元素节点,分为2种情况:
- 该新节点包含子节点,此时要看旧节点是否包含子节点。如果包含子节点,需要递归对比更新子节点(updateChildren());如果不包含子节点,这个旧节点可能是文本节点或者是空节点,如果是文本节点那么清空文本节点,并将新节点的子节点创建一份插入到旧节点中,如果是空节点,就直接创建一份新节点的子节点插入到旧节点中。
- 该新节点不包含子节点,同时又不是文本节点,那么说明它就是一个空节点,只需将旧VNode清空即可。
更新子节点(20200815)
- 更新子节点会出现4种情况,oldCh:旧子节点组,newCh:新子节点组,
- 更新子节点:如果新旧子节点相同,并且位置也相同,只要更新oldCh里的该旧节点,使之与新节点一致。
- 移动子节点:如果newCh里的某个子节点在oldChd中找到了,但是位置不同,那么先更新oldCh中的旧节点,使之与新节点一致,并将oldCh中的旧节点移动到newCh中的新节点一致的位置。
- 创建子节点:如果在newCh中的节点在oldCh中没有找到,那么就在oldCh中创建一个与newCh中一致的节点。
- 删除子节点:如果在newCh中都已经循环完了,oldCh中还有未处理的子节点,那么直接将这些子节点都删除。
- 优化子节点更新,在循环oldCh和newCh的时候,先将4种特殊情况先处理,再进行循环。
- 新前(newStartVnode,newCh数组里所有未处理子节点的第一个子节点)和旧前(oldStartVnode,oldCh数组里的所有未处理子节点的第一个子节点)相同,进行更新子节点操作(patchVnode())。
- 新后(newEndVnode,newCh数组里所有未处理子节点的最后一个子节点)和旧后(oldEndVnode,oldCh数组里的所有未处理子节点的最后一个子节点)相同,进行更新子节点操作。
- 新后和旧前相同,进行移动子节点操作。(先patchVnode(),后移动)
- 新前和旧后相同,进行移动子节点操作。(先patchVnode(),后移动)
- 将上述4种特殊情况处理之后,如果未在oldCh中找到newCh中的子节点的话,会在剩余的子节点中循环查找。若在oldCh中找不到当前循环的newCh里的子节点,就新增节点(在oldCh中查找newCh中的子VNode的key值在oldCh中是否存在,存在的话拿到在oldCh中的下标);若在oldCh里找到了当前循环的newCh里的子节点,对比新旧子节点,如果相同,更新子节点并移动位置,如果不同,创建子节点(查找是根据VNode的key值进行查找)
- 循环的条件是oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx。当跳出循环时,若oldStartIdx > oldEndIdx,说明旧节点循环完了,还有新节点未在oldCh中找到,进行创建子节点操作;若newStartIdx > newEndIdx,说明新节点循环完了,在oldCh中删除未用到的旧子节点。
模板编译篇
- 我们将写在template标签中的类似原生HTML的内容称之为模板。
- Vue会把模板编译,生成渲染函数render函数。具体流程分为三个阶段:
- 模板解析阶段--解析器:parse
- 模板优化阶段--优化器:optimize
- 代码生成阶段--代码生成器:generate
模板解析阶段
模板解析阶段的主要工作是将中的模板使用正则等方式解析成AST抽象语法树。在源码中对应解析器parser模块。
主要有3种解析器,HTML解析器、文本解析器、过滤器解析器。
主要是通过HTML解析器解析整个模板,在解析过程种遇到文本的时候再使用文本解析器,在文本中如果包含过滤器就使用过滤器解析器。
-
HTML解析器(parseHTML)
-
文本解析器(parseText)做了三件事
// 当解析到标签的文本时,触发chars chars (text) { if(res = parseText(text)){ let element = { type: 2, expression: res.expression, tokens: res.tokens, text } } else { let element = { type: 3, text } } } let text = "我叫{{name}},我今年{{age}}岁了"; let res = parseText(text); // 通过文本解析器之后 res = { expression:"我叫"+_s(name)+",我今年"+_s(age)+"岁了", tokens:[ "我叫", {'@binding': name }, ",我今年" {'@binding': age }, "岁了" ] }
- 判断传入的文本是否包含变量
- 构造expression
- 构造tokens
-
过滤器解析器(parseFilters)在使用文本解析器解析文本时,遇到{{}}就使用过滤器解析器。
模板优化阶段
patch过程中,会跳过静态节点,即VNode中有一个isStatic属性,它表示这个节点是否是静态节点,VNode是通过render函数得到的,模板搬移最终的目的是生成一个render函数。这个isStatic属性就是在模板优化节点打标的。模板优化主要做了这2件事:
- 在通过解析器生成的AST中,找出所有静态节点并打上标记。
- 在AST中,找到所有静态根节点并打上标记
代码生成阶段
-
vue实例在挂载的时候会调用自身的render函数来生成实例上的template选项所对应的VNode。
-
render函数可能是开发者手写的,也可能是vue自己生成的。
-
代码生成其实就是根据template解析出的AST抽象语法树通过generate函数生成render函数字符串,再通过调用createFunction生成render函数,调用render函数就可以得到模板所对应的虚拟DOM。
-
generate函数内部会根据AST抽象语法树中不同的元素节点属性,调用不同的方法,生成render函数字符串。虽然元素节点属性的种类很多,但是实际真正创建出来的VNode就三种:
-
元素节点
const data = el.plain ? undefined : genData(el, state) const children = el.inlineTemplate ? null : genChildren(el, state, true) code = `_c('${el.tag}'${ data ? `,${data}` : '' // data }${ children ? `,${children}` : '' // children })`
生成元素节点render函数就是生成一个_c()函数调用的字符串,包含3个参数,分别是标签名tag、节点属性data、节点的子节点列表children。
-
文本节点
export function genText (text: ASTText | ASTExpression): string { return `_v(${text.type === 2 ? text.expression // no need for () because already wrapped in _s() : transformSpecialNewlines(JSON.stringify(text.text)) })` }
文本型的VNode可以通过_v(text)函数来创建。text参数可以可能是静态文本ASTText,也可能是动态文本ASTExpression。
-
注释节点
export function genComment (comment: ASTText): string { return `_e(${JSON.stringify(comment.text)})` }
注释型的VNode通过_e(text)函数来创建。
-
生命周期篇
Vue实例的生命周期大致分为4个阶段:
- 初始化阶段:
- 模板编译阶段:
- 挂载阶段:
- 销毁节点:
初始化阶段--new Vue
初始化阶段做的第一件事就是new Vue创建一个Vue的实例.
// src/core/instance/index.js
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)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
new Vue就做了一件事调用原型上的_init(options)方法把用户所写的options选项传入.
这个_init(options)方法是从initMixin(Vue)内来的.
// src/core/instance/init.js
export function initMixin (Vue) {
Vue.prototype._init = function (options) {
// 将vue实例赋值给变量vm
const vm = this
// 将用户传递的options选项和构造函数的options属性及其父级构造函数的option属性整合
vm.$options = mergeOptions(
// 其实是vm.constructor.options,相当于Vue.options
// 在initGlobalAPI方法内部定义了Vue.options
// 给Vue.options设置了components、directives、filters这三个属性的值为空对象.
// 并把3个内部组件设置到components属性上(keep-alive、transition、transition-group)
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
vm._self = vm
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
callHook(vm, 'beforeCreate') // 执行beforeCreate钩子函数
initInjections(vm) // resolve injections before data/props 初始化injections
initState(vm) // 初始化状态 props data methods computed watch
initProvide(vm) // resolve provide after data/props // 初始化Provide
callHook(vm, 'created') // 执行created钩子函数
/**
*用户传入了el,那么就会调用$mount进入模板编译与挂载阶段,如果没有,则需要手动执行vm.$mount进入下一个生命 * 周期阶段
*/
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
在initMixin内部,给Vue原型绑定了_init方法.
实例方法篇
实例方法是全部挂载到Vue.prototype上的.
数据相关的方法
数据相关的三个实例方法,vm.delete、vm.$watch,它们是在stateMixin函数中挂载到Vue原型上的
export function stateMixin (Vue: Class<Component>) {
// 此处省略不相关的代码
Vue.prototype.$set = set
Vue.prototype.$delete = del
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()
}
}
}
-
vm.$set
export function set (target: Array<any> | Object, key: any, val: any): any { if (process.env.NODE_ENV !== 'production' && // 如果target是null、undefined或原始类型 (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) } // 目标对象是数组,并且key是有效的下标值 if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) return val } // 如果这个key在这个对象中且不在Object原型上时 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // target有__ob__属性,则说明target是个Observer实例 // 如果目标是Vue实例或者是Vue实例的根数据对象(只有root节点vm.Count才大于0) 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 } // 如果target是响应式的,那么将新的属性添加,并设置成响应式的 defineReactive(ob.value, key, val) // 给依赖发通知 ob.dep.notify() return val }
- set函数传入三个参数,依次分别是target、key、val.
- 判断目标数据是否是null,undefined或者原始数据类型,如果是,并且不是在生产环境就发成警告
- 判断目标数据是否是数组,并且key符合数组下标要求,如果返回true的话,就利用数组的splice方法去更新数组的数据(数组splice方法自动发送依赖通知),最后返回val
- 判断key是否已经在目标数据中,并且key不在Object.prototype中,如果是,直接更新目标数据的key值就可以了(不需要设置响应式、发依赖通知操作),最后返回val
- 判断是否是Vue的实例或者Vue根数据对象(ob属性的vmCount初始值为0,在Observer类中的observe方法中,只有根数据对象,vmCount++),如果是的话直接返回val值.
- 根据目标数据的__ob__属性来判断是否是响应式对象,如果不是,直接给目标数据key设置val值,并且返回val
- 如果是响应式对象,那么将新的属性添加,并设置响应式,给依赖发通知,最后返回val.
-
vm.$delete
export function del (target, key) { if (process.env.NODE_ENV !== 'production' && // 目标对象是undefined null 或者原始数据类型 (isUndef(target) || isPrimitive(target)) ) { warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) } if (Array.isArray(target) && isValidArrayIndex(key)) { // 如果是数组.执行splice触发更新 target.splice(key, 1) return } const ob = (target: any).__ob__ if (target._isVue || (ob && ob.vmCount)) { // 如果是vue实例或者vue根数据对象,不执行删除操作 process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ) return } if (!hasOwn(target, key)) { // 对象没有这个属性值 return } delete target[key] // 删除这个属性值 if (!ob) { // 如果不是响应式的数据那就直接返回,否则需要给依赖发通知 return } ob.dep.notify() }
- del函数的原理与set函数原理类型,依次传入2个参数,target和key.
- 判断目标数据target是否是undefined、null或者原始数据类型,如果是的话,发出warn警告.
- 判断目标数据target是否是数组,并且key是否符合数组下标要求,如果是的话,直接使用数组的splice方法删除.
- 判断目标数据target是否是Vue实例或者Vue根数据对象,是的话发出警告warn并直接return.
- 判断目标对象target是否有这个属性key,如果没有的话直接return
- 删除目标对象target中的key属性
- 如果目标对象target不是响应式数据就直接return,如果是响应式数据,就发送依赖通知.
-
vm.$watch
Vue.prototype.$watch = function (expOrFn,cb,options) { const vm: Component = this if (isPlainObject(cb)) { // cb是否是个对象,如果是对象,说明第二和第三个参数合起来放在这个对象中 return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true // 用来区别vue创建的watch实例 const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) { // 先调用一次 cb.call(vm, watcher.value) } return function unwatchFn () { // 取消观察函数 watcher.teardown() // 遍历自己的依赖管理器,并从中一一删除 } }
-
vm.$watch方法传入三个参数,分别是expOrFn、cb、options
-
判断cb是否是个对象,如果是对象,说明第二个参数和第三个参数一起合并在了这个对象中,执行createWatcher函数,并返回这个函数的结果(createWatcher内部把参数重新分配到$watch上)
vm.$watch( 'a.b.c', function (val, oldVal) { /* ... */ }, { deep: true } ) // 写成这样的话,Vue内部会把第二个参数中值拆成到$watch的第二、第三个参数中 vm.$watch( 'a.b.c', { handler: function (val, oldVal) { /* ... */ }, deep: true } ) // createWatcher内部把参数重新分配到$watch上 function createWatcher (vm,expOrFn,handler,options) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
-
设置options.user=true,来和Vue创建的watch实例区分开
-
创建一个Watcher实例,如果options.immediate为true,先执行一遍cb
-
最后返回取消观察函数,函数内部遍历自己的依赖管理器,并一一删除.
-
事件相关的方法
与事件相关的实例方法有4个,vm.once、vm.emit.
在eventsMixin函数中,将这4个方法挂载到了Vue的原型上.
export function eventsMixin (Vue) {
Vue.prototype.$on = function (event, fn) {}
Vue.prototype.$once = function (event, fn) {}
Vue.prototype.$off = function (event, fn) {}
Vue.prototype.$emit = function (event) {}
}
-
$on--侦听当前实例上的自定义事件
emit是发布订阅模式.首先定义一个事件中心,通过emit触发事件中心里面存储的订阅事件.
Vue.prototype.$on = function (event, fn) { const vm: Component = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) } return vm }
- $on接收2个参数,分别是event和fn.event可以是数组或者字符串,fn是个回调函数.
- 首先判断event是否是数组,如果是数组,再重新调用$on,将数组中的事件依次放入到事件中心_events数组中.(这个属性定义在initEvents函数中)
- 如果event不是一个数组就将它放入到Vue实例的_events事件中心中,最后返回vm.
-
$emit--触发当前实例上的事件
Vue.prototype.$emit = function (event: string): Component { const vm: Component = this let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) for (let i = 0, l = cbs.length; i < l; i++) { try { cbs[i].apply(vm, args) } catch (e) { handleError(e, vm, `event handler for "${event}"`) } } } return vm } }
- $emit的第一个参数是要触发的事件名event,之后的参数都会传给被触发事件的调用函数.
- 从事件中心_events中拿到该event事件名所对应的变量函数cbs,
- 获取附加参数args
- 循环cbs并执行每个变量函数,同时将args传入.
-
$off--移除自定义事件监听器
Vue.prototype.$off = function (event, fn) { 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++) { this.$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 } if (fn) { // 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 }
- 如果没有传入任何参数,就移除所有事件
- 如果传入的第一个参数是个数组,那么循环这个数组,再依次调用$off
- 如果传入的event不在_event中,则直接返回vm.
- 如果未传入了第二个参数fn,则将event在_events事件中心中置空
- 如果传入了第二个参数fn,并且cbs中的某一项与fn相同或者某一项的fn属性与fn相同,则从cbs中移除这个fn(变量函数列表cbs是个数组),即只移除event事件下的这个fn回调的监听器.
-
$once--监听一个自定义事件,但是只触发依次.一旦触发之后,监听器就被移除.
Vue.prototype.$once = function (event, fn) { const vm: Component = this function on () { vm.$off(event, on) fn.apply(vm, arguments) } on.fn = fn vm.$on(event, on) return vm }
- $once接收2个参数,第一个是事件名称event,第二个是回调函数fn
- 在off的时候能移除这个事件
- 调用$on将event添加到_events事件中心
- on内部函数里先是调用$off移除event监听器,然后执行fn.
生命周期相关的方法
生命周期相关的方法有4个,分别是destory、mount.
export function lifecycleMixin (Vue) {
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function (fn) {}
}
export function renderMixin (Vue) {
Vue.prototype.$nextTick = function (fn) {}
}
destory在lifecycleMixin函数中挂载到Vue.prototype上.
$nextTick在renderMixin函数中挂载到Vue.prototype上.
-
$forceUpdate
Vue.prototype.$forceUpdate = function () { const vm: Component = this if (vm._watcher) { vm._watcher.update() } }
当前实例的_watcher属性就是实例的watcher,强制执行更新,就是去手动执行一下实例watcher的update方法即可.
-
$destory
完全销毁一个实例,清理它与其他实例的连接,解绑它的全部指令和事件监听器.
-
$nextTick
const callbacks = [] // 回调队列 let pending = false // 异步锁 let timerFunc // 支持Promise就第一选择用Promise if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 解决适配性,为了让微任务强制执行,通过添加一个空计时器来强制刷新微任务队列 if (isIOS) setTimeout(noop) } isUsingMicroTask = true } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // 支持MutationObserver,微任务 // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) let counter = 1 const observer = new MutationObserver(flushCallbacks) const textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true }) timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } isUsingMicroTask = true } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // 宏任务setImmediate timerFunc = () => { setImmediate(flushCallbacks) } } else { // Fallback to setTimeout. timerFunc = () => { setTimeout(flushCallbacks, 0) } } export function nextTick (cb?: Function, ctx?: Object) { let _resolve // 将任务放到回调队列中 callbacks.push(() => { if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { // 异步锁 pending = true timerFunc() } // $flow-disable-line if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
微任务比宏任务优先执行.
$nextTick中依次优先使用Promise(微任务)、MutationObserver(微任务)、setImmediate(宏任务)、setTimeout(宏任务).
-
$mount
全局API篇
与实例方法挂载到Vue的原型上不同,全局API是挂载在Vue上的,一共有12个.
-
Vue.extend--创建Vue的一个子类
一边通过传入的属性方法,将子类的添加上独有的属性方法,一边复制了Vue父类的属性方法复制到子类.
-
Vue.nextTick--原理同$nextTick
-
Vue.set--原理同$set
-
Vue.delete--原理同$delete
-
Vue.directive--注册指令
-
Vue.filter--过滤器
-
Vue.component--全局注册组件
-
Vue.use--使用插件
-
Vue.mixin--全局的混入
-
Vue.compile--编译模板字符串
-
Vue.observable--让一个对象可响应
-
Vue.version
该API在日常业务开发中用不到,但在插件开发时,非常有用,可以根据不同的版本做不同的事.
它是在构建的时候读取了package.json中的version.
内置组件篇
keep-alive
是Vue的一个内置组件,本身不会渲染一个DOM元素,也不会出现在父组件链上,当它封装动态组件时,会缓存不活动的组件实例,而不是销毁他们.有3个属性:
- include:字符串或者正则表达式.只有名称匹配的组件会被缓存.
- exclude:字符串或者正则表达式.任何名称匹配的组件都不会被缓存.
- max:最大缓存组件实例的数量.
keep-alive组件的主要原理在于它的render.它使用的是LRU的缓存策略
- render内部拿到默认插槽第一个节点vnode并且拿到它的options
- 判断vnode是否在include和exclude范围内
- 如果不在缓存范围内,则直接返回vnode;
- 如果在缓存范围内,那么拿到vnode的key,并且去缓存中查找这个key相对应的componentInstance
- 如果查找到就直接使用,并且更新组件在缓存队列中的位置
- 如果查找不到就将组件塞到缓存队列尾部.
- 最后设置
vnode.data.keepAlive = true
,并将vnode返回.
组件一旦被keep-alive标签缓存,那么再次渲染的时候就不会执行created,mounted等钩子函数,vue提供了activated和deactivated2个钩子函数来使组件在被激活和被终止时调用.