一、在没有vue
和react
以前,咱们都使用的是原生的javascript
或jq
,咱们都是经过操做dom
,来达到视图更新的效果。(操做dom->视图更新)
javascript
二、而在vue
中,咱们只需更改data
中的数据,视图就会更新,因此在vue
中,简单说就是数据改变,让视图更新。vue
可咱们有没有想过为何数据改变,视图就更新呢?其实vue
更新视图,也仍是操做了dom
,只不过是在vue
或react
框架内部来完成的。(数据改变->操做dom->视图更新)java
但这就引出了一个问题,难道vue
每次数据更改,都会操做dom
呢,显然这不是最优解,因此就推出了虚拟dom
这个东西。node
什么是虚拟dom
?虚拟dom
就是用js
来模拟dom
结构。react
举个栗子:git
<div id="app">
<h1>认识虚拟dom</h1>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>
复制代码
以上例子中的dom结构,若是用js来表达是什么样的呢?github
用js来表达dom,他会先声明一个对象,而后有三个属性:面试
tag : 元素;
props: 属性或者事件;
children: 子节点或者内容;
{ tag: 'div',
props: { id: 'app', className: 'container' },
children: [
{ tag: 'h1', children: '认识虚拟dom' },
{ tag: 'ul', props: {}, children: [
{ tag: 'li', props: '1', },
{ tag: 'li', props: '2', },
{ tag: 'li', props: '3', },
]
}
]
}
复制代码
这样一来,咱们就用js来表达了dom结构。算法
那咱们要怎么样用虚拟dom来计算咱们的变动,来操做真实的dom呢?数组
扯了这么多,其实就是为了让咱们知道虚拟dom,那到底虚拟dom有什么好处呢?它好在什么地方呢?别着急,这就来。
在这以前,先介绍一个东西。Snabbdom
,vue
里面的虚拟dom就是借助它来完成的
在官方github
上,咱们能够看到他下面的例子
主要的目的,就是经过以前那个js对象结构来表示页面的一些节点,来插入到他的空的容器里。
var container = document.getElementById('container')
复制代码
他里面是用vnode
这个变量来表示的,那个vnode
的结构,就是以前所说的js dom
结构。而后表示出来的节点以后,会经过一个叫Patch
的方法,将虚拟节点vnode塞到某一个容器里。
咱们能够照着它这个来写一个小demo,认识snabbdom
和vnode
的好处:
一、先引入snabbdom
这个库
const snabbdom = window.snabbdom;
复制代码
在这以前,偷了一下懒,直接cnd
引入了
<script src="https://lib.baomitu.com/snabbdom/0.7.4/h.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-class.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-eventlisteners.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-props.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom-style.js"></script>
<script src="https://lib.baomitu.com/snabbdom/0.7.4/snabbdom.js"></script>
复制代码
二、将vnode(虚拟节点)塞到容器中
const patch = snabbdom.init({ classModule, propsModule, styleModule, eventListenersModule,})
复制代码
三、建立vnode,也就是虚拟dom,(引入h函数,经过h函数来建立)
const h = snabbdom.h
复制代码
四、在页面上声明一个空的容器,而后在js选中这个容器
<div id="container"></div>//一个空的容器
const container = document.getElementById('container')
复制代码
五、建立vnode
let vnode = h("ul#list", {}, [ h('li.item', {}, '第一个'), h('li.item', {}, '第二个'), h('li.item', {}, '第三个'),])
复制代码
六、使用patch函数,将vnode塞到容器中,第一个参数:容器。第二个参数:虚拟节点
patch(container, vnode)
复制代码
七、能够看到已经将那几个元素插入空的container里了
若是我须要点击一个按钮,来改变第二个的内容,须要怎么作呢?
八、先在页面上添加一个button
,而后js选中他,添加click监听事件
<button id="btn">点击</button>const btn = document.getElementById('btn')btn.addEventListener('click', ()=> { const newVnode = h('ul#list', {}, [ h('li.item', {}, '第一个'), h('li.item', {}, '第二个,我变了'), h('li.item', {}, '第三个'), h('li.item', {}, '第四个'), ]) patch(vnode, newVnode) vnode = newVnode})
复制代码
值得注意的是,点击以后,需传入一个新的vnode
,而后在建立完vnode
以后,再执行patch函数,用新的vnode
来更新老的vnode
,同时将新的vnode
赋值vnode
,方便下次用最新结果比较。
点击以前:
点击以后:
能够看到第二个和第四个已经变了,由于第一个和第三个是没变了,因此他并无从新渲染那两个节点。
结论:因此到这咱们就知道了,虚拟dom会去计算你有没有变化,从而去决定要不要去操做你那个dom
以前说过,虚拟dom
就是计算节点是否变动修改,来判断是否操做dom
元素来更新视图。
那么这个计算,就是用的diff
算法计算的。因此diff
算法是虚拟dom
的核心。
咱们用虚拟dom来表述真实的dom,这样作的目的,就是为了计算最小的变化,根据这个最小的变化,来更新真实的dom结构。
如上图,有两个虚拟dom,咱们既要用diff算法,来比较更新咱们的虚拟dom。那么它会怎么作呢?
遍历旧的虚拟dom
遍历新的虚拟dom
从新排序
好比上图,有一个改变,和一个新增,那么他会根据这个变化,来从新编排这个虚拟dom。
but……,没错,可恶的可是来了,若是咱们只是为了改一小部分,但是节点数又很是多,有1000个节点,那么他就会计算1000^3,也就是10亿次,显然这不是咱们的初衷也是不可接受的。
因此vue
和react
内部里作diff
算法的时候,内部是作过优化的,咱们能够来see see他们是怎么作的优化。
本来的diff
算法比较,是先遍历旧的vnode
,而后遍历新的vnode
,而后对比,最后再从新编排。
而vue
和react
的是,
只比较同一层级,不作跨级比较。
比较标签名,若是标签名不一样,直接删除,不会继续深度比较。
标签名相同,key相同,就认为是相同节点,不继续深度比较。(因此你们知道咱们写v-for循环的时候,为啥提示要加上key了吧)
因此经过以上的步骤,vue将diff算法给优化了一波
如今若是有1000个节点,就只需计算1000次了,简直nice。以下图:
咱们能够看到snabbdom/src/package/h.ts
咱们是经过h函数来生产vnode的,能够看到这个h方法,能接受好几种状况的传参,
再看github上官方的栗子:
能够看到,这里传了sel
data
children
,最后经过
return vnode(sel, data, children, text, undefined)
复制代码
来返回。咱们执行这个h函数后,而后他往vnode里面传递参数,包括sel, data, children, text, undefined
,咱们能够进一步看看vnode
函数的代码:路径:snabbdom/src/package/vnode.ts
能够看到这个vnode
函数,接受了一堆参数,那堆参数就是上面传递进来的参数。
最终,这个函数会返回一个对象,这个对象就是表示节点的对象。
咱们来看下这个节点对象,须要的组成部分:
sel
: 选择器,好比div
data
: 好比style
onClick
children
: children
和text
只能有一个,要么是children
数组包含子元素,要么text
字符串内容
elm
: 对应的真实的dom
元素,好比将new vnode
替换掉old vnode
,那个old vnode
就是elm
key
: v-for
所需声明的那个属性
执行完后,会将这些参数用对象的形式返回回去,返回到h函数那里,因此咱们执行了h函数以后,就会生成对应的vnode
结构,而后咱们就把vnode
结构,保存在变量中使用。
咱们今天所学的虚拟dom和diff算法,主要围绕snabbdom来学习虚拟dom,它主要的一些核心方法有这几个:vnode h patch,若是你们掌握了这些,面试的时候若是有虚拟dom的问题,也应该内心有底了。还有patch函数中的diff算法,还有那个key,你们也应该知道v-for的时候为何要有了。
最后若是你们有什么好的建议或想法,也欢迎交流。