没有文案,直接上正文吧
目前已经整理了三万多字,后面持续更新……
一、API概览
1.全局配置 9个
通过Vue.config.xxx进行配置
- silent -- 取消所有日志和警告
- optionMergeStrategies -- 自定义合并选项策略
- devtools -- 是否允许浏览器插件识别代码
- errorHandler -- 全局错误捕获
- warnHandler -- 全局警告捕获
- ignoredElements -- 忽略Vue之外的自定义元素
- keyCodes -- 给v-on添加键位别名,
- performance -- 允许浏览器开发工具性能追踪
- productionTip -- 阻止启动时生成生产提示 (控制台)
2.全局API 12个
通过Vue.xxx调用,一些api需要在new Vue之前调用,例如use
- Vue.extend -- Vue构造器
- Vue.nextTick -- 下次DOM更新循环之后的延迟回调
- Vue.set -- 增加响应式数据
- Vue.delete -- 删除响应式数据中的某个属性,会彻底解绑其所关联的watcher和dep等
- Vue.directive -- 注册或获取全局指令
- Vue.filter -- 注册或获取全局过滤器
- Vue.component -- 注册组件 及查找
- Vue.use -- 注入插件
- Vue.mixin -- 混入
- Vue.compile -- 模板编译
- Vue.observable -- 手动对某个对象进行观测
- Vue.version -- 版本号
3.数据选项 6个
- data -- 数据
- props -- 接收的数据
- propsData -- new 实例时传递的props,可在new的时候传递,等同于props
- computed -- 计算属性
- methods -- 方法集合
- watch -- 用户watcher , 原理也是new Watcher
4.DOM选项 4个
- el -- 根节点挂载的dom元素
- template -- 渲染模板,渲染优先级大于data,如果有则取它,没有则会取el对应的元素的innerHTML
- render -- 虚拟dom转换函数,将传入的数据转换成vnode 原理懒得写
- renderError -- render生效时调用的,可在做类似暂无数据提示的组件
5.生命周期选项 14个
8个组件钩子,2个keep-alive钩子,1个错误捕获钩子,
3个v3钩子:渲染追踪、渲染已触发、服务端渲染前
订阅:mergeOptions执行starts中的lifecycle_hooks策略,将对应的hook维护到options上
发布:通过callhook取出对应的handler,调用该handler,(在invokeWithErrorHandling中执行的,收集错误日志)
- beforeCreate -- 数据初始化前
- created -- 数据初始化完成
- beforeMount -- 开始挂载前
- mounted -- 挂载完成
- beforeUpdate -- 视图变更前
- updated -- 视图变更后
- activated -- keep-alive组件激活时
- deactivated -- keep-alive组件失活时
- beforeDestroy -- 实例销毁前
- destroyed -- 实例销毁后
- errorCaptured -- 捕获错误日志
- serverPrefetch --- 服务端渲染前
- renderTracked --- 渲染追踪
- renderTriggered --- 渲染已触发
6.资源选项 3个
- directives -- 自定义指令
- filters -- 过滤器
- components -- 组件注册及查找
7.组合选项 4个
- parent -- 认贼作父,强制和其它组件 建立父子关系
- mixins -- 其实就是调用mergeOptions把传进来的options合并到vm上
- extends -- 允许声明扩展另一个组件,原理是通过mergeOptions合并到parent中,类似mixin
- provide / inject -- 可以理解为上下文,可用于传递数据
8.其它选项 6个
- name -- 组件名称,没有实质作用,只是便于调试,更友好的警告信息以及跟语义化的组件构造函数名
- delimiters -- 自定义模板语法中的文本取值,例如{{}} 改写为[[]],原理是再createCompileToFunction时,改写正则匹配规则
- functional -- 标记是否为函数组件
- model -- v-model
- inheritAttrs -- 传递给组件的属性如果没有用props接收,会被添加到元素上面,设为false可隐藏属性
- comments -- 保留代码注释
9.实例属性 13个
- vm.$data -- 等同于data
- vm.$props -- 等同于props
- vm.$el -- 实例真实Dom根元素
- vm.$options -- 当前实例的全部选项
- vm.$parent -- 父节点
- vm.$root -- 根节点
- vm.$children -- 子节点
- vm.$slots -- 访问被具名插槽分发的内容
- vm.$scopedSlots -- 访问作用域插槽
- vm.$refs -- 用ref注册的dom对象
- vm.$isServer -- 是否运行于服务器
- vm.attrs
- vm.$listeners -- 父组件中不含.native的事件监听器
10.实例数据方法 3个
- vm.watch,里面new Watcher
- vm.$set -- 等同于Vue.set
- vm.$delete -- 等同于Vue.delete
11.实例事件方法 4个
- vm.$on -- 监听实例事件 ,eventMixin注入 > 将传入的函数添加到_events中
- vm.off移除事件
- vm.$off -- 关闭监听事件,eventMixin注入 > 移除_events中对应的函数
- vm.$emit -- 触发监听事件,eventMixin注入 > 取出_event中对应的函数并执行
12.实例生命周期方法 4个
- vm.$mount -- 挂载实例,调用mountComponent方法,创建渲染watcher
- vm.$forceUpdate -- 强制渲染一次,很简单,调用vm._watcher.update()
- vm.$nextTick -- 调用nextTick方法
- vm.$destroy -- 销毁实例,两个callHook,将很多属性改为null
13.原生指令 14个
- v-text -- 渲染指定文本
- v-html -- 渲染指定html
- v-show -- 显示隐藏
- v-if -- 是否渲染
- v-else -- 是否渲染
- v-else-if -- 是否渲染
- v-for -- 循环
- v-on -- 绑定事件$on
- v-bind -- 绑定属性
- v-model -- 双向绑定
- v-slot -- 具名插槽
- v-pre -- 跳过编译,标记静态节点可提高性能
- v-cloak -- 添加到实例属性上直到关联实例结束编译,例如{{ name }} name可能时异步取值,在防止取到值之前的错误渲染,添加了该指令后不会被渲染为‘{{ name }}’,同时结合css将其隐藏,[v-clock]:{ display:none }
- v-once -- 只渲染一次,被标记为静态节点,之后更新操作都不会被重新渲染
14.特殊属性 6个
- key -- 标记节点,vnode进行节点比对时大有用处
- ref -- 获取真实dom,将该节点的$el赋值给ref所指的变量名,并同步给vm
- is -- 用于动态组件,指向组件的key
- slot -- 废弃 被v-slot替代
- slot-scope -- 废弃 被v-slot替代
- scope -- 废弃 被v-slot替代
15.内置组件 5个
- component -- 渲染动态组件 通过is查找对应组件
- transition -- 动画组件
- transition-group 动画组组件
- keep-alive -- 缓存
- slot -- 插槽
二、全局配置原理详解
1. silent
取消所有日志和警告
原理:在warn方法和tip方法中对config.slient进行判断后,再选择性输出日志和警告
warn = (msg, vm = currentInstance) => {
const trace = vm ? generateComponentTrace(vm) : ''
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace)
} else if (hasConsole && !config.silent) { // silent为true时不打印错误信息
console.error(`[Vue warn]: ${msg}${trace}`)
}
}
tip = (msg, vm) => {
if (hasConsole && !config.silent) { // silent为true时不打印警告信息
console.warn(`[Vue tip]: ${msg}` + (vm ? generateComponentTrace(vm) : ''))
}
}
2. optionMergeStrategies
自定义选项合并策略
例如:Vue.config.optionMergeStrategies.myMethods = { //...合并策略 },然后就可在选项中添加我们写的myMethods:{ onClick(){ //... } }
意思就是可以在options中写除了data props methods watch computed 生命周期等以外的,自定义的东西,并且让其挂载到vm
原理:它是选项合并策略的初始化对象,mergeOptions会从基于它扩展的starts中取出对应的策略,再对选项进行合并.我们在该对象上可以增加自己的策略
// config.js
export default {
optionMergeStrategies: Object.create(null),
// ……
}
// /src/core/util/options.ts
const strats = config.optionMergeStrategies // 1.基于该选项合并策略
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeLifecycleHook // 2.扩展生命周期钩子函数合并策略
})
3. devtools
是否允许浏览器插件识别代码
原理:初始化devtools前先判断config.devtools是否为true,再执行devtools的钩子
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
4. errorHandler
全局错误捕获
原理:执行某些try catch或reject的时候,会触发globalHandlerError函数,该函数会尝试调用config.errorHandler
function globalHandleError(err, vm, info) {
if (config.errorHandler) {
try {
return config.errorHandler.call(null, err, vm, info) // 如果用户有配置全局错误捕获,就尝试执行用户的
} catch (e: any) {
if (e !== err) {
logError(e, null, 'config.errorHandler') // 执行vue自己的错误日志函数
}
}
}
logError(err, vm, info) // 执行vue自己的错误日志函数
}
5. warnHandler
全局警告捕获
原理:执行某些try catch或reject的时候,会调用warn函数,该函数会调用config.warnHandler
warn = (msg, vm = currentInstance) => {
const trace = vm ? generateComponentTrace(vm) : ''
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace) // 调用全局配置中的warnHandler
} else if (hasConsole && !config.silent) {
console.error(`[Vue warn]: ${msg}${trace}`)
}
}
6. ignoredElements -- 忽略Vue之外的自定义元素
忽略Vue之外的自定义元素
例如使用Vue组件的同时,如果在app上插入了web components api创建的自定义web组件,则该web组件会被识别成vue组件,但由于没有继承Vue所以会报错.
web components api可参考Vue2架构(3)扩展中的描述 或自行百度
原理:createElm渲染真实dom时,会调用isUnknownElement方法,通过config.ignoredElements判断vnode是否为未知元素,如果是未知元素就采用类似静态节点的处理方式
function isUnknownElement(vnode, inVPre) {
return (
!inVPre &&
!vnode.ns &&
!(
config.ignoredElements.length && // 比对tag和config配置中的忽略元素列表
config.ignoredElements.some(ignore => {
return isRegExp(ignore)
? ignore.test(vnode.tag)
: ignore === vnode.tag
})
) &&
config.isUnknownElement(vnode.tag)
)
}
7. keyCodes
给v-on添加键位别名
原理:events中预设了一些keyCodes,执行相关事件的时候会查找vm上有没有对应的事件回调,调用_k方法的时候会尝试从config.keyCodes中取值
events中的预设
// KeyboardEvent.keyCode aliases
const keyCodes: { [key: string]: number | Array<number> } = {
esc: 27,
tab: 9,
enter: 13,
space: 32,
up: 38,
left: 37,
right: 39,
down: 40,
delete: [8, 46]
}
checkKeyCodes --- Vue.prototype._k
/**
* Runtime helper for checking keyCodes from config.
* exposed as Vue.prototype._k
* passing in eventKeyName as last argument separately for backwards compat
*/
export function checkKeyCodes(
eventKeyCode: number,
key: string,
builtInKeyCode?: number | Array<number>,
eventKeyName?: string,
builtInKeyName?: string | Array<string>
): boolean | null | undefined {
const mappedKeyCode = config.keyCodes[key] || builtInKeyCode // 优先取config中的keyCodes
// ……
}
8. performance
允许浏览器开发工具性能追踪
原理:初始化前、初始化后、更新前、更新后、编译前、编译后,分别调用mark(startTag | endTag),mark方法核心是调用pref,pref 相关api可自行百度
初始化时
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
// ……
if (__DEV__ && config.performance && mark) { // 如果允许性能追踪
startTag = `vue-perf-start:${vm._uid}`
endTag = `vue-perf-end:${vm._uid}`
mark(startTag) // 开始追踪
}
// ……
callHook(vm, 'created') // 初始化完成钩子
if (__DEV__ && config.performance && mark) { // 如果允许性能追踪
vm._name = formatComponentName(vm, false)
mark(endTag) // 结束追踪
measure(`vue ${vm._name} init`, startTag, endTag) // 分析性能
}
// ……
} // ……
}
更新时
// mountComponent中
callHook(vm, 'beforeMount') // 开始更新钩子
let updateComponent
if (__DEV__ && config.performance && mark) { // 如果允许性能追踪
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag) // 开始追踪
const vnode = vm._render() // 更新vnode
mark(endTag) // 结束vnode更新的性能追踪
measure(`vue ${name} render`, startTag, endTag) // 分析能能
mark(startTag) // 开始渲染性能追踪
vm._update(vnode, hydrating) // 渲染完
mark(endTag) // 结束渲染性能追踪
measure(`vue ${name} patch`, startTag, endTag) // 性能分析
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
编译时
// compile时,$mount方法中
if (template) {
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
mark('compile') // 开始追踪
}
// 模板编译 把template拆字符串转为ast语法树,再拼字符串生成render函数
const { render, staticRenderFns } = compileToFunctions(
template,
{
outputSourceRange: __DEV__,
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
},
this
)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (__DEV__ && config.performance && mark) {
mark('compile end') // 结束追踪
measure(`vue ${this._name} compile`, 'compile', 'compile end') // 性能分析
}
}
9. productionTip
阻止启动时生成生产提示
// productionTip为true时才打印
if (
__DEV__ &&
process.env.NODE_ENV !== 'test' &&
config.productionTip !== false &&
typeof console !== 'undefined'
) {
console[console.info ? 'info' : 'log'](
`You are running Vue in development mode.\n` +
`Make sure to turn on production mode when deploying for production.\n` +
`See more tips at https://vuejs.org/guide/deployment.html`
)
}
三、全局API原理详解
1. Vue.extend
Vue构造器,把传入的选项转换成一个继承了Vue的构造函数,是组件实现的核心原理之一
原理:传入继承选项,返回一个继承了Vue的构造函数Sub,并将传入的选项通过mergeOptions合并到Sub.options上
Vue.extend = function (extendOptions: any): typeof Component {
extendOptions = extendOptions || {} // 初始化合并选项
const Super = this // 父级
const SuperId = Super.cid // 父级组件id
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) // 如果有_Ctor说明是组件
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId] // 同一个组件直接返回
}
const name =
getComponentName(extendOptions) || getComponentName(Super.options)
if (__DEV__ && name) {
validateComponentName(name)
}
// 创建一个子函数(子组件)
const Sub = function VueComponent(this: any, options: any) {
this._init(options) // 子函数继承Vue后也有了自己的init方法,开始初始化子组件
} as unknown as typeof Component
Sub.prototype = Object.create(Super.prototype) // 子组件继承父组件
Sub.prototype.constructor = Sub // 修复子组件的constructor,否则子组件被后代组件继承的时候,继承的是Vue
Sub.cid = cid++ // 子组件唯一标识
Sub.options = mergeOptions(Super.options, extendOptions) // 合并父组件和子组件选项
Sub['super'] = Super // super就是父组件,可通过super找到父级,类似$parent的作用
if (Sub.options.props) {
initProps(Sub) // 初始化props,这里不会再对props进行劫持
}
if (Sub.options.computed) {
initComputed(Sub) // 初始化computed
}
Sub.extend = Super.extend // 传递extend方法,使其可被后代组件调用
Sub.mixin = Super.mixin // 传递mixin,使其可被后代组件调用
Sub.use = Super.use // 传递use,同上
// 初始化生命周期策略
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type] // 继承父组件的合并策略
})
// 把选项中的name进行拼接,子组件构造函数名变成了vue-component-xxx
if (name) {
Sub.options.components[name] = Sub
}
Sub.superOptions = Super.options // 记录父组件选项
Sub.extendOptions = extendOptions // 记录自己的选项
Sub.sealedOptions = extend({}, Sub.options) // 克隆一个
cachedCtors[SuperId] = Sub // 缓存构造函数,keep-alive中会用
return Sub
}
2. Vue.nextTick
下次DOM更新循环之后的延迟回调,在dom更新后尽早的拿到更新后的结果,基于js事件机制实现;
说明:
在某一个同步任务队列中,可能存在很多次赋值操作,例如this.name=xxx this.age=xxx...
每一次赋值操作都会触发set,set触发dep.notiyf,notify遍历watcher队列执行update,update通过queueWatcher执行run,run执行watcher收集的getter,
如果是渲染watcher则更新视图,那么每修改一次数据,就会触发一次视图更新,非常浪费性能.所以需要进行批处理,将视图更新放在同步任务队列之后去执行,
这样既避免了重复渲染,也确保了同步任务队列的每一个赋值操作触发的watcher都会被收集;
渲染watcher中的nextTick:
通过queueWatcher将watcher维护成一个队列, flushing状态判断和has去重后,将批处理函数传递给nextTick,
批处理函数flushSchedulerQueue中关键操作:queue队列排序\遍历queue,执行watcher.run
原理:将传入的回调函数维护到待执行队列callbacks中,通过pending状态判断执行timerFn函数,timerFn函数以向下兼容方式,依次尝试promise>MutationObserver>setImmediate>setTimeOut(原因:想要在上一个同步任务队列执行完后,尽快的执行传入的回调函数,微任务是最佳选择,但某些环境可能不支持微任务,最终的妥协就是开启一个宏任务,V3没有兼容直接用的promise),并将flushCallbacks作为回调函数传入timerFn,在flushCallbacks函数中遍历callbacks队列并依次执行,随后修改pending状态,清空callbacks
export let isUsingMicroTask = false // 是否正在使用微任务
// 假如用户连续调用1000次nextTick,如果不做批处理的话,每次都会开启一个异步任务,这样明显不合理;
// 假如nextTick后又改了状态,如果未做批处理,那么当前nextTick拿到的是上一个任务队列的结果,并不是最新的;
// 使用批处理后,当前任务队列无论多少次nextTick都会合并成一个异步任务队列执行,并且确保nextTick是在所有同步任务执行完毕之后再执行的。
const callbacks: Array<Function> = [] // 回调函数队列,会把其放入数组中,在本次任务队列中,一次性全部执行。
let pending = false // 执行状态
// 遍历执行nextTick接收的回调函数
function flushCallbacks() {
pending = false // 重置执行状态
const copies = callbacks.slice(0) // 依次取出回调函数
callbacks.length = 0 // 重置回调函数列表
for (let i = 0; i < copies.length; i++) {
copies[i]() // 执行回调函数
}
}
let timerFunc // 最终执行的函数
if (typeof Promise !== 'undefined' && isNative(Promise)) { // 如果当前环境支持promise
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// IOS中的微任务队列可能不会被主动清空,也就是说可能不会执行上面的p.then
if (isIOS) setTimeout(noop) // 需要开启一个空的宏任务确保微任务被清空
}
isUsingMicroTask = true // 修复状态
} else if (
!isIE &&
typeof MutationObserver !== 'undefined' &&
(isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
// 如果promise不支持,就尝试用用MutatiuonObserver,它的回调函数是微任务
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,这个是IE上的,比setTimeout性能稍好
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最终如果都不支持 就用定时器开启宏任务,这是最后的妥协
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick(cb?: (...args: any[]) => any, ctx?: object) {
let _resolve
// 放入回调队列中
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx) // 尝试call,如果失败说明可能传的不是函数
} catch (e: any) {
handleError(e, ctx, 'nextTick') // 抛出错误
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true // 修改执行状态,确保本轮任务队列执行完之前,只执行一次timerFunc
timerFunc() // 执行timerFunc
}
// 如果没有传回调函数,就返回一个promise
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve // 赋值一个成功回调
})
}
}
3. Vue.set
添加响应式数据
data需要预先定义,未定义的数据默认是不会通过observe进行观测的,该方法将一个没有预定义的数据转变成响应式数据
原理:走了一遍defineReactive的set大致相同的逻辑,同时递归调用了defineReactive,如果是数组还调用了observe,默认再触发一次dep.notify通知视图更新
initGlobalApi
import { set, del } from '../observer/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI(Vue: GlobalAPI) {
// ……
Vue.util = {
warn,
extend,
mergeOptions,
defineReactive
}
Vue.set = set // 直接用的observer中的set方法
Vue.delete = del // 直接用的observer中的del方法
Vue.nextTick = nextTick
// ……
}
/src/core/observer/index.ts
export function set(
target: any[] | Record<string, any>,
key: any,
val: any
): any {
// 如果是开发环境 且目标未定义 或者 目标是原始值,就不能被劫持,只有对象才能被劫持
if (__DEV__ && (isUndef(target) || isPrimitive(target))) {
warn(
`Cannot set reactive property on undefined, null, or primitive value: ${target}`
)
}
// 如果目标只读,也无法被劫持
if (isReadonly(target)) {
__DEV__ && warn(`Set operation on key "${key}" failed: target is readonly.`)
return
}
const ob = (target as any).__ob__ // ob是Observer的实例对象
// 如果是数组,且数组索引有效
if (isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key) // 删除无效的索引对应的数据
target.splice(key, 1, val) // 挨个取出进行劫持
// 模拟ssr时,数组方法不会被劫持
if (ob && !ob.shallow && ob.mock) {
observe(val, false, true) // 递归劫持,observer中会调defineReactive > get
}
return val
}
// 遍历对象中的每一项 排除原型属性
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
// isVue表示是vue的实例,ob表示已经被观测过,这两种都不能劫持
if ((target as any)._isVue || (ob && ob.vmCount)) {
__DEV__ &&
warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
// 如果没有ob属性,说明没有被劫持过
if (!ob) {
target[key] = val
return val
}
// 调用defineReactive进行劫持
defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock)
if (__DEV__) { // dep通知watcher更新视图
// notify接收的对象数据最终会传递给renderTracked生命周期钩子
// renderTracked是v3开发环境下才有的,--- 渲染追踪钩子函数
ob.dep.notify({
type: TriggerOpTypes.ADD,
target: target,
key,
newValue: val,
oldValue: undefined
})
} else { // dep通知watcher更新视图
ob.dep.notify()
}
return val
}
4. Vue.delete
删除响应式数据中的某个属性,会彻底解绑其所关联的watcher和dep等
原理:一堆条件判断后(是否存在 是否只读 是否属于该对象 是否还有ob...),移除属性,同时ob.dep.notify通知视图更新
export function initGlobalAPI(Vue: GlobalAPI) {
Vue.delete = del
// ……
}
// core/observer/index.js
export function del(target: any[] | object, key: any) {
// 开发环境 目标未定义 或者 是原始值,就抛出错误
if (__DEV__ && (isUndef(target) || isPrimitive(target))) {
warn(
`Cannot delete reactive property on undefined, null, or primitive value: ${target}`
)
}
// 如果是数组,且数组索引有效
if (isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1) // 删除key对应的数据
return
}
const ob = (target as any).__ob__ // Observer实例对象
// isVue表示是vue的实例,ob表示已经被观测过,这两种都不能直接删除,只需要设置为null
if ((target as any)._isVue || (ob && ob.vmCount)) {
__DEV__ &&
warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
// 如果目标对象只读 也无法删除
if (isReadonly(target)) {
__DEV__ &&
warn(`Delete operation on key "${key}" failed: target is readonly.`)
return
}
if (!hasOwn(target, key)) { // 如果目标对象上不存在key 直接return
return
}
delete target[key] // 删除目标对象上的属性
if (!ob) { // 如果ob不存在,则该属性是静态属性,没有被劫持过
return
}
// 如果ob存在 就会走这里,dep通知watcheer更新视图
if (__DEV__) {
// notify接收的对象数据最终会传递给renderTriggered生命周期钩子
// renderTriggered是v3开发环境下才有的,--- 渲染已触发钩子函数
ob.dep.notify({
type: TriggerOpTypes.DELETE, // 标记本次操作时删除属性
target: target,
key
})
} else {
ob.dep.notify()
}
}
5. Vue.directive
注册或获取全局指令
说明:v-xxx在parse解析时,会被收录进ast的diractive属性中,创建vnode时会取出diractive和选项中的同名directives进行比对,并且调用其钩子函数,将关键信息传递过去;
原理:通过判断vnode的渲染状态,执行一些钩子函数,在这些钩子函数中,可获取指令绑定的参数以及vnode信息,用户可通过这些参数,做一些公共逻辑处理。
全局方法注入
import { initAssetRegisters } from './assets'
export function initGlobalAPI(Vue: GlobalAPI) {
// ……
initAssetRegisters(Vue) // 创建资源注册方法
}
遍历ASSET_TYPES策略添加全局资源注册方法
// shared/constants.js中
export const ASSET_TYPES = ['component', 'directive', 'filter'] as const
import { ASSET_TYPES } from 'shared/constants'
export function initAssetRegisters(Vue: GlobalAPI) { // 创建资源注册方法
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition?: Function | Object
): Function | Object | void {
if (!definition) {
// 如果没传definition就通过id取值,取出带s的同名属性
// 例如 使用directives就能获取到自定义的指令集合
return this.options[type + 's'][id]
} else {
if (__DEV__ && type === 'component') {
validateComponentName(id) // 检测组件名称是否合规
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition) // 注册组件 _base就是前面说的Vue
}
if (type === 'directive' && isFunction(definition)) {
definition = { bind: definition, update: definition } // 注册指令方法
}
this.options[type + 's'][id] = definition // 带s的就是属性,放在options上
return definition
}
}
})
}
vdom/modules/directives.js 核心就是这个_update方法
// …… 核心就是这个_update方法
function _update(oldVnode, vnode) {
const isCreate = oldVnode === emptyNode // 旧节点是否是空节点
const isDestroy = vnode === emptyNode // 新节点是否是空节点
const oldDirs = normalizeDirectives( // 规范化指令,保证其def有值
oldVnode.data.directives,
oldVnode.context
)
const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) // 同上
const dirsWithInsert: any[] = [] // 收集的inserted回调 --- 订阅事件
const dirsWithPostpatch: any[] = [] // 收集的componentUpdate回调 --- 订阅事件
let key, oldDir, dir
for (key in newDirs) { // 遍历新指令
oldDir = oldDirs[key] // 通过同名key找到旧指令
dir = newDirs[key] // 新指令
if (!oldDir) { // 如果没有旧的 那一定是第一次绑定
callHook(dir, 'bind', vnode, oldVnode) // 执行传入的bind函数,只会执行一次 --- 发布事件
if (dir.def && dir.def.inserted) { // 如果传入了inserted,则将其收集起来
dirsWithInsert.push(dir) // 订阅,等会发布执行
}
} else { // 如果旧的有值,说明是修改
dir.oldValue = oldDir.value // 赋值
dir.oldArg = oldDir.arg // 标记根
callHook(dir, 'update', vnode, oldVnode) // 执行传入的update函数 --- 发布事件
if (dir.def && dir.def.componentUpdated) { // 如果传入了该函数 就收集起来
dirsWithPostpatch.push(dir) // 订阅,等会发布执行
}
}
}
if (dirsWithInsert.length) {
const callInsert = () => { // 遍历执行inserted函数
for (let i = 0; i < dirsWithInsert.length; i++) {
callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) // 执行函数
}
}
if (isCreate) { // 旧节点是空的 说明是第一次绑定
mergeVNodeHook(vnode, 'insert', callInsert) // 合并hook 并执行inserted函数
} else {
callInsert() // 直接执行
}
}
if (dirsWithPostpatch.length) {
mergeVNodeHook(vnode, 'postpatch', () => {
for (let i = 0; i < dirsWithPostpatch.length; i++) { // 遍历执行componentUpdate函数
callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
}
})
}
if (!isCreate) { // 如果旧节点为空 不是第一次绑定,已经绑定好了
for (key in oldDirs) {
if (!newDirs[key]) { // 已经绑定好了就触发unding
callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
}
}
}
}
6. Vue.filter
注册或获取全局过滤器
原理:
解析阶段:parse解析阶段的parseFilters函数中,会把|前后解析为表达式和过滤器,warpFilter函数拼接_f函数时,将表达式作为过滤器的参数传递进去,并将其作为返回值;
执行阶段:_f函数对应的resolveFilter函数中,通过resolveAsset函数找到options上同名的过滤函数,执行该函数并传递参数
function wrapFilter(exp: string, filter: string): string {
const i = filter.indexOf('(')
if (i < 0) {
// _f: resolveFilter
return `_f("${filter}")(${exp})` // 拼接render函数,将表达式作为参数传递给过滤函数
} else {
const name = filter.slice(0, i)
const args = filter.slice(i + 1) // 串联多个过滤函数
return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}`
}
}
执行阶段
// core/index.js中
import { renderMixin } from './render'
function Vue(options) {
if (__DEV__ && !(this instanceof Vue)) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
renderMixin(Vue)
// render.js中
import { installRenderHelpers } from './render-helpers/index'
export function renderMixin(Vue: typeof Component) {
installRenderHelpers(Vue.prototype)
// ……
}
// render-helpers/index.js中
import { resolveFilter } from './resolve-filter'
export function installRenderHelpers(target: any) {
target._f = resolveFilter
// ……
}
// resolveFilter
export function resolveFilter(id: string): Function {
return resolveAsset(this.$options, 'filters', id, true) || identity
}
7. Vue.component
注册组件 及查找
原理:调用_base.extend方法,_base是根 也就是Vue
代码参考前面的directive注解 和 extend注解
8. Vue.use
注入插件
原理:高阶函数,拿到传入的插件和插件配置参数,显示去重判断,然后判断插件是否有install方法,将Vue传给插件,插件中接收Vue并在其原型链上扩展相应功能.
export function initUse(Vue: GlobalAPI) {
Vue.use = function (plugin: Function | any) {
// 记录注入了哪些插件
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 取出传进来的参数,并转成数组,从1开始截取,是因为第0个是插件本身
const args = toArray(arguments, 1)
args.unshift(this) // 参数前面插入this,这个this就是Vue实例,把vue作为插件的第一个参数传给插件
if (isFunction(plugin.install)) { // 如果插件有install方法,且是一个函数
plugin.install.apply(plugin, args) // 将参数传给插件,参数第1个是vue,后面的是use接收的选项
} else if (isFunction(plugin)) { // 如果插件本身就是一个函数
plugin.apply(null, args) // 直接执行这个函数,并传递参数
}
installedPlugins.push(plugin) // 维护进插件列表中
return this
}
}
9. Vue.mixin
选项混入
原理:利用mergerOptions往实例上合并数据,子组件会继承这个方法
export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
// extend中,详细注解参考extend
Vue.extend = function (extendOptions: any): typeof Component {
const Sub = function VueComponent(this: any, options: any) {
this._init(options)
} as unknown as typeof Component
// ……
Sub.mixin = Super.mixin // 子组件继承父组件发mixin方法
// ……
return Sub
}
10. Vue.compile
模板编译 --- 等于compileToFunctions
实现原理:
- 通过createCompilerCreator高阶函数传入baseCompile函数,
- createCompilerCreator中处理最终配置finalOptions后.调用baseCompile函数并返回执行结果,
- baseCompiler函数中通过parse方法创建ast语法树,再将ast传入generate函数中生成rende函数,
- 最终返回包含ast\render\staticRenderFns(静态渲染)的对象
tuntime-with-compiler.js中
import { compileToFunctions } from './compiler/index'
import Vue from './runtime/index'
Vue.compile = compileToFunctions
compiler/index.js中
// createCompilerCreator是一个高阶函数,里面还有很多不同配置不同环境的处理,最终会执行传入的这个函数
export const createCompiler = createCompilerCreator(function baseCompile(
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options) // 生成ast语法树
if (options.optimize !== false) { // 如果没有标记过
optimize(ast, options) // 各种标记,例如静态节点
}
const code = generate(ast, options) // 生成render函数
return {
ast,
render: code.render, // 渲染函数
staticRenderFns: code.staticRenderFns // 静态渲染函数
}
})
11. Vue.observable
手动对某个对象进行观测
原理:直接调用observe方法,啥也没做,observe方法中先是一堆可不可以被观测的判断,然后通过new Observer对数据进行观测
Observer中…创建dep > 观测对象 > 观测数组 切片数组7个方法 …… 递归观测 defineReactive …… 这一块太熟悉了懒得整理了
import { observe } from 'core/observer/index'
export function initGlobalAPI(Vue: GlobalAPI) {
// ……
Vue.observable = <T>(obj: T): T => {
observe(obj) // 调用observer进行观测
return obj
}
}
observer方法
export function observe(
value: any,
shallow?: boolean,
ssrMockReactivity?: boolean
): Observer | void {
// 如果存在__ob__属性,且该属性是Observer的实例,说明已经被观测过,直接返回该属性,否则会陷入回调地狱
if (value && hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
// 这里就是一堆能不能被观测的判断
if (
shouldObserve && // 允许观测,这个参数用于后期手动设置
(ssrMockReactivity || !isServerRendering()) && // 是服务端渲染 且ssrMockReactivity为true
(isArray(value) || isPlainObject(value)) && // 是数组或者对象
Object.isExtensible(value) && // 可以扩展 比如添加修改等
!value.__v_skip /* ReactiveFlags.SKIP */ && // 跳过
!isRef(value) && // 排除undefined或者null
!(value instanceof VNode) // 排除vnode
) {
// 通过Observer类观测,并返回一个实例,该实例上有__ob__属性指向实例自身
return new Observer(value, shallow, ssrMockReactivity)
}
}
Observer类
export class Observer {
constructor(public value: any, public shallow = false, public mock = false) {
this.dep = mock ? mockDep : new Dep() // 创建dep
def(value, '__ob__', this) // defineProperty劫持一个__ob__属性,值是自身实例
if (isArray(value)) {
// 改写数组7个方法,切片重写的,新增时调用observer,修改时调用dep.notify
;(value as any).__proto__ = arrayMethods
// ……这里我删掉了一些不重要的代码
if (!shallow) { // 如果不是浅数组,其中可能包含对象
this.observeArray(value) // 递归进行观测
}
} else {
// 对象直接遍历,调用defineReactive进行观测就行了
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock)
}
}
}
// 递归观测数组
observeArray(value: any[]) {
for (let i = 0, l = value.length; i < l; i++) {
observe(value[i], false, this.mock)
}
}
}
defineReactive方法 --- 劫持数据
export function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
const dep = new Dep() // 创建一个属性dep
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return // 如果该属性不可配置(不能添加不能删除不能修改),停止劫持
}
const getter = property && property.get // 预定义get
const setter = property && property.set // 预定义set
// 如果不是浅数据,就递归继续观测,并拿到子属性的ob,childOb可能是数组,需要递归让dep和watcher相互关联
let childOb = !shallow && observe(val, false, mock)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val // 如果本身有get 就取get返回值
if (Dep.target) { // target是watcher取值是pushTarget添加的 如果Dep上面有watcher
if (__DEV__) {
// 关联watcher的时候 执行v3生命周期的数据追踪钩子
dep.depend({
target: obj,
type: TrackOpTypes.GET,
key
})
} else {
dep.depend() // 直接开始关联watcher
}
if (childOb) {
childOb.dep.depend() // 关联子属性watcher
if (isArray(value)) {
dependArray(value) // 递归关联数组watcher
}
}
}
return isRef(value) && !shallow ? value.value : value
},
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (!hasChanged(value, newVal)) {
return
}
if (__DEV__ && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else if (getter) {
// #7981: for accessor properties without setter
return
} else if (!shallow && isRef(value) && !isRef(newVal)) {
value.value = newVal
return
} else {
val = newVal
}
childOb = !shallow && observe(newVal, false, mock)
if (__DEV__) {
dep.notify({
type: TriggerOpTypes.SET,
target: obj,
key,
newValue: newVal,
oldValue: value
})
} else {
dep.notify()
}
}
})
return dep
}
12. Vue.version
版本号
没啥原理,就是构造函数上做个标记而已
四、数据选项原理详解
new Vue执行_init函数,会调用initState初始化数据
export function initState(vm: Component) {
const opts = vm.$options // 取出options上的配置
if (opts.props) initProps(vm, opts.props) // 初始化props
initSetup(vm) // v3的 Composition API
if (opts.methods) initMethods(vm, opts.methods) // 初始化methods
if (opts.data) {
initData(vm) // 初始化data
} else {
const ob = observe((vm._data = {})) // 如果没有传data,则_data默认是空对象
ob && ob.vmCount++ // 这个属性用于统计root 中 $data虚拟机的数量
}
if (opts.computed) initComputed(vm, opts.computed) // 初始化计算属性
if (opts.watch && opts.watch !== nativeWatch) { // 火狐浏览器的Object.prototype上默认有一个watch
initWatch(vm, opts.watch) // 初始化用户watcher
}
}
1. data
自身数据
原理:initData > 取出options上的data > 调用observe > observe判断是否可以被观测后,return new Observe
function initData(vm: Component) {
let data: any = vm.$options.data // 取出options中传入的data
// 如果是函数就用函数返回值,否则默认是对象
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
if (!isPlainObject(data)) { // data如果不是对象
data = {} // 改为对象
__DEV__ && // 开发环境下报错
warn(
'data functions should return an object:\n' +
'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
)
}
// 把data代理到实例上
const keys = Object.keys(data) // 取出key
const props = vm.$options.props // 取出props 和key进行比对,如果同名就报错
const methods = vm.$options.methods // 同上,props、methods和data中的属性名不能有冲突
let i = keys.length
while (i--) {
const key = keys[i]
if (__DEV__) {
// 如果methods和data中的属性名冲突就报错,否则代理到this上时不知道应该访问哪个属性
if (methods && hasOwn(methods, key)) {
warn(`Method "${key}" has already been defined as a data property.`, vm)
}
}
// props也是和methods一样的,不能重名
if (props && hasOwn(props, key)) {
__DEV__ &&
warn(
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(key)) { // 同时属性不能以$开头,因为$开头的会被识别成内部属性
proxy(vm, `_data`, key) // 将key代理到_data,访问vm._data.xxx的时候,可用vm.xxx
}
}
const ob = observe(data) // 观测数据,劫持
ob && ob.vmCount++ // 统计虚拟机数量
}
2. props
接收的数据
原理:
- initProps > 通过$parent判断当前是否为根节点,如果是根节点则修改shouldObserve为false(是否允许观测)
- 取出options上面的propsData > 遍历propsData并调用validateProp进行属性校验 > 调用defineReactive进行数据劫持,重置shouldObserve
3. propsData
new 实例时传递和接收的数据,等同于props
原理:new 实例时传递的props,可在new的时候传递,等同于props
4. computed
计算属性
原理:
- initComputed > 取出options上的computed > 遍历并同步一份到vm._computedWatchers > new Watcher > dedineComputed
- defineComputed > 判断是否缓存过 > 劫持get判断:缓存过走createComputedGetter\否则createGetterInvoker > defineProperty将对key的访问指向劫持get
- createComputedGetter > 高阶函数,返回一个computedGetter函数 > 取出_computedWatchers中key对应的watcher,判断dirty > watcher.deped(watcher关联dep) > return watcher.value
5. methods
方法合集
原理:mergeoptions合并到$options, 初始化时遍历取出methods,并bind改变this指向vm
6. watch
用户watcher
原理:
- initWatch > 判断是否数组 是则遍历数组调用createWatcher,否则直接调用createWatcher
- createWatcher > 一堆判断后调用vm.$watch
- $watch > new Watcher > this.get() > 如果immdiate为true 立即pushTarget 取值watcher.value popTarget > 如果deep为true,则调用_traverse递归取值 > while循环 > 遇到取过值的会通过__ob__.dep.id标记
五、DOM选项原理详解
1. el
根节点挂载的dom元素
2. template
渲染模板,渲染优先级大于data,如果有则取它,没有则会取el对应的元素的innerHTML
3. render
虚拟dom转换函数,将传入的数据转换成vnode
4. renderError
render生效前调用的,可在做类似暂无数据提示的组件
六、生命周期选项14个
原理:
- mergeOptions执行starts中的lifecycle_hooks策略,将对应的hook维护到options上,
- 通过callhook取出对应的handler,调用该handler,就是一个发布订阅而已,下面只列举部分钩子,其他都一样
callHook(vm, 'beforeCreate', undefined, false) // 数据初始化前执行beforeCreate
initInjections(vm) // 初始化injections
initState(vm) // 初始话数据 > props>methods>data>computed>watch
initProvide(vm) // 初始化provide
callHook(vm, 'created') // 数据初始化完成,执行created
callhook函数
export function callHook(
vm: Component,
hook: string,
args?: any[],
setContext = true
) {
// 禁用深度收集
pushTarget() // dep收集watcher watcher进栈
const prev = currentInstance // 当前实例
setContext && setCurrentInstance(vm)
const handlers = vm.$options[hook] // 从options上取出对应hook
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
// 带错误处理的调用,尝试执行函数,执行失败会抛出错误信息
invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
}
}
if (vm._hasHookEvent) {
vm.$emit('hook:' + hook)
}
setContext && setCurrentInstance(prev)
popTarget() // watcher出栈
}
七、资源选项原理详解
1. directives
自定义指令
2. filters
过滤器
3. components
组件注册及查找
八、组合选项原理详解
1. parent
认贼作父,强制和其它组件 建立父子关系
2. mixins
混入,其实就是调用mergeOptions把传进来的options合并到vm上
3. extends
允许声明扩展另一个组件,原理是通过mergeOptions合并到parent中,类似mixin
4. provide / inject
可以理解为上下文,可用于传递数据
原理:
- provide注入依赖:provide在mergePorions时,会通过mergeData添加一个form属性;initProvide时,遍历$options.provide > Object.defineProperty + Object.getOwnPropertyDescriptor 劫持_provide,getOwnPropertyDescriptor中有属性的所有描述(可读可写可枚举可扩展 get set等),原来就有get set就继续使用
- inject引用依赖:initInjections时,遍历inject,不断通过form往上找父节点上的同名属性,找到后先取消观测,再用defineReactive观测一下,就近原则,先找到谁就是谁,所以并不安全,存在被覆盖可能性。
九、其它选项原理详解
1. name
组件名称,没有实质作用,只是便于调试,更友好的警告信息以及跟语义化的组件构造函数名
2. delimiters
自定义模板语法中的文本取值,例如{{}} 改写为[[]],原理是再createCompileToFunction时,改写正则匹配规则
3.functional
标记是否为函数组件
4.model
v-model的自定义
5.inheritAttrs
传递给组件的属性如果没有用props接收,会被添加到元素上面,设为false可隐藏属性
6.comments
保留代码注释
十、实例属性原理详解
1. vm.$data
等同于data
2. vm.$props
等同于props
3. vm.$el
实例真实Dom根元素
4. vm.$options
当前实例的全部选项
5. vm.$parent
父节点
6. vm.$root
根节点
7. vm.$children
子节点
8. vm.$slots
访问被具名插槽分发的内容
9. vm.$scopedSlots
访问作用域插槽
10. vm.$refs
用ref注册的dom对象
11. vm.$isServer
是否运行于服务器
12. vm.$attrs
父组件传递过来,但没有被子组件props接收的的属性,会被维护到子组件的$attrs
13. vm.$listeners
父组件中不含.native的事件监听器
十一、实例数据方法原理详解
1. vm.$on
监听实例事件 ,eventMixin注入 > 将传入的函数添加到_events中
2. vm.$once
派发一个只执行一次的实例事件,eventMixin注入 > 将一个执行fn的函数传入_events,该函数中执fn时先调用$off移除事件
3. vm.$off
关闭监听事件,eventMixin注入 > 移除_events中对应的函数
4. vm.$emit
触发监听事件,eventMixin注入 > 取出_event中对应的函数并执行
十二、实例生命周期方法原理详解
1. vm.$mount
挂载实例,调用mountComponent方法,创建渲染watcher
2. vm.$forceUpdate
强制渲染一次,很简单,调用vm._watcher.update()
3. vm.$nextTick
调用nextTick方法
4. vm.$destroy
销毁实例,两个callHook,将很多属性改为null
十三、原生指令原理详解
1. v-text
渲染指定文本
2. v-html
渲染指定html
3. v-show
显示隐藏
4. v-if
是否渲染
5. v-else
是否渲染
6. v-else-if
是否渲染
7. v-for
循环
8. v-on
绑定事件$on
9. v-bind
绑定属性
10. v-model
双向绑定
input --- :value+@input
select --- :value+@change
checkbox --- checked+@change
11. v-slot
具名插槽
12. v-pre
跳过编译,标记静态节点可提高性能
13. v-cloak
添加到实例属性上直到关联实例结束编译,例如{{ name }} name可能时异步取值,在防止取到值之前的错误渲染,添加了该指令后不会被渲染为‘{{ name }}’,同时结合css将其隐藏,[v-clock]:{ display:none }
14. v-once
只渲染一次,被标记为静态节点,之后更新操作都不会被重新渲染
十四、特殊属性原理详解
1. key
标记节点,vnode进行节点比对时大有用处
2. ref
获取真实dom,将该节点的$el赋值给ref所指的变量名,并同步给vm
3. is
用于动态组件,指向组件的key
4. slot
废弃 被v-slot替代
5. slot-scope
废弃 被v-slot替代
6. scope
废弃 被v-slot替代
十五、内置组件原理详解
1. component
渲染动态组件 通过is查找对应组件
2. transition
动画组件
3. transition-group
动画组组件
4. keep-alive
视图缓存
3个属性:include-对哪些缓存 exclude-对哪些不缓存 max-最大缓存数量
2个生命周期钩子:activated-缓存组件生效时,deactivated-缓存组件失效时,createLifcycle进行收集,cacheVnode时触发activated,remove时触发deactivated
缓存策略:LRU最近最久未使用淘汰法
5. slot
插槽