当前位置: 首页 > news >正文

常州网站建设市场高清免费观看电视网站

常州网站建设市场,高清免费观看电视网站,建设外贸网站注意什么,汕头市建设局网站Keep Moving 时至今日,前端对于知识的考量是越来越有水平了,逼格高大上了 各类框架大家已经可以说无论是工作还是日常中都已经或多或少的使用过了 曾经听说很多人被问到过虚拟DOM和DOM-diff算法是如何实现的,有没有研究过? 想…

Keep Moving

时至今日,前端对于知识的考量是越来越有水平了,逼格高大上了

各类框架大家已经可以说无论是工作还是日常中都已经或多或少的使用过了

曾经听说很多人被问到过虚拟DOM和DOM-diff算法是如何实现的,有没有研究过?

想必问出此问题的也是高手高手之高高手了,很多人都半开玩笑的说:“面试造航母,工作拧螺丝”

那么,话不多说了,今天就让我们也来一起研究研究这个东东

好饭不怕晚,沉淀下来收收心!我们虽然走的慢,但是却从未停下脚步

神奇的虚拟DOM

首先神奇不神奇的我们先不去关注,先来简单说说何为虚拟DOM

虚拟DOM简而言之就是,用JS去按照DOM结构来实现的树形结构对象,你也可以叫做DOM对象

好了,一句话就把这么伟大的东西给解释了,那么不再耽误时间了,赶紧进入主环节吧

当然,这里还有整个项目的地址方便查看

实现一下虚拟DOM

在亲自上阵之前,我们让粮草先行,先发个图,来看一下整个目录结构是什么样子的

 

这个目录结构是用create-react-app脚手架直接生成的,也是为了方便编译调试

 

// 全局安装
npm i create-react-app -g
// 生成项目
create-react-app dom-diff
// 进入项目目录
cd dom-diff
// 编译
npm run start
复制代码

现在我们开始正式写吧,从创建虚拟DOM及渲染DOM起步吧

创建虚拟DOM

在element.js文件中要实现如何创建虚拟DOM以及将创建出来的虚拟DOM渲染成真实的DOM

首先实现一下如何创建虚拟DOM,看代码:

// element.js// 虚拟DOM元素的类,构建实例对象,用来描述DOM
class Element {constructor(type, props, children) {this.type = type;this.props = props;this.children = children;}
}
// 创建虚拟DOM,返回虚拟节点(object)
function createElement(type, props, children) {return new Element(type, props, children);
}export {Element,createElement
}
复制代码

写好了方法,我们就从index.js文件入手来看看是否成功吧

调用createElement方法

在主入口文件里,我们主要做的操作就是来创建一个DOM对象,渲染DOM以及通过diff后去打补丁更新DOM,不啰嗦了,直接看代码:

// index.js// 首先引入对应的方法来创建虚拟DOM
import { createElement } from './element';let virtualDom = createElement('ul', {class: 'list'}, [createElement('li', {class: 'item'}, ['周杰伦']),createElement('li', {class: 'item'}, ['林俊杰']),createElement('li', {class: 'item'}, ['王力宏'])
]);console.log(virtualDom);
复制代码

createElement方法也是vue和react用来创建虚拟DOM的方法,我们也叫这个名字,方便记忆。接收三个参数,分别是typepropschildren

  • 参数分析
    • type: 指定元素的标签类型,如'li', 'div', 'a'等
    • props: 表示指定元素身上的属性,如class, style, 自定义属性等
    • children: 表示指定元素是否有子节点,参数以数组的形式传入

下面来看一下打印出来的虚拟DOM,如下图

 

到目前为止,已经轻而易举的实现了创建虚拟DOM。那么,接下来进行下一步,将其渲染为真实的DOM,别犹豫,继续回到element.js文件中

 

渲染虚拟DOM

