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

v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  7. </head>
  8. <body>
  9. <div id="d"><p v-show="isShow">Hello Vue!</p></div>
  10. <script>
  11. Vue.config.productionTip=false;
  12. Vue.config.devtools=false;
  13. var app = new Vue({el:'#d',data:{isShow:true}})
  14. </script>
  15. </body>
  16. </html>

渲染结果为:

当我们在修改isShow为false时:

页面里的Hello Vue!就隐藏部件了,我们查看DOM结构如下:

 

可以看到Vue是通过修改display这个CSS属性来隐藏元素的

 

源码分析


在解析模板将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('p',{directives:[{name:"show",rawName:"v-show",value:(isShow),expression:"isShow"}]},[_v("Hello Vue!")])])}

最后等渲染完成后会执行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. }
  13. function _update (oldVnode, vnode) { //第6187行 初始化/更新指令
  14. var isCreate = oldVnode === emptyNode; //是否为初始化
  15. var isDestroy = vnode === emptyNode;
  16. var oldDirs = normalizeDirectives$1(oldVnode.data.directives, oldVnode.context);
  17. var newDirs = normalizeDirectives$1(vnode.data.directives, vnode.context); //调用normalizeDirectives$1()函数规范化参数1,返回格式:{v-show:{name: "show", rawName: "v-show", value: true, expression: "ok", modifiers: {…}, …}}
  18. var dirsWithInsert = [];
  19. var dirsWithPostpatch = [];
  20. var key, oldDir, dir;
  21. for (key in newDirs) { //遍历newDirs
  22. oldDir = oldDirs[key]; //oldVnode上的key指令信息
  23. dir = newDirs[key]; //vnode上的key指令信息
  24. if (!oldDir) { //如果oldDir不存在,即是新增指令
  25. // new directive, bind
  26. callHook$1(dir, 'bind', vnode, oldVnode); //调用callHook$1()函数,参数2为bind,即执行v-show模块的bint函数
  27. if (dir.def && dir.def.inserted) {
  28. dirsWithInsert.push(dir);
  29. }
  30. } else {
  31. // existing directive, update
  32. dir.oldValue = oldDir.value;
  33. callHook$1(dir, 'update', vnode, oldVnode);
  34. if (dir.def && dir.def.componentUpdated) {
  35. dirsWithPostpatch.push(dir);
  36. }
  37. }
  38. }
  39. /*以下略*/
  40. }

  normalizeDirectives$1会调用resolveAsset()函数从Vue.options.directives里获取v-show指令的信息如下:

  1. function normalizeDirectives$1 ( //第6249行 规范化dirs
  2. dirs,
  3. vm
  4. ) {
  5. var res = Object.create(null); //存储最后的结果
  6. if (!dirs) { //如果用户没有定义指令,则直接返回空对象
  7. // $flow-disable-line
  8. return res
  9. }
  10. var i, dir;
  11. for (i = 0; i < dirs.length; i++) { ///遍历dirs
  12. dir = dirs[i];
  13. if (!dir.modifiers) { //如果没有修饰符,则重置dir.modifiers为空对象
  14. // $flow-disable-line
  15. dir.modifiers = emptyModifiers;
  16. }
  17. res[getRawDirName(dir)] = dir; //将dir保存到res里面,键名为原始的指令名
  18. dir.def = resolveAsset(vm.$options, 'directives', dir.name, true); //调用resolveAsset获取该指令的信息,是一个对象,保存到res的def属性里面
  19. }
  20. // $flow-disable-line
  21. return res
  22. }

resolveAsset是获取资源用的,当我们定义了组件、过滤器、指令时,都通过该函数获取对应的信息,之前组件和过滤里介绍了,这里不说了

回到_update函数,最后调用callHook$1()函数,参数2为bind,该函数如下:

  1. function callHook$1 (dir, hook, vnode, oldVnode, isDestroy) { //第6276行 执行指令的某个回调函数 dir:指令信息,
  2. var fn = dir.def && dir.def[hook]; //尝试获取钩子函数
  3. if (fn) {
  4. try {
  5. fn(vnode.elm, dir, vnode, oldVnode, isDestroy); //执行钩子函数,参数依次为绑定的元素、dir对象、新的VNode,老的VNode
  6. } catch (e) {
  7. handleError(e, vnode.context, ("directive " + (dir.name) + " " + hook + " hook"));
  8. }
  9. }
  10. }

v-show指令的信息如下:

  1. var show = { //第8082行 v-show指令的信息
  2. bind: function bind (el, ref, vnode) { //初次绑定时执行
  3. var value = ref.value;
  4. vnode = locateNode(vnode);
  5. var transition$$1 = vnode.data && vnode.data.transition; //尝试获取transition,如果v-show绑定的标签外层套了一个transition则会把信息保存到该对象里 这是transition的组件分支,可先忽略
  6. var originalDisplay = el.__vOriginalDisplay = //保存最初的display属性
  7. el.style.display === 'none' ? '' : el.style.display;
  8. if (value && transition$$1) { //如果transition$$1存在的话
  9. vnode.data.show = true;
  10. enter(vnode, function () {
  11. el.style.display = originalDisplay;
  12. });
  13. } else {
  14. el.style.display = value ? originalDisplay : 'none'; //否则直接根据value的值是否可以转换为1来设置el.style.display属性
  15. }
  16. },
  17. update: function update (el, ref, vnode) {
  18. /*更新时的逻辑*/
  19. },
  20. unbind: function unbind (
  21. el,
  22. binding,
  23. vnode,
  24. oldVnode,
  25. isDestroy
  26. ) {
  27. /*卸载时的逻辑*/
  28. }
  29. }

v-show的流程就是这样的,注意,v-show不支持<template>元素,也不支持v-else。

原文链接:http://www.cnblogs.com/greatdesert/p/11157771.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号