博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面试官:你了解 vue 的diff算法吗?
阅读量:6587 次
发布时间:2019-06-24

本文共 5715 字,大约阅读时间需要 19 分钟。

写在前面

vue2.0加入了virtual dom,有点向react靠拢的意思。vue的diff位于文件中,复杂度为O(n)。 听大神说了解diff过程可以让我们更高效的使用框架,工作和女朋友都更加好找了,我们赶快了解哈~。 了解diff过程,我们先从虚拟dom开始。

虚拟dom

所谓的virtual dom,也就是虚拟节点。它通过JS的Object对象模拟DOM中的节点,然后再通过特定的render方法将其渲染成真实的DOM节点 dom diff 则是通过JS层面的计算,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染, 上一张图让大家更加清晰点:

到这里有童鞋可能会问,模拟DOM是干嘛为什么要这样做?虚拟dom对应的是真实dom, 使用document.CreateElement 和 document.CreateTextNode创建的就是真实节点。

我们可以做个试验。打印出一个空元素的第一层属性,可以看到标准让元素实现的东西太多了。如果每次都重新生成新的元素,对性能是巨大的浪费。

var odiv = document.createElement('div');for(var k in odiv ){  console.log(k)}复制代码

看看你的打印台,有你想要的结果。

实现步骤

  • 用JavaScript对象模拟DOM
  • 把此虚拟DOM转成真实DOM并插入页面中
  • 如果有事件发生修改了虚拟DOM
  • 比较两棵虚拟DOM树的差异,得到差异对象
  • 把差异对象应用到真正的DOM树上

代码实现

