V-for就地复用原理
举个:
<div v-for="(item,index) in items"> <input /> <button @click="del(index)">delthis</button> {{item.message}}</div>
JS局部
//data外面的itemsitems: [ { message: "1" }, { message: "2" }, { message: "3" }, { message: "4" },],//methods中的del办法del(index) { this.items.splice(index, 1); //依据传入的index删掉items中对应数据},
成果如下
能够发现:
- 当删掉items中的第二个对象时,输入框中的值还是2--这意味着没有删除对应的第二个节点。这是因为vue采纳虚构DOM+diff算法导致的数据凌乱。
vue监听到items数组中少了个元素后,会更新虚构DOM,而后应用diff算法比拟新、旧DOM树,在这个过程中,因为要计算出实在DOM树的最小变更规模,因而会尽可能复用已有的节点(如果节点类型雷同)
此处,咱们的需要当然是不复用节点,那该如何实现呢?
:key解决v-for导致的数据凌乱
- 在渲染列表时,为每个元素绑定举世无双的key,这样,vue在更新经v-for渲染过的列表时,因为key值不同,会认为是不同的节点类型,不采取复用。这样就防止了数据凌乱
为什么不能应用数组下标作为key:
不能应用各元素的index作为key,因为当新增或删除列表中元素时,各项索引都会变,也就是说索引对应元素变了,失去了标识的唯一性
申明式渲染
Vue 提供一套基于 HTML 的模板语法,容许开发者申明式地将实在 DOM 与 Vue 实例的数据绑定在一起。
"申明式" 的意思就是: 只须要指出指标, 而不必关怀如何实现,将实现交由vue解决
虚构DOM
Vdom(virtual dom),能够看作是一个应用javascript模仿了DOM构造的树形构造
- 其中Vnode节点对应实在DOM节点
Vdom树用于缓存实在DOM树的所有信息
为什么要采纳虚构DOM?
所有为了性能。
“间接操作 DOM 性能差”,这是因为 ——
- DOM 引擎、JS 引擎互相独立,但又工作在同一线程(主线程),因而JS 代码调用DOM API时必须挂起 JS 引擎、激活 DOM 引擎,实现后再转换到 JS 引擎
- 引擎间切换的代价会迅速积攒
- 强制重排的DOM API调用,哪怕只改变一个节点,也会引起整个DOM树重排,从新计算布局、从新绘制图像会引起更大的性能耗费
所以,升高引擎切换频率(缩小DOM操作次数)、减小 DOM 变更规模才是DOM 性能优化的两个关键点。
虚构 DOM +diff算法是一种可选的解决方案
基本思路:“在 JS 中缓存必要数据,计算界面更新时的数据差别,只提交最终差集”。
- 虚构dom只用于缓存,而
diff算法负责--
- 计算出‘虚构dom和目前实在DOM之间的数据差别’
- 提交最终差集
留神:“单纯VDOM是进步不了性能的,VDOM次要作用在于它的二次形象提供了一个diff/patch和batch commit(批量提交)的机会”
watcher的节流成果:借助watcher响应式原理,使数据异步更新(滞后更新),可能实现节流成果,在一段时间内,容许屡次更新虚构DOM,而后一次性patch到实在DOM树。像是应用精灵图以缩小申请次数那样,达到优化性能的目标。
vue在监听到数据变动后,会将依赖该数据的watcher退出微工作队列,因为微工作是异步的,因而所有同步更新数据的操作,都会及时地在微工作队列中的工作更新前触发watcher响应,换个说法:执行第一次变动后的每次变动都会更新watcher中的各项依赖。这样的话,在该微工作执行结束之前的这段时间,就相当于节流中的时延了
Vdom的Diff算法
diff算法的两个外围:
- 两个雷同的组件产生相似的DOM构造,不同的组件产生不同的DOM构造。
- 同一层级的一组节点,他们能够通过惟一的key进行辨别。
diff算法的复杂度
- 比拟两棵虚构DOM树的差别是Virtual DOM算法最外围的局部,这也是所谓的 VirtualDOM的diff 算法。两个树的齐全的diff 算法是一个工夫复杂度为O(n^3)的问题。
然而在前端当中,你很少会逾越层级地挪动DOM元素。所diff算法只会对同一个层级的元素进行比照。上面的div只会和同一层级的div比照,第二层级的只会跟第二层级比照。这样算法复杂度就能够达到O(n)。
比拟时是否复用的逻辑
当页面的数据发生变化时,Diff算法只会比拟同一层级的节点:
- 如果节点类型不同,间接干掉旧的节点,创立并插入新的那个节点,不会再比拟这个节点当前的子节点了。
如果节点类型雷同,则会间接复用该节点,从新设置该节点的属性,从而实现节点的更新。
当某一层有很多雷同的节点时,也就是列表节点时,Diff算法的更新过程默认状况下也是遵循以上准则。
比方--咱们心愿能够在B和C之间加一个F
Diff算法默认执行起来是这样的:
- 老的Vdom树的该层上有6个节点,新的Vdom树上有7个类型雷同的节点,那么就顺次复用实在DOM树该层上的对应的前6个节点,在最初再新建一个节点,赋予之前节点E的属性。
- 即把C更新成F,D更新成C,E更新成D,最初再插入E,是不是很没有效率?
所以咱们须要应用key来给每个节点做一个惟一标识,这样vue会把他们当做是不同的节点,因而不会复用,diff算法会间接创立新的节点,并插入正确的地位
key的作用
- key的作用次要是为了高效的更新虚构DOM。
- 也可防止间接复用v-for进去的节点,防止数据凌乱
- 另外vue中在应用雷同标签名元素的过渡切换时,也会应用到key属性,其目标也是为了让vue能够辨别它们,否则vue只会替换其外部属性而不会触发过渡成果。
patch到实在DOM
模仿实现
如何将vnode(右边)变成实在的DOM元素(左边)
实现如下:
let nodes = { tag: "ul", attrs: { id: "list", }, children: [ { tag: "li", attrs: { class: "item", }, children: ["Item 1"], }, ],};//实现办法:递归遍历function createElement(vnode) { var tag = vnode.tag; var attrs = vnode.attrs || {}; var children = vnode.children || []; if (!tag) { return null; } var elem = document.createElement(tag); var attrName; for (attrName in attrs) { if (attrs.hasOwnProperty(attrName)) { elem.setAttribute(attrName, attrs[attrName]); } } for (let i = 0; i < children.length; i++) { let childVnode = children[i]; if (typeof childVnode === "object" || childVnode.constructor === Object ) { elem.appendChild(createElement(childVnode)); } else { let text = document.createTextNode(childVnode); elem.appendChild(text); break; } } return elem;}let elem = createElement(nodes);console.log(elem);
PS:
vue 在patch时,在一个update 办法外面调用createElment()办法,通过虚构节点创立实在的 DOM 并插入到它的父节点中;
相当于打补丁到实在DOM
最初,举个栗子梳理一下:
让 Vue 将name
的数据和<p>
标签绑定在一起:
<p>Hello {{ name }}</p>
让咱们梳理一下vue对这个节点p和数据所做的所有
- Vue 会把这些模板编译成一个渲染函数render。
- 该函数被调用后会渲染并且返回一个虚构的 DOM 树. 这个 "树" 的职责就是形容以后视图应处的状态。
- 之后再通过一个Patch 函数,计算和旧虚构dom树的差集,并通过打补丁的形式将差集中的虚构节点更新到实在 DOM树。
- 在整个过程中, Vue 借助数据劫持和订阅者模式实现监听状态、依赖收集、依赖追踪告诉变动等。 会侦测在渲染过程中所依赖到的数据起源,以实现双向绑定,自动更新状态。
参考:
理解一下v-for原理
Vue2.0 v-for 中 :key 到底有什么用?
Vue--patch | 学Vue 看这个就够了
https://www.zhihu.com/question/324992717/answer/690011952
vue考点 —— Diff算法
既然用 virtual dom 能够进步性能,为什么浏览器不间接自带这个性能呢?--水歌 | 知乎
你不晓得的React 和 Vue 的20个区别【面试必备】
vue diff算法 patch
diff算法中的概念