这篇文章分析Vue实例怎么通过$refs 访问到dom元素的。通过上一篇的例子来进行分析:

<html><head>
<style type="text/css"></style>
</head><body><script src="./vue.js"></script>
<div id="app"><div id="div1Id" ref="div1Ref" style="margin-bottom: 20px">div 1 test</div><button @click="addBorder()">add border for div1</button>
</div><script>const app = new Vue({el: "#app",methods : {addBorder : function(){//document.getElementById("div1Id").style.border = "5px solid red";console.log("div1 is: " + this.$refs.div1Ref.outerHTML);this.$refs.div1Ref.style.border = "5px solid blue";}}
});</script></body>
</html>
<div id="app"><div id="div1Id" ref="div1Ref" style="margin-bottom: 20px">div 1 test</div><button @click="addBorder()">add border for div1</button>
</div>

通过Vue模板编译系统生成的render函数如下:

with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',{ref:"div1Ref",staticStyle:{"margin-bottom":"20px"},attrs:{"id":"div1Id"}},[_v("div 1 test")]),_v(" "),_c('button',{on:{"click":function($event){return addBorder()}}},[_v("add border for div1")])])}

我们看到ref = "div1Ref" 被编译到了render函数中的data参数中,我们在createElement 加个打印看看这个data:

  function createElement (context,tag,data,children,normalizationType,alwaysNormalize) {if (Array.isArray(data) || isPrimitive(data)) {normalizationType = children;children = data;data = undefined;}if (isTrue(alwaysNormalize)) {normalizationType = ALWAYS_NORMALIZE;}console.log("liubbc data is: " + JSON.stringify(data));return _createElement(context, tag, data, children, normalizationType)}

打印如下:

liubbc data is: {"ref":"div1Ref","staticStyle":{"margin-bottom":"20px"},"attrs":{"id":"div1Id"}}

接下来就是创建VNode,并把data参数赋值给VNode,代码如下:

  function _createElement (context,tag,data,children,normalizationType) {//some codevnode = new VNode(tag, data, children,undefined, undefined, context);//some code}var VNode = function VNode (tag,data,children,text,elm,context,componentOptions,asyncFactory) {this.tag = tag;this.data = data;//some code}

我们省略了一些代码,但还是能清晰的看到给VNode 的data属性赋值过程。对ref = "div1Ref"的处理就到这里了,那么怎么通过this.$refs.div1Ref就能访问到div呢?我们在全局vue.js中搜索$refs,发现只有两处:

  function initLifecycle (vm) {var options = vm.$options;// locate first non-abstract parentvar parent = options.parent;if (parent && !options.abstract) {while (parent.$options.abstract && parent.$parent) {parent = parent.$parent;}parent.$children.push(vm);}vm.$parent = parent;vm.$root = parent ? parent.$root : vm;vm.$children = [];vm.$refs = {};vm._watcher = null;vm._inactive = null;vm._directInactive = false;vm._isMounted = false;vm._isDestroyed = false;vm._isBeingDestroyed = false;}function registerRef (vnode, isRemoval) {var key = vnode.data.ref;console.log("liubbc registerRef key: " + key);if (!isDef(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] = [ref];} else if (refs[key].indexOf(ref) < 0) {// $flow-disable-linerefs[key].push(ref);}} else {refs[key] = ref;}}}

从上面代码看到,第一处initLifecycle函数中 vm.$refs = {};  这行代码就让vue实例拥有了一个$refs对象。从第二处registerRef函数名中就看到这是注册$refs的地方。我们先分析一下这个函数。其实这个函数还是蛮简单的,我们看一下几处关键代码:

var key = vnode.data.ref;  //之前分析过,vnode的data为{"ref":"div1Ref","staticStyle": //{"margin-bottom":"20px"},"attrs":{"id":"div1Id"}}var ref = vnode.componentInstance || vnode.elm;  //如果是组件实例,ref则指向组件实例,否则指向//elm, elm就是div dom元素
var refs = vm.$refs;        //refs 指向vue实例$refs对象refs[key] = ref;      // vue实例$refs对象的div1Ref 属性赋值为div dom元素

到这里基本上我们也就明白了this.$refs.div1Ref 能取得div dom元素的过程了,但还有一点需要澄清,就是谁调用的registerRef函数。还是在vue.js中全局搜索registerRef,又加了点打印,发现在这个地方调用了这个函数:

  var ref = {create: function create (_, vnode) {console.log("liubbc ref create");registerRef(vnode);},update: function update (oldVnode, vnode) {console.log("liubbc ref update");if (oldVnode.data.ref !== vnode.data.ref) {registerRef(oldVnode, true);registerRef(vnode);}},destroy: function destroy (vnode) {console.log("liubbc ref destroy");registerRef(vnode, true);}};

从这个ref对象中的create,update,destroy 属性来看,都调用了registerRef函数。凭经验应该从create函数中来找,那么谁又调用了create函数呢?

    function invokeCreateHooks (vnode, insertedVnodeQueue) {for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode);    //在这里调用了create函数}i = vnode.data.hook; // Reuse variableif (isDef(i)) {if (isDef(i.create)) {i.create(emptyNode, vnode); }if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }}}function createElm (vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index) {//some codevar data = vnode.data;  //vnode.data 之前分析过里面有ref属性//some codevnode.elm = vnode.ns? nodeOps.createElementNS(vnode.ns, tag): nodeOps.createElement(tag, vnode);        //创建了真实的div dom //some code{createChildren(vnode, children, insertedVnodeQueue);if (isDef(data)) {    invokeCreateHooks(vnode, insertedVnodeQueue);   //这里注册create hooks}insert(parentElm, vnode.elm, refElm);}

对关键代码进行了注释。在创建真实dom过程中做了invoke create hooks操作。在invokeCreateHooks函数中的cbs又是什么呢?

   var hooks = ['create', 'activate', 'update', 'remove', 'destroy']; //定义了这么多hooksfunction createPatchFunction (backend) {var i, j;var cbs = {};  //invokeCreateHooks  访问了cbs,所以形成了闭包var modules = backend.modules;var nodeOps = backend.nodeOps;for (i = 0; i < hooks.length; ++i) {cbs[hooks[i]] = [];    // 在这里注册了create  hookfor (j = 0; j < modules.length; ++j) {if (isDef(modules[j][hooks[i]])) {cbs[hooks[i]].push(modules[j][hooks[i]]);  //往create hook 里面push 钩子函数}}}//some code function invokeCreateHooks (vnode, insertedVnodeQueue) {for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, vnode);    // 遍历调用cbs中的create 钩子}}}

上面代码就是注册create  hooks过程,我们看看往create hook 里面push的钩子函数有哪些?是否有var ref中的create钩子函数?

  var baseModules = [ref,    //这里就是ref 对象directives];var platformModules = [attrs,klass,events,domProps,style,transition];/*  */// the directive module should be applied last, after all// built-in modules have been applied.var modules = platformModules.concat(baseModules);var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); //modules中//含有ref对象

从上面我们看到其实在加载vue.js的时候都把var ref中的create 钩子函数注册进cbs了。在创建真实dom过程中会检测vnode中的data属性是否为空,如果不为空就invokeCreateHooks  调用create 钩子函数,在create钩子函数中给$refs对象创建名为data中ref属性值的属性div1Ref,并使div1Ref属性值指向真实的div dom元素。

不知道写清楚没有,如哪里没看明白,请留言。

Vue $refs 原理相关推荐

  1. 深入了解Vue 2响应式原理,并手写一个简单的Vue

    1. Vue 2的响应式原理 Vue.js 一个核心思想是数据驱动.所谓数据驱动是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据.vue.js里面只需要改变数据,Vue ...

  2. 造轮子之Vue实现原理,几十行代码实现Vue

    Vue.js框架的原理其实很简单,就是利用了Object.defineProperty()方法进行了数据劫持,重写里面的get和set方法,数据发生变动,相应的set()方法变回响应,进而拿到更新的数 ...

  3. 前端知识点总结——VUE

    转载自:http://www.bslxx.com/m/view.php?aid=1799 1.框架和库的区别: 框架:framework 有着自己的语法特点.都有对应的各个模块 库 library 专 ...

  4. 前端面试题--vue

    文章目录 1. vue 生命周期 2. VUE 中 computed 和 watch 的区别是什么? 3. 动态绑定 类名和样式 4. v-if 与 v-show 的区别 5. v-for 列表渲染 ...

  5. git原理及常见使用方法

    Git 原理入门-来自阮一峰 Git 是最流行的版本管理工具,也是程序员的必备技能之一. 即使天天使用它,很多人也未必了解它的原理.Git 为什么可以管理版本?git add.git commit这些 ...

  6. Rocksdb 写流程,读流程,WAL文件,MANIFEST文件,ColumnFamily,Memtable,SST文件原理详解

    文章目录 前言 Rocksdb写流程图 WAL 原理分析 概述 文件格式 查看WAL的工具 创建WAL 清理WAL MANIFEST原理分析 概述 查看MANIFEST的工具 创建 及 清除 MANI ...

  7. Git详解之九 Git内部原理

    以下内容转载自:http://www.open-open.com/lib/view/open1328070620202.html Git 内部原理 不管你是从前面的章节直接跳到了本章,还是读完了其余各 ...

  8. Bundle Adjustment原理及应用(附实战代码)

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 虽然现在的轮子很多,但我们在使用过程中会碰到很多问题,而我们经常不知道从哪里下手,说明轮子不是你造的你 ...

  9. Gerrit 代码审核服务器的工作流和原理

    2019独角兽企业重金招聘Python工程师标准>>> Gerrit 代码审核服务器的工作流和原理 谷歌 Android 开源项目在 Git 的使用上有两个重要的创新,一个是为多版本 ...

最新文章

  1. 异地多活实践与设计思考点归纳
  2. 机器学习如何彻底改变游戏中的物理模拟
  3. 深js, jsconf China 回顾
  4. 如何在 ASP.Net Core 中使用 Consul 来存储配置
  5. 手机来电秀怎么开启_360手机卫士怎么设置来电秀 360手机卫士来电秀设置方法...
  6. 使用Fiddler对android应用抓包 专题
  7. 参数幂等性校验失败_快速入手 Spring Boot 参数校验
  8. js对象赋值只保留存在的属性_js对象的创建对象模式和继承模式(上)---构建对象模式...
  9. 云服务器一般选什么系统,云服务器一般选择什么系统好
  10. 微课|中学生可以这样学Python(2.3.1节):基本输入输出函数
  11. 【白皮书】中国高端制造投融资白皮书.pdf(附下载链接)
  12. java 比较器类_高级编程之(Java常用类(Java比较器))
  13. mysql union 别名报错_MySQL中UNION和UNION ALL的使用
  14. SOA平台之争:Java EE,还是.NET……
  15. mysql linux 表名区分大小写吗_MySQL在linux下的表名如何不区分大小写
  16. 华为路由器支持ftp服务器,如何配置华为路由器的FTP
  17. amd显卡测试帧数显示软件,NVIDIA发布帧数显示及显卡基准测试应用FrameView
  18. 局域网中的几大分类,包含以太网,FDDI网,令牌环网,ATM网
  19. Modelsim与ISE联和仿真错误
  20. from __future__ imports must occur at the beginning of the file问题的解决

热门文章

  1. 帮助IT人解决颈肩周炎
  2. dB、dBm、dBi、dBc,一文带你学习无线信号强度指标
  3. 面向对象软件开发实战
  4. 建网站并不难,只需6个步骤,就能做出一个网站
  5. 元素匹配jquery中filter、find、children、contents、contains区别
  6. CSS去除元素点击时出现的小竖杠
  7. 检测图片是否含有二维码
  8. photoneo实例-PhoXi 3D Scanner L 机床上下料
  9. 【Axure RP9 制作轮播图】
  10. PHP日常试题1.1