前言

本文是vue2.x源码分析的第八篇,主要看v-*指令的处理过程!

实例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Vue</title>
  6. <script src="./vue.js" type="text/javascript" charset="utf-8" ></script>
  7. </head>
  8. <body>
  9. <div id="app">
  10. <!-- <div>{{pre}}</div> -->
  11. <!-- <div v-text='text'></div> -->
  12. <!-- <div v-html='html'></div> -->
  13. <!-- <div v-pre>{{pre}}</div> -->
  14. <!-- <div v-for='item in items' >{{item}}</div> -->
  15. <!-- <div v-cloak>{{cloak}}</div> -->
  16. <!-- <div v-once>{{once}}</div> -->
  17. <!-- <div v-show='show'>v-show</div> -->
  18. <!-- <div v-on:click='click'>click</div> -->
  19. <!-- <div v-bind:me='message'></div> -->
  20. <div v-model='model'></div>
  21. </div>
  22. <script>
  23. var vm=new Vue({
  24. el:'#app',
  25. name:'app',
  26. data:{
  27. items:[1,2,3],
  28. pre:'v-pre',
  29. once:'v-once-1',
  30. show:true,
  31. text:'v-text测试',
  32. html:'<span>v-html测试</span>',
  33. cloak:"v-cloak",
  34. message:'message',
  35. model:'model'
  36. },
  37. methods:{
  38. click:function(){
  39. console.log('click')
  40. }
  41. }
  42. });
  43. </script>
  44. </body>
  45. </html>

根据AST的不同,将v-*指令系列分为三部分

1 插值

基本的插值AST有9个基本属性
Vue源码分析(8)--实例分析v--指令 - 图1

2 v-if,v-on,v-pre,v-once,v-for,v-bind

这6个指令会在9个基本属性上添加自己的属性(有的会去掉部分基本属性)
Vue源码分析(8)--实例分析v--指令 - 图2
Vue源码分析(8)--实例分析v--指令 - 图3
Vue源码分析(8)--实例分析v--指令 - 图4
Vue源码分析(8)--实例分析v--指令 - 图5
Vue源码分析(8)--实例分析v--指令 - 图6
Vue源码分析(8)--实例分析v--指令 - 图7

3 v-show,v-model,v-text,v-html,v-cloak

这5个指令都会加上directives,hasBindings两个属性
Vue源码分析(8)--实例分析v--指令 - 图8

Vue源码分析(8)--实例分析v--指令 - 图9
Vue源码分析(8)--实例分析v--指令 - 图10
Vue源码分析(8)--实例分析v--指令 - 图11
Vue源码分析(8)--实例分析v--指令 - 图12
此外,自定义的指令如v-focus等的处理过程与这5个指令类似

4 invokeCreateHooks(vnode, insertedVnodeQueue)

在上一节中提到createElm函数的四步中,第三步其实是很重要的

  1. function invokeCreateHooks (vnode, insertedVnodeQueue) {
  2. for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
  3. cbs.create[i$1](emptyNode, vnode);
  4. }
  5. i = vnode.data.hook; // Reuse variable
  6. if (isDef(i)) {
  7. if (isDef(i.create)) { i.create(emptyNode, vnode); }
  8. if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
  9. }
  10. }

这里的cbs如下:

  1. activate:Array(1)
  2. create:Array(8)
  3. destroy:Array(2)
  4. remove:Array(1)
  5. update:Array(7)
  6. __proto__:Object