class crtateElement {    constructor (el, attr, child) {        this.el = el        this.attrs = attr        this.child = child || []    }    render () {         let virtualDOM =  document.createElement(this.el)        // attr是个对象所以要遍历渲染        for (var attr in this.attrs) {            virtualDOM.setAttribute(attr, this.attrs[attr])        }        // 深度遍历child        this.child.forEach(el => {            console.log(el instanceof crtateElement)            //如果子节点是一个元素的话,就调用它的render方法创建子节点的真实DOM,如果是一个字符串的话,创建一个文件节点就可以了            // 判断一个对象是否是某个对象的实力            let childElement = (el instanceof crtateElement) ? el.render() : document.createTextNode(el);            virtualDOM.appendChild(childElement);        });        return virtualDOM    }}function element (el, attr, child) {    return new crtateElement(el, attr, child)}module.exports = element复制代码

用JavaScript对象结构表示DOM树的结构;然后用这个树构建一个真正的DOM树,插到文档当中

let element = require('./element') let myobj = {    "class": 'big_div'}let ul = element('div',myobj,[    '我是文字',    element('div',{
'id': 'xiao'},['1']), element('div',{
'id': 'xiao1'},['2']), element('div',{
'id': 'xiao2'},['3']),])console.log(ul)ul = ul.render()document.body.appendChild(ul)复制代码

DOM DIFF

比较两棵DOM树的差异是Virtual DOM算法最核心的部分.简单的说就是新旧虚拟dom 的比较,如果有差异就以新的为准,然后再插入的真实的dom中,重新渲染。、 借网络一张图片说明:

比较只会在同层级进行, 不会跨层级比较。

比较后会出现四种情况:
1、此节点是否被移除 -> 添加新的节点
2、属性是否被改变 -> 旧属性改为新属性
3、文本内容被改变-> 旧内容改为新内容
4、节点要被整个替换 -> 结构完全不相同 移除整个替换

看diff.js 的简单代码实现,下面都有相应的解释说明

let utils = require('./utils');let keyIndex = 0;function diff(oldTree, newTree) {    //记录差异的空对象。key就是老节点在原来虚拟DOM树中的序号,值就是一个差异对象数组    let patches = {};    keyIndex = 0;  // 儿子要起另外一个标识    let index = 0; // 父亲的表示 1 儿子的标识就是1.1 1.2    walk(oldTree, newTree, index, patches);    return patches;}//遍历function walk(oldNode, newNode, index, patches) {    let currentPatches = [];//这个数组里记录了所有的oldNode的变化    if (!newNode) {//如果新节点没有了,则认为此节点被删除了        currentPatches.push({ type: utils.REMOVE, index });        //如果说老节点的新的节点都是文本节点的话    } else if (utils.isString(oldNode) && utils.isString(newNode)) {        //如果新的字符符值和旧的不一样        if (oldNode != newNode) {            ///文本改变             currentPatches.push({ type: utils.TEXT, content: newNode });        }    } else if (oldNode.tagName == newNode.tagName) {        //比较新旧元素的属性对象        let attrsPatch = diffAttr(oldNode.attrs, newNode.attrs);        //如果新旧元素有差异 的属性的话        if (Object.keys(attrsPatch).length > 0) {            //添加到差异数组中去            currentPatches.push({ type: utils.ATTRS, attrs: attrsPatch });        }        //自己比完后再比自己的儿子们        diffChildren(oldNode.children, newNode.children, index, patches, currentPatches);    } else {        currentPatches.push({ type: utils.REPLACE, node: newNode });    }    if (currentPatches.length > 0) {      patches[index] = currentPatches;    }}//老的节点的儿子们 新节点的儿子们 父节点的序号 完整补丁对象 当前旧节点的补丁对象function diffChildren(oldChildren, newChildren, index, patches, currentPatches) {    oldChildren.forEach((child, idx) => {        walk(child, newChildren[idx], ++keyIndex, patches);    });}function diffAttr(oldAttrs, newAttrs) {    let attrsPatch = {};    for (let attr in oldAttrs) {        //如果说老的属性和新属性不一样。一种是值改变 ,一种是属性被删除 了        if (oldAttrs[attr] != newAttrs[attr]) {            attrsPatch[attr] = newAttrs[attr];        }    }    for (let attr in newAttrs) {        if (!oldAttrs.hasOwnProperty(attr)) {            attrsPatch[attr] = newAttrs[attr];        }    }    return attrsPatch;}module.exports = diff;复制代码

其中有个需要注意的是新旧虚拟dom比较的时候,是先同层比较,同层比较完看看时候有儿子,有则需要继续比较下去,直到没有儿子。搞个简单的图来说明一下吧:

同层比较,比较顺序是上面的数字来,把不同的打上标记,放到数组里面去,统一交给patch处理。

patch.js的简单实现

let keyIndex = 0;let utils = require('./utils');let allPatches;//这里就是完整的补丁包function patch(root, patches) {    allPatches = patches;    walk(root);}function walk(node) {    let currentPatches = allPatches[keyIndex++];    (node.childNodes || []).forEach(child => walk(child));    if (currentPatches) {        doPatch(node, currentPatches);    }}function doPatch(node, currentPatches) {    currentPatches.forEach(patch => {        switch (patch.type) {            case utils.ATTRS:                for (let attr in patch.attrs) {                    let value = patch.attrs[attr];                    if (value) {                        utils.setAttr(node, attr, value);                    } else {                        node.removeAttribute(attr);                    }                }                break;            case utils.TEXT:                node.textContent = patch.content;                break;            case utils.REPLACE:                let newNode = (patch.node instanceof Element) ? path.node.render() : document.createTextNode(path.node);                node.parentNode.replaceChild(newNode, node);                break;            case utils.REMOVE:                node.parentNode.removeChild(node);                break;        }    });}module.exports = patch;复制代码

说明改天有空补上,欢迎留言。diff部分还有一个key的设置,它可以更高效的利用dom。这个也很重要。时间不早了,改天写。你的点赞是我不断的动力。感谢,欢迎抛砖~~

转载于:https://juejin.im/post/5ad6182df265da23906c8627

你可能感兴趣的文章
ASPNetCore MVC ModelValidation-ajax
查看>>
【java虚拟机序列】java中的垃圾回收与内存分配策略
查看>>
png图片 透明区域如何 让其不响应鼠标事件?
查看>>
Lua学习笔记(1)
查看>>
BarTender怎么打印公式化的三列标签
查看>>
数据结构顺序字符串
查看>>
《算法导论》读书笔记之第10章 基本数据结构之二叉树
查看>>
C#结构体
查看>>
SVN四部曲之SVN设置详解深入
查看>>
MS-SQL的智能脚本智能提示失效丢失
查看>>
JS回调函数--简单易懂有实例
查看>>
C语言基础:for循环演示源码,字符循环和浮点数循环
查看>>
迈克菲实验室:Flame病毒的深度分析
查看>>
移动互联网初创型团队需要什么样的云计算服务?
查看>>
ssh信任
查看>>
【转载】用备份进行Active Directory的灾难重建:Active Directory系列之三
查看>>
nginx+lua+redis实现post请求接口之黑名单(一)
查看>>
MySQL之MHA架构的介绍
查看>>
MongoDB基本使用
查看>>
使用路由器配置DHCP
查看>>