前言
本文是vue2.x源码分析的第八篇,主要看v-*指令的处理过程!
实例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Vue</title><script src="./vue.js" type="text/javascript" charset="utf-8" ></script></head><body><div id="app"><!-- <div>{{pre}}</div> --><!-- <div v-text='text'></div> --><!-- <div v-html='html'></div> --><!-- <div v-pre>{{pre}}</div> --><!-- <div v-for='item in items' >{{item}}</div> --><!-- <div v-cloak>{{cloak}}</div> --><!-- <div v-once>{{once}}</div> --><!-- <div v-show='show'>v-show</div> --><!-- <div v-on:click='click'>click</div> --><!-- <div v-bind:me='message'></div> --><div v-model='model'></div></div><script>var vm=new Vue({el:'#app',name:'app',data:{items:[1,2,3],pre:'v-pre',once:'v-once-1',show:true,text:'v-text测试',html:'<span>v-html测试</span>',cloak:"v-cloak",message:'message',model:'model'},methods:{click:function(){console.log('click')}}});</script></body></html>
根据AST的不同,将v-*指令系列分为三部分
1 插值
基本的插值AST有9个基本属性

2 v-if,v-on,v-pre,v-once,v-for,v-bind
这6个指令会在9个基本属性上添加自己的属性(有的会去掉部分基本属性)






3 v-show,v-model,v-text,v-html,v-cloak
这5个指令都会加上directives,hasBindings两个属性