// element.jsclass Element {// 省略
}function createElement() {// 省略
}// render方法可以将虚拟DOM转化成真实DOM
function render(domObj) {// 根据type类型来创建对应的元素let el = document.createElement(domObj.type);// 再去遍历props属性对象,然后给创建的元素el设置属性for (let key in domObj.props) {// 设置属性的方法setAttr(el, key, domObj.props[key]);}// 遍历子节点// 如果是虚拟DOM,就继续递归渲染// 不是就代表是文本节点,直接创建domObj.children.forEach(child => {child = (child instanceof Element) ? render(child) : document.createTextNode(child);// 添加到对应元素内el.appendChild(child);});return el;
}// 设置属性
function setAttr(node, key, value) {switch(key) {case 'value':// node是一个input或者textarea就直接设置其value即可if (node.tagName.toLowerCase() === 'input' ||node.tagName.toLowerCase() === 'textarea') {node.value = value;} else {node.setAttribute(key, value);}break;case 'style':// 直接赋值行内样式node.style.cssText = value;break;default:node.setAttribute(key, value);break;}
}// 将元素插入到页面内
function renderDom(el, target) {target.appendChild(el);
}export {Element,createElement,render,setAttr,renderDom
};
复制代码

既然写完了,那就赶快来看看成果吧

调用render方法

再次回到index.js文件中,修改为如下代码

// index.js// 引入createElement、render和renderDom方法
import { createElement, render, renderDom } from './element';let virtualDom = createElement('ul', {class: 'list'}, [createElement('li', {class: 'item'}, ['周杰伦']),createElement('li', {class: 'item'}, ['林俊杰']),createElement('li', {class: 'item'}, ['王力宏'])
]);console.log(virtualDom);// +++
let el = render(virtualDom); // 渲染虚拟DOM得到真实的DOM结构
console.log(el);
// 直接将DOM添加到页面内
renderDom(el, document.getElementById('root'));
// +++
复制代码

通过调用render方法转为真实DOM,并调用renderDom方法直接将DOM添加到了页面内

下图为打印后的结果:

 

截止目前,我们已经实现了虚拟DOM并进行了渲染真实DOM到页面中。那么接下来我们就有请DOM-diff隆重登场,来看一下这大有来头的diff算法是如何发光发热的吧!

 

DOM-diff闪亮登场

说到DOM-diff那一定要清楚其存在的意义,给定任意两棵树,采用先序深度优先遍历的算法找到最少的转换步骤

DOM-diff比较两个虚拟DOM的区别,也就是在比较两个对象的区别。

作用: 根据两个虚拟对象创建出补丁,描述改变的内容,将这个补丁用来更新DOM

已经了解到DOM-diff是干嘛的了,那就没什么好说的了,继续往下写吧

// diff.jsfunction diff(oldTree, newTree) {// 声明变量patches用来存放补丁的对象let patches = {};// 第一次比较应该是树的第0个索引let index = 0;// 递归树 比较后的结果放到补丁里walk(oldTree, newTree, index, patches);return patches;
}function walk(oldNode, newNode, index, patches) {// 每个元素都有一个补丁let current = [];if (!newNode) { // rule1current.push({ type: 'REMOVE', index });} else if (isString(oldNode) && isString(newNode)) {// 判断文本是否一致if (oldNode !== newNode) {current.push({ type: 'TEXT', text: newNode });}} else if (oldNode.type === newNode.type) {// 比较属性是否有更改let attr = diffAttr(oldNode.props, newNode.props);if (Object.keys(attr).length > 0) {current.push({ type: 'ATTR', attr });}// 如果有子节点,遍历子节点diffChildren(oldNode.children, newNode.children, patches);} else {    // 说明节点被替换了current.push({ type: 'REPLACE', newNode});}// 当前元素确实有补丁存在if (current.length) {// 将元素和补丁对应起来,放到大补丁包中patches[index] = current;}
}function isString(obj) {return typeof obj === 'string';
}function diffAttr(oldAttrs, newAttrs) {let patch = {};// 判断老的属性中和新的属性的关系for (let key in oldAttrs) {if (oldAttrs[key] !== newAttrs[key]) {patch[key] = newAttrs[key]; // 有可能还是undefined}}for (let key in newAttrs) {// 老节点没有新节点的属性if (!oldAttrs.hasOwnProperty(key)) {patch[key] = newAttrs[key];}}return patch;
}// 所有都基于一个序号来实现
let num = 0;function diffChildren(oldChildren, newChildren, patches) {// 比较老的第一个和新的第一个oldChildren.forEach((child, index) => {walk(child, newChildren[index], ++num, patches);});
}// 默认导出
export default diff;
复制代码

代码虽然又臭又长,但是这些代码就让我们实现了diff算法了,所以大家先不要盲动,不要盲动,且听风吟,让我一一道来

