经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue.js 源码分析(二十三) 高级应用 自定义指令详解
来源:cnblogs  作者:大沙漠  时间:2019/7/12 9:18:12  对本文有异议

除了核心功能默认内置的指令 (v-modelv-show),Vue 也允许注册自定义指令。

官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染、插入到父节点、更新、解绑时可以执行一些特定的操作(钩子函数()

自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指令,局部自定义指令只能在当前Vue实例内使用

自定义指令可以绑定如下钩子函数:

    ·bind                      ;只调用一次,元素渲染成DOM节点后,执行directives模块的初始化工作时调用,在这里可以进行一次性的初始化设置。
    ·inserted               ;被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
    ·update              ;所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
    ·componentUpdated       ;指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    ·unbind              ;只调用一次,指令与元素解绑时调用。

每个钩子函数可以有四个参数,分别是el(对应的DOM节点引用)、binding(一些关于指令的扩展信息,是个对象)、vnode(该节点对应的虚拟VN哦的)和oldVnode(之前的VNode,仅在update和componentUpdated钩子中可用)

bind钩子函数执行的时候该DOM元素被渲染出来了,但是并没有插入到父元素中,例如:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="d"><input type="" name="" v-focus></div>
  10. <script>
  11. Vue.directive('focus', {
  12. bind:function(el){console.log(el.parentElement);},    //打印父节点
  13. inserted: function (el) {console.log(el.parentElement);el.focus()}    //打印父节点,并将当前元素处于聚焦状态
  14. })
  15. var app = new Vue({el:"#d"})
  16. </script>
  17. </body>
  18. </html>

输出如下:

可以看到input元素自动获得焦点了,控制台输出如下:

可以看到对于bind()钩子来说,它的父节点是获取不到的,因为Vue内部会在执行bind()钩子后才会将当前元素插入到父元素的子节点里

 

源码分析


 

在解析模板将DOM转换成AST对象的时候会执行processAttrs()函数,如下:

  1. function processAttrs (el) { //解析Vue的属性
  2. var list = el.attrsList;
  3. var i, l, name, rawName, value, modifiers, isProp;
  4. for (i = 0, l = list.length; i < l; i++) { //遍历每个属性
  5. name = rawName = list[i].name;
  6. value = list[i].value;
  7. if (dirRE.test(name)) { //如果该属性以v-、@或:开头,表示这是Vue内部指令
  8. // mark element as dynamic
  9. el.hasBindings = true;
  10. // modifiers
  11. modifiers = parseModifiers(name);
  12. if (modifiers) {
  13. name = name.replace(modifierRE, '');
  14. }
  15. if (bindRE.test(name)) { // v-bind //bindRD等于/^:|^v-bind:/ ,即该属性是v-bind指令时
  16. /*v-bind的分支*/
  17. } else if (onRE.test(name)) { // v-on
  18. /*v-on的分支*/
  19. } else { // normal directives
  20. name = name.replace(dirRE, ''); //去掉指令前缀,比如v-show执行后等于show
  21. // parse arg
  22. var argMatch = name.match(argRE);
  23. var arg = argMatch && argMatch[1];
  24. if (arg) {
  25. name = name.slice(0, -(arg.length + 1));
  26. }
  27. addDirective(el, name, rawName, value, arg, modifiers); //执行addDirective给el增加一个directives属性
  28. if ("development" !== 'production' && name === 'model') {
  29. checkForAliasModel(el, value);
  30. }
  31. }
  32. } else {
  33. /*非Vue指令的分支*/
  34. }
  35. }
  36. }

addDirective会给AST对象上增加一个directives属性保存指令信息,如下:

  1. function addDirective ( //第6561行 指令相关,给el这个AST对象增加一个directives属性,值为该指令的信息
  2. el,
  3. name,
  4. rawName,
  5. value,
  6. arg,
  7. modifiers
  8. ) {
  9. (el.directives || (el.directives = [])).push({ name: name, rawName: rawName, value: value, arg: arg, modifiers: modifiers });
  10. el.plain = false;
  11. }

例子里的p元素执行到这里时对应的AST对象如下:

接下来在generate生成rendre函数的时候,会执行genDirectives()函数,将AST转换成一个render函数,如下:

  1. with(this){return _c('div',{attrs:{"id":"d"}},[_c('input',{directives:[{name:"focus",rawName:"v-focus"}],attrs:{"type":"","name":""}})])}

最后等渲染完成后会执行directives模块的create钩子函数,如下:

  1. var directives = { //第6173行 directives模块
  2. create: updateDirectives, //创建DOM后的钩子
  3. update: updateDirectives,
  4. destroy: function unbindDirectives (vnode) {
  5. updateDirectives(vnode, emptyNode);
  6. }
  7. }
  8. function updateDirectives (oldVnode, vnode) { //第6181行 oldVnode:旧的Vnode,更新时才有 vnode:新的VNode
  9. if (oldVnode.data.directives || vnode.data.directives) {
  10. _update(oldVnode, vnode);
  11. }
  12. }

_updat 就是处理指令初始化和更新的,如下:

 

  1. function _update (oldVnode, vnode) { //第6187行 初始化/更新指令
  2. var isCreate = oldVnode === emptyNode; //是否为初始化
  3. var isDestroy = vnode === emptyNode;
  4. var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
  5. var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); //调用normalizeDirectives$1()函数规范化参数
  6. var dirsWithInsert = [];
  7. var dirsWithPostpatch = [];
  8. var key, oldDir, dir;
  9. for (key in newDirs) { //遍历newDirs
  10. oldDir = oldDirs[key]; //oldVnode上的key指令信息
  11. dir = newDirs[key]; //vnode上的key指令信息
  12. if (!oldDir) { //如果oldDir不存在,即是新增指令
  13. // new directive, bind
  14. callHook$1(dir, 'bind', vnode, oldVnode); //调用callHook$1()函数,参数2为bind,即执行v-focus指令的bind函数
  15. if (dir.def && dir.def.inserted) { //如果有定义了inserted钩子函数
  16. dirsWithInsert.push(dir); //则保存到dirsWithInsert数组里
  17. }
  18. } else {
  19. // existing directive, update
  20. dir.oldValue = oldDir.value;
  21. callHook$1(dir, 'update', vnode, oldVnode);
  22. if (dir.def && dir.def.componentUpdated) {
  23. dirsWithPostpatch.push(dir);
  24. }
  25. }
  26. }
  27. if (dirsWithInsert.length) { //如果dirsWithInsert存在(即有绑定了inserted钩子函数)
  28. var callInsert = function () { //定义一个callInsert函数,该函数会执行dirsWithInsert里的每个函数
  29. for (var i = 0; i < dirsWithInsert.length; i++) {
  30. callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);
  31. }
  32. };
  33. if (isCreate) { //如果是初始化
  34. mergeVNodeHook(vnode, 'insert', callInsert); //则调用mergeVNodeHook()函数
  35. } else {
  36. callInsert();
  37. }
  38. }
  39. if (dirsWithPostpatch.length) {
  40. mergeVNodeHook(vnode, 'postpatch', function () {
  41. for (var i = 0; i < dirsWithPostpatch.length; i++) {
  42. callHook$1(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode);
  43. }
  44. });
  45. }
  46. if (!isCreate) {
  47. for (key in oldDirs) {
  48. if (!newDirs[key]) {
  49. // no longer present, unbind
  50. callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
  51. }
  52. }
  53. }
  54. }

 

