先问大家一个简单的问题:

还有人记得 jquery 里面的 data 方法是如何让 ​​DOM 节点​​绑定对应的​​数据对象​​的吗

有时候我们做​​节点关联​​设计的思路其实有一点类似,但是在 vue 里面多了很多概念,比如:

1、​​vnode​​: 如何生成的,包含子父关系、属性 ​​data​

2、内置的 ​​ref​​ 对象的 ​​create​​ 如何​​注册​

3、​​生命周期​​:解析到根节点之后获取 ​​outerHTML​​ 再一步一步解析​​子元素​



用惯 ​​vue​​ 的人都会很熟悉地:

使用 ref 来注册引用信息,再通过 ​​$refs​​ 对象就可以做关联

但是我们看看它们是​​如何关联上​​的呢?

代码片段来自 ​​2.5.16​​ 版本:

1、需要初始化 ​​$refs​​,默认是一个​​空对象​

我们看到在函数 ​​initLifecycle​​ 上会往 ​​vm​​ 上设置一个 key 为 $refs 值为一个对象


function initLifecycle (vm) {
vm.$refs = {};
}


2、获取​​元素​​上的 ​​ref​​ 值:

在函数 ​​registerRef​​ 上,它接受 ​​2​​ 个参数:

  • vnode
  • isRemoval

function registerRef (vnode, isRemoval) {}


直接通过 ​​vnode.data​​ 获取:


var key = vnode.data.ref;


然后获取 ​​$refs​

在这之前需要获取 ​​vm​​:

从 vnode 上下文 context 获取

var vm = vnode.context;


然后很简单的就是 ​​vm.$refs​


var refs = vm.$refs;


ref 其实是什么呢?

DOM 节点或组件实例

这里的:

  • componentInstance -- 组件实例
  • elm -- DOM 节点

var ref = vnode.componentInstance || vnode.elm;


这里需要处理一下 v-for 一起用的情况,官网也提过:

对应的引用信息是包含 DOM 节点或组件实例的​​数组​

if (vnode.data.refInFor) {}


情况一:如果不是数组格式,​​强制​​转换一下,​​外层套一个数组​

判断方式:​​Array.isArray​


if (!Array.isArray(refs[key])) {
refs[key] = [ref];
}


情况二:看数组里面​​是否存在​​当前这个 ref,如果不存在,​​push​​ 进去


if (refs[key].indexOf(ref) < 0) {
refs[key].push(ref);
}


如果不是和 v-for 一起用:直接设置对象的 key 和 value:


refs[key] = ref;


最后一个问题,官网提到了:

ref 注册时间 -- 因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在

那我们看看:

1、它到底是在什么时机绑定的

2、vnode 是如何产生的

最开始我们从 ​​_init​​ 开始


Vue.prototype._init = function (options) {
// vm.$mount
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}


生成 vnode 最核心的部分:

实例化 VNode


function _createElement (
var vnode;
if (typeof tag === 'string') {
// ...
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
}
}


我们以如下代码为例:


<div id="app">
<img ref="imgbox" src="https://vuejs.org/images/logo.png" alt="Vue logo">
</div>


我们的 VNode 如下:

最外层 app 转换的 vnode:


children:[VNode]
data: {
attrs: {
id: "app"
}
}
tag: "div"


子 vnode 如下:


data: {
ref: "imgbox",
attrs: {
src:"https://vuejs.org/images/logo.png",
alt:"Vue logo"
}
}
tag: "img"


内置了一个 ref 对象,里面有 create 函数,调用了 ​​registerRef​


var ref = {
create: function create (_, vnode) {
registerRef(vnode);
}
}


在函数 ​​invokeCreateHooks​​ 调用 ​​create​

注意两点:

1、​​cbs​​ 是什么?

2、​​create​​又是什么,和 ​​ref​​ 对象的 ​​create​​ 有什么关系


function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
}


后面会提到:hooks


var hooks = ['create', 'activate', 'update', 'remove', 'destroy'];


核心部分:​​createPatchFunction​​,往 ​​cbs​​ 里面放置对应的函数


function createPatchFunction (backend) {
var cbs = {};

var modules = backend.modules;

for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = [];
for (j = 0; j < modules.length; ++j) {
// ...
cbs[hooks[i]].push(modules[j][hooks[i]]);
}
}
}


那谁调用了 ​​createPatchFunction​​ 函数呢:


var modules = platformModules.concat(baseModules);

var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });


我们发现 ​​baseModules​​ 关联了 ​​ref​


var baseModules = [
ref,
directives
]