比较规则

  • 新的DOM节点不存在{type: 'REMOVE', index}
  • 文本的变化{type: 'TEXT', text: 1}
  • 当节点类型相同时,去看一下属性是否相同,产生一个属性的补丁包{type: 'ATTR', attr: {class: 'list-group'}}
  • 节点类型不相同,直接采用替换模式{type: 'REPLACE', newNode}

根据这些规则,我们再来看一下diff代码中的walk方法这位关键先生

walk方法都做了什么?

  • 每个元素都有一个补丁,所以需要创建一个放当前补丁的数组
  • 如果没有new节点的话,就直接将type为REMOVE的类型放到当前补丁里
    if (!newNode) {current.push({ type: 'REMOVE', index });}
复制代码
  • 如果新老节点是文本的话,判断一下文本是否一致,再指定类型TEXT并把新节点放到当前补丁
    else if (isString(oldNode) && isString(newNode)) {if (oldNode !== newNode) {current.push({ type: 'TEXT', text: newNode });}}
复制代码
  • 如果新老节点的类型相同,那么就来比较一下他们的属性props
    • 属性比较
      • diffAttr
        • 去比较新老Attr是否相同
        • 把newAttr的键值对赋给patch对象上并返回此对象
    • 然后如果有子节点的话就再比较一下子节点的不同,再调一次walk
      • diffChildren
        • 遍历oldChildren,然后递归调用walk再通过child和newChildren[index]去diff
    else if (oldNode.type === newNode.type) {// 比较属性是否有更改let attr = diffAttr(oldNode.props, newNode.props);if (Object.keys(attr).length > 0) {current.push({ type: 'ATTR', attr });}// 如果有子节点,遍历子节点diffChildren(oldNode.children, newNode.children, patches);}
复制代码
  • 上面三个如果都没有发生的话,那就表示节点单纯的被替换了,type为REPLACE,直接用newNode替换即可
    else {current.push({ type: 'REPLACE', newNode});}
复制代码
  • 当前补丁里确实有值的情况,就将对应的补丁放进大补丁包里
    if (current.length > 0) {// 将元素和补丁对应起来,放到大补丁包中patches[index] = current;}
复制代码

以上就是关于diff算法的分析过程了,没太明白的话没关系,再反复看几遍试试,意外总是不期而遇的

diff已经完事了,那么最后一步就是大家所熟知的打补丁

补丁要怎么打?那么让久违的patch出来吧

patch补丁更新

打补丁需要传入两个参数,一个是要打补丁的元素,另一个就是所要打的补丁了,那么直接看代码

import { Element, render, setAttr } from './element';let allPatches;
let index = 0;  // 默认哪个需要打补丁function patch(node, patches) {allPatches = patches;// 给某个元素打补丁walk(node);
}function walk(node) {let current = allPatches[index++];let childNodes = node.childNodes;// 先序深度,继续遍历递归子节点childNodes.forEach(child => walk(child));if (current) {doPatch(node, current); // 打上补丁}
}function doPatch(node, patches) {// 遍历所有打过的补丁patches.forEach(patch => {switch (patch.type) {case 'ATTR':for (let key in patch.attr) {let value = patch.attr[key];if (value) {setAttr(node, key, value);} else {node.removeAttribute(key);}}break;case 'TEXT':node.textContent = patch.text;break;case 'REPLACE':let newNode = patch.newNode;newNode = (newNode instanceof Element) ? render(newNode) : document.createTextNode(newNode);node.parentNode.replaceChild(newNode, node);break;case 'REMOVE':node.parentNode.removeChild(node);break;default:break;}});
}export default patch;
复制代码

看完代码还需要再来简单的分析一下

patch做了什么?

  • 用一个变量来得到传递过来的所有补丁allPatches
  • patch方法接收两个参数(node, patches)
    • 在方法内部调用walk方法,给某个元素打上补丁
  • walk方法里获取所有的子节点
    • 给子节点也进行先序深度优先遍历,递归walk
    • 如果当前的补丁是存在的,那么就对其打补丁(doPatch)
  • doPatch打补丁方法会根据传递的patches进行遍历
    • 判断补丁的类型来进行不同的操作
      1. 属性ATTR for in去遍历attrs对象,当前的key值如果存在,就直接设置属性setAttr; 如果不存在对应的key值那就直接删除这个key键的属性

      2. 文字TEXT 直接将补丁的text赋值给node节点的textContent即可

      3. 替换REPLACE 新节点替换老节点,需要先判断新节点是不是Element的实例,是的话调用render方法渲染新节点;

        不是的话就表明新节点是个文本节点,直接创建一个文本节点就OK了。

        之后再通过调用父级parentNode的replaceChild方法替换为新的节点

      4. 删除REMOVE 直接调用父级的removeChild方法删除该节点

  • 将patch方法默认导出方便调用

好了,一切都安静下来了。让我们回归index.js文件中,去调用一下diff和patch这两个重要方法,看看奇迹会不会发生吧

回归

// index.jsimport { createElement, render, renderDom } from './element';
// +++ 引入diff和patch方法
import diff from './diff';
import patch from './patch';
// +++let virtualDom = createElement('ul', {class: 'list'}, [createElement('li', {class: 'item'}, ['周杰伦']),createElement('li', {class: 'item'}, ['林俊杰']),createElement('li', {class: 'item'}, ['王力宏'])    
]);let el = render(virtualDom);
renderDom(el, window.root);// +++
// 创建另一个新的虚拟DOM
let virtualDom2 = createElement('ul', {class: 'list-group'}, [createElement('li', {class: 'item active'}, ['七里香']),createElement('li', {class: 'item'}, ['一千年以后']),createElement('li', {class: 'item'}, ['需要人陪'])    
]);
// diff一下两个不同的虚拟DOM
let patches = diff(virtualDom, virtualDom2);
console.log(patches);
// 将变化打补丁,更新到el
patch(el, patches);
// +++
复制代码