其中的create是含有8个函数的数组,这8个函数依次是

  1. //1、处理data对象中的attrs,
  2. function updateAttrs(oldVnode, vnode) {
  3. if (!oldVnode.data.attrs && !vnode.data.attrs) {
  4. return
  5. }
  6. var key, cur, old;
  7. var elm = vnode.elm;
  8. var oldAttrs = oldVnode.data.attrs || {};
  9. var attrs = vnode.data.attrs || {};
  10. // clone observed objects, as the user probably wants to mutate it
  11. if (attrs.__ob__) {
  12. attrs = vnode.data.attrs = extend({}, attrs);
  13. }
  14. for (key in attrs) {
  15. cur = attrs[key];
  16. old = oldAttrs[key];
  17. if (old !== cur) {
  18. setAttr(elm, key, cur);
  19. }
  20. }
  21. // #4391: in IE9, setting type can reset value for input[type=radio]
  22. /* istanbul ignore if */
  23. if (isIE9 && attrs.value !== oldAttrs.value) {
  24. setAttr(elm, 'value', attrs.value);
  25. }
  26. for (key in oldAttrs) {
  27. if (attrs[key] == null) {
  28. if (isXlink(key)) {
  29. elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
  30. } else if (!isEnumeratedAttr(key)) {
  31. elm.removeAttribute(key);
  32. }
  33. }
  34. }
  35. }
  36. //2 处理data对象中的class和staticClass
  37. function updateClass(oldVnode, vnode) {
  38. var el = vnode.elm;
  39. var data = vnode.data;
  40. var oldData = oldVnode.data;
  41. if (!data.staticClass && !data.class && (!oldData || (!oldData.staticClass && !oldData.class))) {
  42. return
  43. }
  44. var cls = genClassForVnode(vnode);
  45. // handle transition classes
  46. var transitionClass = el._transitionClasses;
  47. if (transitionClass) {
  48. cls = concat(cls, stringifyClass(transitionClass));
  49. }
  50. // set the class
  51. if (cls !== el._prevClass) {
  52. el.setAttribute('class', cls);
  53. el._prevClass = cls;
  54. }
  55. }
  56. //3 处理data对象中的on,v-on就是在这儿处理的
  57. function updateDOMListeners(oldVnode, vnode) {
  58. if (!oldVnode.data.on && !vnode.data.on) {
  59. return
  60. }
  61. var on = vnode.data.on || {};
  62. var oldOn = oldVnode.data.on || {};
  63. target$1 = vnode.elm;
  64. normalizeEvents(on);
  65. updateListeners(on, oldOn, add$1, remove$2, vnode.context);
  66. }
  67. //4 处理data对象中的domProps,v-on就是在这儿处理的,v-text和v-html就是在这儿处理
  68. function updateDOMProps(oldVnode, vnode) {
  69. if (!oldVnode.data.domProps && !vnode.data.domProps) {
  70. return
  71. }
  72. var key, cur;
  73. var elm = vnode.elm;
  74. var oldProps = oldVnode.data.domProps || {};
  75. var props = vnode.data.domProps || {};
  76. // clone observed objects, as the user probably wants to mutate it
  77. if (props.__ob__) {
  78. props = vnode.data.domProps = extend({}, props);
  79. }
  80. for (key in oldProps) {
  81. if (props[key] == null) {
  82. elm[key] = '';
  83. }
  84. }
  85. for (key in props) {
  86. cur = props[key];
  87. // ignore children if the node has textContent or innerHTML,
  88. // as these will throw away existing DOM nodes and cause removal errors
  89. // on subsequent patches (#3360)
  90. if (key === 'textContent' || key === 'innerHTML') {
  91. if (vnode.children) {
  92. vnode.children.length = 0;
  93. }
  94. if (cur === oldProps[key]) {
  95. continue
  96. }
  97. }
  98. if (key === 'value') {
  99. // store value as _value as well since
  100. // non-string values will be stringified
  101. elm._value = cur;
  102. // avoid resetting cursor position when value is the same
  103. var strCur = cur == null ? '' : String(cur);
  104. if (shouldUpdateValue(elm, vnode, strCur)) {
  105. elm.value = strCur;
  106. }
  107. } else {
  108. elm[key] = cur;
  109. }
  110. }
  111. }
  112. //5 处理data对象中的style和staticStyle
  113. function updateStyle(oldVnode, vnode) {
  114. var data = vnode.data;
  115. var oldData = oldVnode.data;
  116. if (!data.staticStyle && !data.style && !oldData.staticStyle && !oldData.style) {
  117. return
  118. }
  119. var cur, name;
  120. var el = vnode.elm;
  121. var oldStaticStyle = oldVnode.data.staticStyle;
  122. var oldStyleBinding = oldVnode.data.style || {};
  123. // if static style exists, stylebinding already merged into it when doing normalizeStyleData
  124. var oldStyle = oldStaticStyle || oldStyleBinding;
  125. var style = normalizeStyleBinding(vnode.data.style) || {};
  126. vnode.data.style = style.__ob__ ? extend({}, style) : style;
  127. var newStyle = getStyle(vnode, true);
  128. for (name in oldStyle) {
  129. if (newStyle[name] == null) {
  130. setProp(el, name, '');
  131. }
  132. }
  133. for (name in newStyle) {
  134. cur = newStyle[name];
  135. if (cur !== oldStyle[name]) {
  136. // ie9 setting to null has no effect, must use empty string
  137. setProp(el, name, cur == null ? '' : cur);
  138. }
  139. }
  140. }
  141. //6 处理data对象中的show,与transition有关
  142. function _enter(_, vnode) {
  143. if (!vnode.data.show) {
  144. enter(vnode);
  145. }
  146. }
  147. //7 处理data对象中的ref
  148. create: function create(_, vnode) {
  149. registerRef(vnode);
  150. }
  151. //其中registerRef函数如下:
  152. function registerRef(vnode, isRemoval) {
  153. var key = vnode.data.ref;
  154. if (!key) {
  155. return
  156. }
  157. var vm = vnode.context;
  158. var ref = vnode.componentInstance || vnode.elm;
  159. var refs = vm.$refs;
  160. if (isRemoval) {
  161. if (Array.isArray(refs[key])) {
  162. remove(refs[key], ref);
  163. } else if (refs[key] === ref) {
  164. refs[key] = undefined;
  165. }
  166. } else {
  167. if (vnode.data.refInFor) {
  168. if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {
  169. refs[key].push(ref);
  170. } else {
  171. refs[key] = [ref];
  172. }
  173. } else {
  174. refs[key] = ref;
  175. }
  176. }
  177. }
  178. //8 处理data对象中的directives
  179. function updateDirectives(oldVnode, vnode) {
  180. if (oldVnode.data.directives || vnode.data.directives) {
  181. _update(oldVnode, vnode);
  182. }
  183. }
  184. //其中_updata函数如下:
  185. function _update(oldVnode, vnode) {
  186. var isCreate = oldVnode === emptyNode;
  187. var isDestroy = vnode === emptyNode;
  188. var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
  189. var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context);
  190. var dirsWithInsert = [];
  191. var dirsWithPostpatch = [];
  192. var key, oldDir, dir;
  193. for (key in newDirs) {
  194. oldDir = oldDirs[key];
  195. dir = newDirs[key];
  196. if (!oldDir) {
  197. // new directive, bind
  198. callHook$1(dir, 'bind', vnode, oldVnode);
  199. if (dir.def && dir.def.inserted) {
  200. dirsWithInsert.push(dir);
  201. }
  202. } else {
  203. // existing directive, update
  204. dir.oldValue = oldDir.value;
  205. callHook$1(dir, 'update', vnode, oldVnode);
  206. if (dir.def && dir.def.componentUpdated) {
  207. dirsWithPostpatch.push(dir);
  208. }
  209. }
  210. }
  211. if (dirsWithInsert.length) {
  212. var callInsert = function() {
  213. for (var i = 0; i < dirsWithInsert.length; i++) {
  214. callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
  215. }
  216. };
  217. if (isCreate) {
  218. mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', callInsert);
  219. } else {
  220. callInsert();
  221. }
  222. }
  223. if (dirsWithPostpatch.length) {
  224. mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'postpatch', function() {
  225. for (var i = 0; i < dirsWithPostpatch.length; i++) {
  226. callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
  227. }
  228. });
  229. }
  230. if (!isCreate) {
  231. for (key in oldDirs) {
  232. if (!newDirs[key]) {
  233. // no longer present, unbind
  234. callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
  235. }
  236. }
  237. }
  238. }

patch在createElm调用完后,还调用了invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);这个就是执行指令中的inserted函数

对于内置的指令v-show和v-model,vue已经为其准备好了bind等函数,若要自定义指令,则需自己定义bind,inserted,update,updateComponent,unbind函数
如:

  1. // 注册一个全局自定义指令 v-focus
  2. Vue.directive('focus', {
  3. // 当绑定元素插入到 DOM 中。
  4. inserted: function (el) {
  5. el.focus()
  6. }
  7. })
  8. //若简写成这样
  9. // 注册一个全局自定义指令 v-focus
  10. Vue.directive('focus', function (el) {
  11. el.focus()
  12. })
  13. vue会自动将该函数视为bindupdate函数,此时不会取得focus效果,因为el.focus()执行时机过早,尚未插入父元素,不在文档流中