对于bind钩子函数来说是直接执行了,而对于inserted钩子函数则是把函数保存到dirsWithInsert数组里,再定义了一个callInsert函数,该函数内部通过作用域访问dirsWithInsert变量,并遍历该变量依次执行每个inserted钩子函数

 

mergeVNodeHook()钩子函数的作用是把insert作为一个hooks属性保存到对应的Vnode的data上面,当该Vnode插入到父节点后会调用该hooks,如下:

 

  1. function mergeVNodeHook (def, hookKey, hook) { //第2074行 合并VNode的钩子函数 def:一个VNode hookKey:(事件名,比如:insert) hook:回调函数
  2. if (def instanceof VNode) { //如果def是一个VNode
  3. def = def.data.hook || (def.data.hook = {}); //则将它重置为VNode.data.hook,如果VNode.data.hook不存在则初始化为一个空对象 注:普通节点VNode.data.hook是不存在的。
  4. }
  5. var invoker;
  6. var oldHook = def[hookKey];
  7. function wrappedHook () {
  8. hook.apply(this, arguments); //先执行hook函数
  9. // important: remove merged hook to ensure it's called only once
  10. // and prevent memory leak
  11. remove(invoker.fns, wrappedHook); //然后把wrappedHook从invoker.fns里remove掉,以且包只执行一次
  12. }
  13. if (isUndef(oldHook)) { //如果oldHook不存在,即之前没有定义hookKey这个钩子函数
  14. // no existing hook
  15. invoker = createFnInvoker([wrappedHook]); //直接调用createFnInvoker()返回一个闭包函数,参数为执行的回调函数
  16. } else {
  17. /* istanbul ignore if */
  18. if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
  19. // already a merged invoker
  20. invoker = oldHook;
  21. invoker.fns.push(wrappedHook);
  22. } else {
  23. // existing plain hook
  24. invoker = createFnInvoker([oldHook, wrappedHook]);
  25. }
  26. }
  27. invoker.merged = true;
  28. def[hookKey] = invoker; //设置def的hookKey属性指向新的invoker
  29. }

createFnInvoker就是v-on指令对应的那个函数,用到了同一个API,执行完后,我们就把invoker插入到input对应的VNode.data.hook里了,如下:

最后等到该VNode插入到父节点后就会执行invokeCreateHooks()函数,该函数会遍历VNode.hook.insert,依次执行每个函数,也就执行到我们自定义定义的inserted钩子函数了。

原文链接:http://www.cnblogs.com/greatdesert/p/11171785.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号