将修改后的代码保存,会在浏览器里看到DOM被更新了,如下图

 

到这里就finish了,内容有些多,可能不是很好的消耗,不过没关系,就让我用最后几句话来总结一下实现的整个过程吧

 

四句话

我们来梳理一下整个DOM-diff的过程:

  1. 用JS对象模拟DOM(虚拟DOM)
  2. 把此虚拟DOM转成真实DOM并插入页面中(render)
  3. 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  4. 把差异对象应用到真正的DOM树上(patch)

行了,就这四句话吧,说多了就有点画蛇添足了。好久没有写文章了,很感谢小伙伴们的观看,辛苦各位了,886

参考:

虚拟DOM和原生谁更快?

DOM-Diff算法学习


作者:chenhongdong
链接:https://juejin.im/post/5c8e5e4951882545c109ae9c
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

http://www.lbrq.cn/news/2533177.html

相关文章:

  • 中企动力 网站建设 眼镜巨量引擎广告投放平台官网
  • 个人建网站需要什么手续查询网站
  • 哪里需要人做钓鱼网站网络游戏推广
  • 宁乡电商网站建设报价在线识别图片
  • 绵阳网站建设怎么做十大骗子教育培训机构
  • 泰安最好网站建设公司it教育培训机构排名
  • 室内设计培训网站重庆seo报价
  • 广州海珠做网站的公司互联网营销师报名费
  • 网站定制建设湘潭网站seo磐石网络
  • vs2010 c 网站开发接单平台app
  • 电子商务系统 网站建设电商网课
  • 阿里云wordpress在哪里设置巢湖seo推广
  • 无休网站建设搜索引擎最佳化
  • 的建站公司百度图像搜索
  • 上海做网站那家公司好手机端关键词排名免费软件
  • 建设网站需要备案萧山seo
  • 网站开发工程师待遇网站seo优化免费
  • 成都网站建设名录互联网营销策略有哪些
  • 网站建设和维护采购协议书指定关键词排名优化
  • 大学生做家教靠谱网站网络广告推广平台
  • 商业网站设计方案商品推广软文范例100字
  • 滨州网站建设哪家好徐州seo企业
  • 怎样免费建企业网站电商网站大全
  • java网站开发设计百度竞价推广专员
  • 网站建设核心技术创新点百度引流推广怎么做
  • 团购网站建设案例资源优化网站排名
  • 响水做网站的价格百度导航最新版本免费下载
  • 海口网站建设优化案例微信软文模板
  • 秦皇岛网站建设费用惠州seo计费
  • 接网站开发的公司电话seo视频
  • systmctl的作用,使用场景和用法
  • 推客小程序商业模型设计:合规分佣体系×盈利模式×LTV提升策略
  • 脚手架搭建React项目
  • 企业微信API接口发消息实战:从0到1的技术突破之旅
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现裂缝的检测识别(C#代码UI界面版)
  • [硬件电路-106]:模拟电路 - 电路为什么会出现不同的频率特性?元件频率依赖性、信号传输路径、电路拓扑结构、外部因素