此外,自定义的指令如v-focus等的处理过程与这5个指令类似
4 invokeCreateHooks(vnode, insertedVnodeQueue)
在上一节中提到createElm函数的四步中,第三步其实是很重要的
function invokeCreateHooks (vnode, insertedVnodeQueue) {for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {cbs.create[i$1](emptyNode, vnode);}i = vnode.data.hook; // Reuse variableif (isDef(i)) {if (isDef(i.create)) { i.create(emptyNode, vnode); }if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }}}
这里的cbs如下:
activate:Array(1)create:Array(8)destroy:Array(2)remove:Array(1)update:Array(7)__proto__:Object
其中的create是含有8个函数的数组,这8个函数依次是
//1、处理data对象中的attrs,function updateAttrs(oldVnode, vnode) {if (!oldVnode.data.attrs && !vnode.data.attrs) {return}var key, cur, old;var elm = vnode.elm;var oldAttrs = oldVnode.data.attrs || {};var attrs = vnode.data.attrs || {};// clone observed objects, as the user probably wants to mutate itif (attrs.__ob__) {attrs = vnode.data.attrs = extend({}, attrs);}for (key in attrs) {cur = attrs[key];old = oldAttrs[key];if (old !== cur) {setAttr(elm, key, cur);}}// #4391: in IE9, setting type can reset value for input[type=radio]/* istanbul ignore if */if (isIE9 && attrs.value !== oldAttrs.value) {setAttr(elm, 'value', attrs.value);}for (key in oldAttrs) {if (attrs[key] == null) {if (isXlink(key)) {elm.removeAttributeNS(xlinkNS, getXlinkProp(key));} else if (!isEnumeratedAttr(key)) {elm.removeAttribute(key);}}}}//2 处理data对象中的class和staticClassfunction updateClass(oldVnode, vnode) {var el = vnode.elm;var data = vnode.data;var oldData = oldVnode.data;if (!data.staticClass && !data.class && (!oldData || (!oldData.staticClass && !oldData.class))) {return}var cls = genClassForVnode(vnode);// handle transition classesvar transitionClass = el._transitionClasses;if (transitionClass) {cls = concat(cls, stringifyClass(transitionClass));}// set the classif (cls !== el._prevClass) {el.setAttribute('class', cls);el._prevClass = cls;}}//3 处理data对象中的on,v-on就是在这儿处理的function updateDOMListeners(oldVnode, vnode) {if (!oldVnode.data.on && !vnode.data.on) {return}var on = vnode.data.on || {};var oldOn = oldVnode.data.on || {};target$1 = vnode.elm;normalizeEvents(on);updateListeners(on, oldOn, add$1, remove$2, vnode.context);}//4 处理data对象中的domProps,v-on就是在这儿处理的,v-text和v-html就是在这儿处理function updateDOMProps(oldVnode, vnode) {if (!oldVnode.data.domProps && !vnode.data.domProps) {return}var key, cur;var elm = vnode.elm;var oldProps = oldVnode.data.domProps || {};var props = vnode.data.domProps || {};// clone observed objects, as the user probably wants to mutate itif (props.__ob__) {props = vnode.data.domProps = extend({}, props);}for (key in oldProps) {if (props[key] == null) {elm[key] = '';}}for (key in props) {cur = props[key];// ignore children if the node has textContent or innerHTML,// as these will throw away existing DOM nodes and cause removal errors// on subsequent patches (#3360)if (key === 'textContent' || key === 'innerHTML') {if (vnode.children) {vnode.children.length = 0;}if (cur === oldProps[key]) {continue}}if (key === 'value') {// store value as _value as well since// non-string values will be stringifiedelm._value = cur;// avoid resetting cursor position when value is the samevar strCur = cur == null ? '' : String(cur);if (shouldUpdateValue(elm, vnode, strCur)) {elm.value = strCur;}} else {elm[key] = cur;}}}//5 处理data对象中的style和staticStylefunction updateStyle(oldVnode, vnode) {var data = vnode.data;var oldData = oldVnode.data;if (!data.staticStyle && !data.style && !oldData.staticStyle && !oldData.style) {return}var cur, name;var el = vnode.elm;var oldStaticStyle = oldVnode.data.staticStyle;var oldStyleBinding = oldVnode.data.style || {};// if static style exists, stylebinding already merged into it when doing normalizeStyleDatavar oldStyle = oldStaticStyle || oldStyleBinding;var style = normalizeStyleBinding(vnode.data.style) || {};vnode.data.style = style.__ob__ ? extend({}, style) : style;var newStyle = getStyle(vnode, true);for (name in oldStyle) {if (newStyle[name] == null) {setProp(el, name, '');}}for (name in newStyle) {cur = newStyle[name];if (cur !== oldStyle[name]) {// ie9 setting to null has no effect, must use empty stringsetProp(el, name, cur == null ? '' : cur);}}}//6 处理data对象中的show,与transition有关function _enter(_, vnode) {if (!vnode.data.show) {enter(vnode);}}//7 处理data对象中的refcreate: function create(_, vnode) {registerRef(vnode);}//其中registerRef函数如下:function registerRef(vnode, isRemoval) {var key = vnode.data.ref;if (!key) {return}var vm = vnode.context;var ref = vnode.componentInstance || vnode.elm;var refs = vm.$refs;if (isRemoval) {if (Array.isArray(refs[key])) {remove(refs[key], ref);} else if (refs[key] === ref) {refs[key] = undefined;}} else {if (vnode.data.refInFor) {if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {refs[key].push(ref);} else {refs[key] = [ref];}} else {refs[key] = ref;}}}//8 处理data对象中的directivesfunction updateDirectives(oldVnode, vnode) {if (oldVnode.data.directives || vnode.data.directives) {_update(oldVnode, vnode);}}//其中_updata函数如下:function _update(oldVnode, vnode) {var isCreate = oldVnode === emptyNode;var isDestroy = vnode === emptyNode;var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);var dirsWithInsert = [];var dirsWithPostpatch = [];var key, oldDir, dir;for (key in newDirs) {oldDir = oldDirs[key];dir = newDirs[key];if (!oldDir) {// new directive, bindcallHook$1(dir, 'bind', vnode, oldVnode);if (dir.def && dir.def.inserted) {dirsWithInsert.push(dir);}} else {// existing directive, updatedir.oldValue = oldDir.value;callHook$1(dir, 'update', vnode, oldVnode);if (dir.def && dir.def.componentUpdated) {dirsWithPostpatch.push(dir);}}}if (dirsWithInsert.length) {var callInsert = function() {for (var i = 0; i < dirsWithInsert.length; i++) {callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);}};if (isCreate) {mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert);} else {callInsert();}}if (dirsWithPostpatch.length) {mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function() {for (var i = 0; i < dirsWithPostpatch.length; i++) {callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);}});}if (!isCreate) {for (key in oldDirs) {if (!newDirs[key]) {// no longer present, unbindcallHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);}}}}
patch在createElm调用完后,还调用了invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);这个就是执行指令中的inserted函数
对于内置的指令v-show和v-model,vue已经为其准备好了bind等函数,若要自定义指令,则需自己定义bind,inserted,update,updateComponent,unbind函数
如:
// 注册一个全局自定义指令 v-focusVue.directive('focus', {// 当绑定元素插入到 DOM 中。inserted: function (el) {el.focus()}})//若简写成这样// 注册一个全局自定义指令 v-focusVue.directive('focus', function (el) {el.focus()})则vue会自动将该函数视为bind和update函数,此时不会取得focus效果,因为el.focus()执行时机过早,尚未插入父元素,不在文档流中
