前言
不得不说,随着越来越多的企业的前端框架选择vue之后,面试也变得越来越卷了,早在几年前,关于vue的面试题还只是v-model的使用、vue的生命周期、数据的变化是怎么检测到的等等一系列表面的问题,现在的情况却变成了,你没了解过vue的源码都不好意思说自己懂vue...
Diff算法
一说起diff算法,相信很多人都不陌生,对两个虚拟dom进行比较嘛,只将数据真正进行变化的地方更新在真实的dom上。其实vue对diff算法的实现就用到了我们的打补丁的 方式,也就是接下来提到的patch(),现在的面试经常会出现的一道面试题便是让我们手写一下patch()的实现过程(简单实现),这时候很多人便会一脸懵,我没写过这个呀,没关系,其实patch()呀,真没我们想的那么的高大上,也不是那么难以理解滴,废话少说,我们代码上见
patch的实现过程
function createElement(vnode){
let tag = vnode.tag //目标元素
let attrs = vnode.attr || {} //属性
let children = vnode.children || [] //子节点
if(!tag){
return null
}
//创建对应dom
let elem = document.createElement(tag)
let attrName
//给dom添加属性
for(attrName in attrs){
if(attrs.haOwnProperty(attrName)){
elem.setAttribute(attrName,attr[attrName])
}
}
//将子元素添加到目标元素
children.forEach(function(childVnode){
elem.appendChild(createElement(childVnode))
})
return elem
}
首先我们定义了一个名为createElement的函数,目的便是将虚拟dom转变为真实的dom,将此函数接收到的参数是我们通过render()函数生成的虚拟dom,我们起个名字叫做vnode。注意:这个render是vue源码中生成虚拟dom的函数,不是我们平时使用的render。我们使用了三个变量:tag来表示我们的目标元素,attrs来表示这个元素下面的属性,children表示这个元素下面还包含的子元素。因为目标元素下面的属性和子节点都有可能为空,所以要加一个“ || ”
接下来就开始判断了,首先,如果不存在tag,就没有往下进行的必要了,直接return一个null,完活。如果存在这个tag我们该怎么操作呢?其实啊就是使用的我们刚开始学习js的时候使用的document.createElement来创建一个真实的节点。节点创建好了,接下来是干嘛呢?没错,就是将下面的属性和子节点都挂上去不就可以了嘛。一个for循环,一个forEach(),大家都不陌生吧,挂载完成之后将这个真实的节点返回出去就完成了。
当属性发生变化时所进行的操作
不知道大家有没有想过一个问题,当数据进行变化的时候,我们的vue是把整个节点全部销毁,然后创建一个全新的节点吗?其实不是这样的,我们的vue是很聪明的,他会比较出两个虚拟dom之间的差异,只将变化的部分进行重新渲染,也就是我们平时经常提到的局部渲染。还是老套路,我们代码上见
function updataChildren(vnode,newVnode){
let children = vnode.children || []
let newChildren = newVnode.children || []
children.forEach(function(childVnode,index){
let newChildrenVnode = newChildren[index]
//第一层没有变化
if(childVnode.tag === newChildrenVnode.tag){
updataChildren(childrenVnode,newChildrenVnode)
}else{
replaceNode(childrenVnode,newChildrenVnode)
}
})
}
我们定义了一个函数来对两个节点进行比较,一个是之前的虚拟dom,我们叫它vdom,一个是新生成的dom,我们叫它newVnode。还是熟悉的代码,还是熟悉的操作,将两个不同的元素下面的children列出来,循环进行比较每一层,如果第一层没有变化,那就进行递归操作,一直向下遍历,直到找到不同的地方,将变化的地方替换掉,到此为止,我们的patch操作就可以告一段落了。
总结
当然了,真正的源码中对patch的实现不可能就这么的简单,我们只创建了子节点,下面的属性两个小部分,而且进行更新的操作也只是简单的进行了实现。其实,面试中能把这些写出来并且给面试官讲清楚,实际上就已经足够了,他也不可能让我们把源码所实现的所有操作都写出来,别说我们了,面试官都不一定能全部写出来。其实啊,还有很多细节我们没去了解,看代码我们就可以看得出来,真实的操作中真的会逐层遍历每个子节点和子元素吗?那性能浪费的也太严重了吧,更高明的就在这里,vue会通过一系列操作,为每个节点中容易发生变化的地方做上记号,这样遍历起来就能更快的找到数据发生更改的地方,这个功能实现有亿点的复杂,这里就不做过多的介绍,感兴趣的朋友们去源码上一探究竟。