第 3517 行至第 3894 行
- renderMixin // 引入视图渲染混合函数
- ensureCtor
- createAsyncPlaceholder
- resolveAsyncComponent
- isAsyncPlaceholder
- getFirstComponentChild
- initEvents// 初始化事件
- add
- remove$1
- createOnceHandler
- updateComponentListeners
- eventsMixin // 挂载事件响应相关方法
第 3898 行至第 4227 行
- setActiveInstance
- initLifecycle
- lifecycleMixin// 挂载生命周期相关方法
- mountComponent
- updateChildComponent
- isInInactiveTree
- activateChildComponent
- deactivateChildComponent
- callHook
几乎所有JS框架或插件的编写都有一个类似的模式,即向全局输出一个类或者说构造函数,通过创建实例来使用这个类的公开方法,或者使用类的静态全局方法辅助实现功能。相信精通Jquery或编写过Jquery插件的开发者会对这个模式非常熟悉。Vue.js也如出一辙,只是一开始接触这个框架的时候对它所能实现的功能的感叹盖过了它也不过是一个内容较为丰富和精致的大型类的本质。
阶段小结
这里要对 js 的继承有一个深刻的理解。link
- 类继承
function Animal(){ this.live=true; } function Dog(name){ this.name=name } Dog.prototype=new Animal() var dog1=new Dog("wangcai") console.log(dog1)// Dog {name: "wangcai"} console.log(dog1.live)// true
- 构造继承
function Animal(name,color){ this.name=name; this.color=color;} function Dog(){ Animal.apply(this,arguments) } var dog1=new Dog("wangcai","balck") console.log(dog1)// Dog {name: "wangcai", color: "balck"}
- 组合继承(类继承 + 构造继承)
function Animal(name,color){ this.name=name; this.color=color; this.live=true; } function Dog(){ Animal.apply(this, arguments); } Dog.prototype=new Animal() var dog1=new Dog("wangcai","black") console.log(dog1)// Dog {name: "wangcai", color: "black", live: true}
- 寄生组合式继承
- extend继承
Vue 同 Jquery 一样,本质也是一个大型的类库。
// 定义Vue构造函数,形参options
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) }
// 功能函数
// 引入初始化混合函数 import { initMixin } from './init' // 引入状态混合函数 import { stateMixin } from './state' // 引入视图渲染混合函数 import { renderMixin } from './render' // 引入事件混合函数 import { eventsMixin } from './events' // 引入生命周期混合函数 import { lifecycleMixin } from './lifecycle' // 引入warn控制台错误提示函数 import { warn } from '../util/index' ... // 挂载初始化方法 initMixin(Vue) // 挂载状态处理相关方法 stateMixin(Vue) // 挂载事件响应相关方法 eventsMixin(Vue) // 挂载生命周期相关方法 lifecycleMixin(Vue) // 挂载视图渲染方法 renderMixin(Vue)
第 4231 行至第 4406 行
- resetSchedulerState // 重置状态
- flushSchedulerQueue// 据变化最终会把flushSchedulerQueue传入到nextTick中执行flushSchedulerQueue函数会遍历执行watcher.run()方法,watcher.run()方法最终会完成视图更新
vue中dom的更像并不是实时的,当数据改变后,vue会把渲染watcher添加到异步队列,异步执行,同步代码执行完成后再统一修改dom。
- callUpdatedHooks
- queueActivatedComponent
- callActivatedHooks
- queueWatcher
第 4412 行至第 4614 行
- Watcher// !important 重中之重的重点
这一 part 在 Watcher 的原型链上定义了get、addDep、cleanupDeps、update、run、evaluate、depend、teardown 方法,即 Watcher 的具体实现的一些方法,比如新增依赖、清除、更新试图等。
每个Vue组件都有一个对应的watcher,这个watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染。
第 4618 行至第 5071 行
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = 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) } // 如果是Vue的实例,则不需要被observe // a flag to avoid this being observed vm._isVue = true // merge options // 第一步: options参数的处理 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 { // mergeOptions接下来我们会详细讲哦~ vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } // 第二步: renderProxy /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 第三步: vm的生命周期相关变量初始化 initLifecycle(vm) // 第四步: vm的事件监听初始化 initEvents(vm) // 第五步: vm的编译render初始化 initRender(vm) // 第六步: vm的beforeCreate生命钩子的回调 callHook(vm, 'beforeCreate') // 第七步: vm在data/props初始化之前要进行绑定 initInjections(vm) // resolve injections before data/props // 第八步: vm的sate状态初始化 initState(vm) // 第九步: vm在data/props之后要进行提供 initProvide(vm) // resolve provide after data/props // 第十步: vm的created生命钩子的回调 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) } // 第十一步:render & mount if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
主要是为我们的Vue原型上定义一个方法_init。然后当我们执行new Vue(options) 的时候,会调用这个方法。而这个_init方法的实现,便是我们需要关注的地方。 前面定义vm实例都挺好理解的,主要我们来看一下mergeOptions这个方法,其实Vue在实例化的过程中,会在代码运行后增加很多新的东西进去。我们把我们传入的这个对象叫options,实例中我们可以通过vm.$options访问到。
0 至 5000 行 总结
从 0 至 5000 行我们可以清晰看到 Vue 模板编译的轮廓了。
- 笔者将这一部分出现的关键词进行按顺序罗列:
- function (global, factory)
- 工具函数
- Dep
- Observe
- VNode
- nextTick
- 事件机制
- Render
- components
- Watcher
我们可以总结:Vue 的核心就是 VDOM !对 DOM 对象的操作调整为操作 VNode 对象,采用 diff 算法比较差异,一次 patch。
render 的流程是:
- Vue使用HTML的Parser将HTML模板解析为AST
- function render(){}
- Virtual DOM
- watcher将会在组件render的时候收集组件所依赖的数据,并在依赖有更新的时候,触发组件重新渲染
推荐阅读:link
第 5073 行至第 5446 行
// 定义 Vue 构造函数 function Vue (options) { if (!(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } // 将 Vue 作为参数传递给导入的五个方法 initMixin(Vue);// 初始化 Mixin stateMixin(Vue);// 状态 Mixin eventsMixin(Vue);// 事件 Mixin lifecycleMixin(Vue);// 生命周期 Mixin renderMixin(Vue);// 渲染 Mixin
这一部分就是初始化函数的调用。
// Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering });
为什么这么写?
Object.defineProperty能保护引入的库不被重新赋值,如果你尝试重写,程序会抛出“TypeError: Cannot assign to read only property”的错误。
// 版本 Vue.version = '2.6.11';
阶段小结
这一部分是 Vue index.js 的内容,包括 Vue 的整个挂在过程
- 先进入 initMixin(Vue),在prototype上挂载
Vue.prototype._init = function (options) {}
- 进入 stateMixin(Vue),在prototype上挂载 Vue.prototype.$data
Vue.prototype.$props Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function(){}
- 进入eventsMixin(Vue),在prototype上挂载
Vue.prototype.$on Vue.prototype.$once Vue.prototype.$off Vue.prototype.$emit
- 进入lifecycleMixin(Vue),在prototype上挂载
Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroy
- 最后进入renderMixin(Vue),在prototype上挂载 Vue.prototype.$nextTick
Vue.prototype._render Vue.prototype._o = markOnce Vue.prototype._n = toNumber Vue.prototype._s = toString Vue.prototype._l = renderList Vue.prototype._t = renderSlot Vue.prototype._q = looseEqual Vue.prototype._i = looseIndexOf Vue.prototype._m = renderStatic Vue.prototype._f = resolveFilter Vue.prototype._k = checkKeyCodes Vue.prototype._b = bindObjectProps Vue.prototype._v = createTextVNode Vue.prototype._e = createEmptyVNode Vue.prototype._u = resolveScopedSlots Vue.prototype._g = bindObjectListeners
mergeOptions使用策略模式合并传入的options和Vue.options合并后的代码结构, 可以看到通过合并策略components,directives,filters继承了全局的, 这就是为什么全局注册的可以在任何地方使用,因为每个实例都继承了全局的, 所以都能找到。
推荐阅读:
new 一个 Vue 对象发生了什么:
第 5452 行至第 5655 行
// these are reserved for web because they are directly compiled away // during template compilation // 这些是为web保留的,因为它们是直接编译掉的 // 在模板编译期间
- isBooleanAttr
- genClassForVnode// class 转码获取vonde 中的staticClass 静态class 和class动态class转义成真实dom需要的class格式。然后返回class字符串
- mergeClassData// mergeClassData
- renderClass// 渲染calss 这里获取到已经转码的calss
- stringifyClass// 转码 class,把数组格式,对象格式的calss 全部转化成 字符串格式
- stringifyArray// 数组字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串
- stringifyObject// 对象字符串变成字符串,然后用空格 隔开 拼接 起来变成字符串
- namespaceMap
- isHTMLTag
- isSVG// 判断svg 标签
- isUnknownElement// 检查dom 节点的tag标签 类型 是否是VPre 标签 或者是判断是否是浏览器自带原有的标签
- isTextInputType // //匹配'text,number,password,search,email,tel,url'
这一 part 没有特别要说的,主要是对 class 的转码、合并和其他二次封装的工具函数。实际上我们在 Vue 源码很多地方看到了这样的封装,在平常的开发中,我们也得要求自己封装基本的函数。如果能形成自己习惯用的函数的库,会方便很多,且对自己能力也是一个提升。
第 5659 行至第 5792 行
- createElement // 创建元素,实例化 VNode
- createElementNS
- createTextNode
- createComment
- insertBefore
- removeChild
- appendChild
- parentNode
- nextSibling
- tagName
- setTextContent
- setStyleScope
- nodeOps
// nodeOps: createElement: createElement$1, //创建一个真实的dom createElementNS: createElementNS, //创建一个真实的dom svg方式 createTextNode: createTextNode, // 创建文本节点 createComment: createComment, // 创建一个注释节点 insertBefore: insertBefore, //插入节点 在xxx dom 前面插入一个节点 removeChild: removeChild, //删除子节点 appendChild: appendChild, //添加子节点 尾部 parentNode: parentNode, //获取父亲子节点dom nextSibling: nextSibling, //获取下一个兄弟节点 tagName: tagName, //获取dom标签名称 setTextContent: setTextContent, // //设置dom 文本 setStyleScope: setStyleScope //设置组建样式的作用域
- ref
- registerRef // 注册ref或者删除ref。比如标签上面设置了ref='abc' 那么该函数就是为this.$refs.abc 注册ref 把真实的dom存进去
阶段小结
这里的重点想必就是 “ref” 了
在绝大多数情况下,我们最好不要触达另一个组件实例内部或手动操作 DOM 元素。不过也确实在一些情况下做这些事情是合适的。ref 为我们提供了解决途径。
ref属性不是一个标准的HTML属性,只是Vue中的一个属性。
第 5794 行至第 6006 行
Virtual DOM !
没错,这里就是 虚拟 dom 生成的源码相关。
- sameVnode
- sameInputType
- createKeyToOldIdx
- createPatchFunction // !important:patch 把 vonde 渲染成真实的 dom
- emptyNodeAt
- createRmCb
- removeNode
- isUnknownElement?1
- createElm // 创造 dom 节点
- createComponent // 创建组件,并且判断它是否实例化过
- initComponent
createElement方法接收一个tag参数,在内部会去判断tag标签的类型,从而去决定是创建一个普通的VNode还是一个组件类VNode;
createComponent 的实现,在渲染一个组件的时候的 3 个关键逻辑:
- 构造子类构造函数,
- 安装组件钩子函数
- 实例化 vnode。createComponent 后返回的是组件 vnode,它也一样走到 vm._update 方法
我们传入的 vnode 是组件渲染的 vnode,也就是我们之前说的 vm._vnode,如果组件的根节点是个普通元素,那么 vm._vnode 也是普通的 vnode,这里 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值是 false。接下来的过程就系列一的步骤一样了,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复过程,这样通过一个递归的方式就可以完整地构建了整个组件树。
initComponent 初始化组建,如果没有tag标签则去更新真实dom的属性,如果有tag标签,则注册或者删除ref 然后为insertedVnodeQueue.push(vnode);
第 6008 行至第 6252 行
- reactivateComponent
- insert
- createChildren
- isPatchable
- invokeCreateHooks
- setScope
- addVnodes // 添加 Vnodes
- invokeDestroyHook
- removeVnodes // 移除 Vnodes
- removeAndInvokeRemoveHook
- updateChildren // 在patchVnode中提到,如果新老节点都有子节点,但是不相同的时候就会调用 updateChildren,这个函数通过diff算法尽可能的复用先前的DOM节点。
// diff 算法就在这里辣!详解link
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx, idxInOld, elmToMove, refElm while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (isUndef(oldStartVnode)) { oldStartVnode = oldCh[++oldStartIdx] } else if (isUndef(oldEndVnode)) { oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null if (isUndef(idxInOld)) { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) newStartVnode = newCh[++newStartIdx] } else { elmToMove = oldCh[idxInOld] if (sameVnode(elmToMove, newStartVnode)) { patchVnode(elmToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm) newStartVnode = newCh[++newStartIdx] } else { createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm) newStartVnode = newCh[++newStartIdx] } } } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } }
- checkDuplicateKeys
- findIdxInOld
reactivateComponent 承接上文 createComponent
第 6259 行至第 6561 行
- patchVnode // 如果符合sameVnode,就不会渲染vnode重新创建DOM节点,而是在原有的DOM节点上进行修补,尽可能复用原有的DOM节点。
- invokeInsertHook
- isRenderedModule
- hydrate
- assertNodeMatch
- patch // !important: patch的本质是将新旧vnode进行比较,创建、删除或者更新DOM节点/组件实例
阶段小结
Vue 的核心思想:组件化。
这一部分是关于构建组件树,形成虚拟 dom ,以及非常重要的 patch 方法。
再来亿遍:
- 原因:当修改某条数据的时候,这时候js会将整个DOM Tree进行替换,这种操作是相当消耗性能的。所以在Vue中引入了Vnode的概念:Vnode是对真实DOM节点的模拟,可以对Vnode Tree进行增加节点、删除节点和修改节点操作。这些过程都只需要操作VNode Tree,不需要操作真实的DOM,大大的提升了性能。修改之后使用diff算法计算出修改的最小单位,在将这些小单位的视图进行更新。
- 原理:data中定义了一个变量a,并且模板中也使用了它,那么这里生成的Watcher就会加入到a的订阅者列表中。当a发生改变时,对应的订阅者收到变动信息,这时候就会触发Watcher的update方法,实际update最后调用的就是在这里声明的updateComponent。 当数据发生改变时会触发回调函数updateComponent,updateComponent是对patch过程的封装。patch的本质是将新旧vnode进行比较,创建、删除或者更新DOM节点/组件实例。
联系前后QA
Q:vue.js 同时多个赋值是一次性渲染还是多次渲染DOM?
A:官网已给出答案:cn.vuejs.org/v2/guide/re…
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。
这样是不是有种前后连贯起来的感觉,原来 nextTick 是这样子的。
第 6566 行至第 7069 行
- directives // 官网:cn.vuejs.org/v2/guide/cu…
- updateDirectives // 更新指令
- _update
- normalizeDirectives // 统一directives的格式
- getRawDirName // 返回指令名称 或者属性name名称+修饰符
- callHook$1 //触发指令钩子函数
- updateAttrs // 更新属性
- setAttr // 设置属性
- baseSetAttr
- updateClass // 更新样式
- klass
- parseFilters // 处理value 解析成正确的value,把过滤器 转换成 vue 虚拟dom的解析方法函数 比如把过滤器 ' ab | c | d' 转换成 _f("d")(_f("c")(ab))
- wrapFilter // 转换过滤器格式
- baseWarn // 基础警告
- pluckModuleFunction //循环过滤数组或者对象的值,根据key循环 过滤对象或者数组[key]值,如果不存在则丢弃,如果有相同多个的key值,返回多个值的数组
- addProp //在虚拟dom中添加prop属性
- addAttr //添加attrs属性
- addRawAttr //添加原始attr(在预转换中使用)
- addDirective //为虚拟dom 添加一个 指令directives属性 对象
- addHandler // 为虚拟dom添加events 事件对象属性
前面围绕“指令”和“过滤器”的一些基础工具函数。
后面围绕为虚拟 dom 添加属性、事件等具体实现函数。
第 7071 行至第 7298 行
- getRawBindingAttr
- getBindingAttr // 获取 :属性 或者v-bind:属性,或者获取属性 移除传进来的属性name,并且返回获取到 属性的值
- getAndRemoveAttr // 移除传进来的属性name,并且返回获取到 属性的值
- getAndRemoveAttrByRegex
- rangeSetItem
- genComponentModel // 为虚拟dom添加model属性
/* * Parse a v-model expression into a base path and a final key segment. * Handles both dot-path and possible square brackets. * 将 v-model 表达式解析为基路径和最后一个键段。 * 处理点路径和可能的方括号。 */
- parseModel //转义字符串对象拆分字符串对象 把后一位key分离出来
// 如果数据是object.info.name的情况下 则返回是 {exp: "object.info",key: "name"} // 如果数据是object[info][name]的情况下 则返回是 {exp: "object[info]",key: "name"}
- next
- eof
- parseBracket //检测 匹配[] 一对这样的=括号
- parseString // 循环匹配一对''或者""符号
这一部分包括:原生指令 v-bind 和为虚拟 dom 添加 model 属性,以及格式校验工具函数。
第 7300 行至第 7473 行
- model
- genCheckboxModel // 为input type="checkbox" 虚拟dom添加 change 函数 ,根据v-model是否是数组,调用change函数,调用 set 去更新 checked选中数据的值
- genRadioModel // 为虚拟dom inpu标签 type === 'radio' 添加change 事件 更新值
- genSelect // 为虚拟dom添加change 函数 ,change 函数调用 set 去更新 select 选中数据的值
- genDefaultModel // 如果虚拟dom标签是 'input' 类型不是checkbox,radio 或者是'textarea' 标签的时候,获取真实的dom的value值调用 change或者input方法执行set方法更新数据
阶段小结
- v-bind、v-model
区别:
- v-bind 用来绑定数据和属性以及表达式,缩写为':'
- v-model 使用在表单中,实现双向数据绑定的,在表单元素外使用不起作用
Q:你知道v-model的原理吗?说说看
A: v-model本质上是语法糖,即利用v-model绑定数据,其实就是既绑定了数据,又添加了一个input事件监听 link
- 自定义指令钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。 4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。 5. unbind:只调用一次,指令与元素解绑时调用。
- 指令钩子函数会被传入以下参数:
1. el:指令所绑定的元素,可以用来直接操作 DOM 。 2. binding:一个对象,包含以下属性: name:指令名,不包括 v- 前缀。 value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。 oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。 arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。 modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。 3. vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。 4. oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。