我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Vue.js源码分析之自定义指令的示例分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Vue.js源码分析之自定义指令的示例分析

这篇文章给大家分享的是有关Vue.js源码分析之自定义指令的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

前言

除了核心功能默认内置的指令 (v-model 和 v-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元素被渲染出来了,但是并没有插入到父元素中,例如:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title>    <script class="lazy" data-src="vue.js"></script></head><body>    <div id="d"><input type="" name="" v-focus></div>    <script>        Vue.directive('focus', {                   bind:function(el){console.log(el.parentElement);},                      //打印父节点            inserted: function (el) {console.log(el.parentElement);el.focus()}      //打印父节点,并将当前元素处于聚焦状态        })        var app = new Vue({el:"#d"})    </script></body></html>

输出如下:

Vue.js源码分析之自定义指令的示例分析

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

Vue.js源码分析之自定义指令的示例分析

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

源码分析

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

function processAttrs (el) {                     //解析Vue的属性  var list = el.attrsList;   var i, l, name, rawName, value, modifiers, isProp;  for (i = 0, l = list.length; i < l; i++) {             //遍历每个属性     name = rawName = list[i].name;    value = list[i].value;    if (dirRE.test(name)) {                                 //如果该属性以v-、@或:开头,表示这是Vue内部指令      // mark element as dynamic      el.hasBindings = true;      // modifiers      modifiers = parseModifiers(name);      if (modifiers) {        name = name.replace(modifierRE, '');      }      if (bindRE.test(name)) { // v-bind                          //bindRD等于/^:|^v-bind:/ ,即该属性是v-bind指令时              } else if (onRE.test(name)) { // v-on              } else { // normal directives        name = name.replace(dirRE, '');                         //去掉指令前缀,比如v-show执行后等于show        // parse arg        var argMatch = name.match(argRE);        var arg = argMatch && argMatch[1];        if (arg) {          name = name.slice(0, -(arg.length + 1));        }        addDirective(el, name, rawName, value, arg, modifiers); //执行addDirective给el增加一个directives属性        if ("development" !== 'production' && name === 'model') {          checkForAliasModel(el, value);        }      }    } else {          }  }}

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

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

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

Vue.js源码分析之自定义指令的示例分析

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

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

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

var directives = {                 //第6173行 directives模块   create: updateDirectives,             //创建DOM后的钩子  update: updateDirectives,  destroy: function unbindDirectives (vnode) {    updateDirectives(vnode, emptyNode);  }}function updateDirectives (oldVnode, vnode) {         //第6181行   oldVnode:旧的Vnode,更新时才有 vnode:新的VNode  if (oldVnode.data.directives || vnode.data.directives) {    _update(oldVnode, vnode);  }}

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

function _update (oldVnode, vnode) {                 //第6187行 初始化/更新指令  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);                 //调用normalizeDirectives$1()函数规范化参数       var dirsWithInsert = [];  var dirsWithPostpatch = [];  var key, oldDir, dir;  for (key in newDirs) {                                     //遍历newDirs    oldDir = oldDirs[key];                                         //oldVnode上的key指令信息    dir = newDirs[key];                                            //vnode上的key指令信息    if (!oldDir) {                                                 //如果oldDir不存在,即是新增指令      // new directive, bind      callHook$1(dir, 'bind', vnode, oldVnode);                     //调用callHook$1()函数,参数2为bind,即执行v-focus指令的bind函数      if (dir.def && dir.def.inserted) {                            //如果有定义了inserted钩子函数        dirsWithInsert.push(dir);                                     //则保存到dirsWithInsert数组里      }    } else {      // existing directive, update      dir.oldValue = oldDir.value;      callHook$1(dir, 'update', vnode, oldVnode);      if (dir.def && dir.def.componentUpdated) {        dirsWithPostpatch.push(dir);      }    }  }  if (dirsWithInsert.length) {                                    //如果dirsWithInsert存在(即有绑定了inserted钩子函数)    var callInsert = function () {                                  //定义一个callInsert函数,该函数会执行dirsWithInsert里的每个函数      for (var i = 0; i < dirsWithInsert.length; i++) {        callHook$1(dirsWithInsert[i], 'inserted', vnode, oldVnode);         }    };    if (isCreate) {                                                 //如果是初始化        mergeVNodeHook(vnode, 'insert', callInsert);                    //则调用mergeVNodeHook()函数    } else {      callInsert();    }  }  if (dirsWithPostpatch.length) {            mergeVNodeHook(vnode, '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, unbind        callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);      }    }  }}

writer by:大沙漠 QQ:22969969

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

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

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

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

Vue.js源码分析之自定义指令的示例分析

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

感谢各位的阅读!关于“Vue.js源码分析之自定义指令的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Vue.js源码分析之自定义指令的示例分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Vue.js源码分析之自定义指令的示例分析

这篇文章给大家分享的是有关Vue.js源码分析之自定义指令的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令
2023-06-14

docker中资源指标API及自定义指标API的示例分析

这篇文章给大家分享的是有关docker中资源指标API及自定义指标API的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。 以前是用heapster来收集资源指标才能看,现在heapster要废弃了。
2023-06-04

Vue源码分析之虚拟DOM的示例分析

小编给大家分享一下Vue源码分析之虚拟DOM的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!为什么需要虚拟dom?虚拟DOM就是为了解决浏览器性能问题而被
2023-06-15

python源码剖析之PyObject的示例分析

这篇文章主要介绍python源码剖析之PyObject的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、Python中的对象Python中一切皆是对象。————Guido van Rossum(1989)这
2023-06-15

Java源码解析之ConcurrentHashMap的示例分析

小编给大家分享一下Java源码解析之ConcurrentHashMap的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!早期 ConcurrentHashMap,其实现是基于:分离锁,也就是将内部进行分段(Segme
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录