Virtual DOM

前端发展至今,从直接的DOM操作到MVC设计,然后到MVP,再到如今的MVVM设计模式,其方向都是朝着更高效、更方便、易维护的角度去发展,极大的方便了我们开发过程。
但是,我们需要知道的,目前所有的MV*的设计模式,总的来说仅仅改变了一件事情:提高开发效率。其根本仍旧是DOM操作。对于DOM操作所产生的开销,大家可以看这篇文章。简单来说,操作DOM会产生页面重绘和回流,开销就花在这个上面。

当然,我们要清楚一点是,这里我们优化DOM开销的任何手段都不及浏览器的一次优化升级所产生的效益大,其他优化也是一样。

为了优化DOM元素运行的效率,前端出现了Virtual DOM(虚拟DOM)。当然,Virtual DOM 也仅仅是减少DOM操作,不可能完全做到不操作DOM。

为什么要有Virtual DOM

前面我们简单的说了,为了解决DOM操作所带来的效率问题而设计了Virtual DOM。这里我们通过一段代码来直观的感受下解决了什么问题。

<ul id='root'>
  <!-- 遍历list -->
  <li h-for='value in list'>{{value}}</li>
</ul>
// 伪代码
let viewModel = new VM({
  $el: document.getElementById('root'),
  data: {
    list: [
      {value: 1},
      {value: 2},
      {value: 3}
    ]
  }
})

这里我模仿Vue的语法来写了一段伪代码,约定了一个以h开头的Directive(指令),虽然实际不可能会运行。

通过遍历list数组,来显示3个li元素。这个很容易理解。但是当我此时list增加一个{value: 4}的元素:

list: [
  {value: 1},
  {value: 2},
  {value: 3},
  {value: 4}
]

此时,在一般MVVM框架中,页面中会重新渲染DOM,更新View,显示4个li元素。

但是,在这个过程中, 我们只是希望向ul元素中的尾部添加一个li元素就可以了,没必要再去重新渲染list数据,因为这样会导致前面3个没有改变的数组元素也会重新渲染,这样无疑产生了一次性能浪费。

所以,此时Virtual DOM就是为了解决这一个问题,不去渲染未改变的数据,而是仅仅通过某种算法,去检查哪个数据需要添加或者删除,这样就减少了DOM操作,也就提升了性能。

获取差异化DOM的思路

前面我们提到了,我们只需要通过对比变化前的数据变化后的数据来获得差异化数据,然后将该数据重新渲染即可。

那么,具体的思路是如何的?

我们都知道,ViewModel的变化会反映到View层上,我们可以通过对比新ViewModel和旧的ViewMode 来获得发生改变的位置,从而获取需要View需要改变的位置。

ViewModel从某个角度来说,就是描述View层的一种数据结构。了解AST的同学会清楚,可以通过数据结构来生成一种DOM对象树结构。

我们还是以前面的代码为例,模拟一下DOM树结构:

//旧的dom对象
let ulTree = {
  tagName: 'ul',
  attributes: [
    {id: 'root'}
  ],
  children: [
    {
      tagName: 'li',
      nodeText: '1',
      children: []
    }
  ]
}

这里我只写了一个li,但是通过这段代码,大家应该可以清楚所谓的DOM树结构是什么。

list新增了一个元素,对应的会生成如下一种JS对象的DOM树结构:

// 新的dom对象
let ulTree = {
  tagName: 'ul',
  attributes: [
    {id: 'root'}
  ],
  children: [
    {
      tagName: 'li',
      nodeText: '1',
      children: []
    },
    {
      // 新增的
      tagName: 'li',
      nodeText: '2',
      children: []
    }
  ]
}

有了新旧两段JS对象来描述的DOM树结构,我们就可以通过对比这两个JS对象,获得一个差异化的数据,并且拿到该发生变化数据的位置,在该位置进行相应的DOM操作。避免产生其他没必要的操作。

在这里,我个人对Virtual DOM有一个比较俗的理解:使用Virtual DOM的过程就好比我们生病了,去医院检查出什么病,直接对症下药,直接命中🎯。而不使用Virtual DOM的过程就好比我们不管什么病,直接吃一种包治百病的药💊,反正也能治好。

核心实现

前面我们用一些伪代码来介绍了VIrtual DOM的实现基本原理和概念。总的可以概括为三个步骤:1. 创建原始Virtual DOM;2.对比原始(旧)Virtual DOM 和用户操作所生成的新的Virtual DOM,生成差异化Virtual DOM;3.将差异化Virtual DOM渲染到页面上。

我们分别来看这3个步骤如何去实现。

创建Virtual DOM

创建Virtual DOM即把一段HTML字符串解析成一个可以描述它的JS对象,也就是类似一个DOM树结构的对象,前面我们也已经介绍了。但是,如何去创建Virtual DOM是一件很重的事情。一般的,我们可以能想到直接去遍历HTML去实现一个Virtual DOM结构。但是这样是错误的。

我们要清楚的是,Virtual DOM的优点就是减少DOM的操作,而我们如果直接去通过DOM API去扫描HTML代码,这本身就会使用的DOM的读取操作,很显然违背了我们的原则。

所以,我们选择另外一种方式,将HTML代码写成JS中的一个字符串,通过某种解析规则去解析这段HTML的字符串,在解析的过程中,我们就可以生成Virtual DOM。

🌰🌰🌰

let htmlString = `
  <ul id="root">
    <li>1</li>
  </ul>
`;
// 调用parse方法来解析这段字符串,比如通过正则。
// parse方法逐个分析字符,
// 将标签存入tagName, 
// 将属性存入attributes,
// 将子标签存入children,
// 这样就会产生前面我们所描述的DOM树结构对象。
// 整个过程中仅仅是对js字符串的操作
let vdom = parse(htmlString); 

简单来说,创建Virtual DOM往往就是将一段DOM描述字符串解析成VIrtual DOM对象的过程。

接着,我们生成了Virtual DOM后,还没有交给浏览器去解析,目前仅仅是有一个JS对象。所以我们还要去渲染DOM,但是,我们不会直接丢给浏览器去解析,而是通过自己去解析Virtual DOM 去生成HTML元素。

🌰🌰🌰

// 接收一个Virtual DOM对象。
function render(vdom) {
  let element = document.createElement(vdom.tagName); // 这也是React 外面只能有一个根节点的原因。
  let attribuest = vdom.attributes;
  // 遍历,设置根节点的dom属性
  for (let key in attribuets) {
    element.setAttribuets(key, attributes[key]);  
  }
  // 处理子元素
  let children = vdom.children || [];
  for (let child of children) {
    // 判断是文本节点还是元素节点
    let childNode = (typeof child === 'string') ? document.createTextNode(child) : render(child); 
    element.appendChild(childNode);
  }
  return element;
}

上面是一个简单的DOM渲染的逻辑处理,此时我们就获取了Virtual DOM 并且重新在页面上渲染出了对应的元素。

获取差异化Virtual DOM

前面我们也提到了通过对比来获取差异化Virtual DOM。这里涉及到一个算法,即多叉树算法。Virtual DOM的对比实际上就是对于多叉树结构的遍历算法。其中,又有广度优先算法和深度优先算法。我对这个算法不熟悉,大家可以自行去搜索学习。

但是,基本的概念还是要理解的。

假设我们两个Virtual DOM每个节点对应一个唯一的字母,节点顺序分别使用深度优先遍历算法表示为:
1.ABCEFGHDIJ
2.AKLMBEFCGHDIJ

[图片上传中...(2.png-17cc3b-1524548971902-0)]

2.png

通过对比,很容易发现我们需要在AB之间插入KLM节点,然后在通过KLM的关系得知,我们只需要插入K节点即可。

在对比过程中,我们要进行一系列的记录,比如发生的类型、位置,进行相应的增删改查操作等。

渲染差异化Virtual DOM

前面通过对比获取到了差异化Virtual DOM,拿到了类型和位置,我们直接通过DOM操作将内容渲染到对应位置上即可。

总结

Virtual DOM 是JS对象,对DOM树结构的一种抽象表现。

Virtual DOM最大的优势就是通过减少对DOM的操作次数,来提高交互性能和效率。但是我们也要清楚,其本质还是操作DOM,只不过仅仅是操作那些发生变化的DOM,减少了多余的操作。

基本的实现步骤为以下3点:

  • 初始化Virtual DOM;
  • 对比初始化(旧)的Virtual DOM和新的Virtual DOM 来生成差异化 Virtual DOM;
  • 渲染差异化Virtual DOM。

谈谈自己的目标

自己毕业也将近一年了,回顾自己刚开始接触自己前端的时候,发现确实成长了不少,特别是在毕业之后到现在,这段时间相较之前的成长是最快的,自己的知识体系丰富了不少。但是,当知识体系丰富了之后,发现自己需要掌握的还有很多。

总的目标方向不能变,就是提升自己的技术水平,当技术水平达到baseline后,开始寻求某一方面的突破,并提升自己的领导和业务能力

当前目标

自己在毕业的时候和同学注册了公司,但是一年下来,感觉对自己的技术成长有局限性。所以准备辞去当前公司工作,出去历练一下。所以当前目标,就是为了近期面试做准备。

  • 掌握Vue原理
  • 夯实JS基础知识
  • 复习一下基本的WEB安全和HTTP协议
  • 刷一些面试题

短期目标

在2-3年内,全面掌握这次课程大纲中涉及的内容,进入一线互联网公司,并慢慢成长为一个可以领导小型项目leader,或者具备一个leader的能力,对前端知识体系有一个全面掌握,并在个别方便有突破。

所以,成为一个leader,除了技术要达标外,还要有业务和领导能力。

总的来说,短期目标就是将自己前端技术水平提升到一个高度,具体来说:除了基本的前端语言外,需要掌握HTTP、WEB安全、算法、部分计算机原理等。另外,还要提升自己的业务能力和领导能力。

长期目标

长期目标,对于我来说,在技术水平达到一个高度后,选择重新创业的可能性会更大。但是在自己成长过程中,自己最基本的技术仍然要时刻学习,技术没有尽头的,况且前端行业发展迅速,知识更新快。

另外,继续坚持每周写周报的习惯。

文坚老师给出的这段话我觉得很好,这也是成长过程中需要的:

  • 知道敌人在哪。也就是业务问题在哪里?
  • 知道敌人从哪里来。也就是这个问题可以用什么来解决?是用技术来解决?还是用流程解决?还是要靠协同合作解决?
  • 知道怎么去伏击敌人,自己的优势在哪里。也就是根据自己的情况(资源、协同能力、影响力等)去制定最有效的方案(可能是技术,也可能是流程,亦可能是协同)
  • 知道在哪里避战,哪里主动出击,避其锋芒。知道哪些东西可以放弃,哪些东西必须解决,有重点有主线。
  • 知道怎么提升小队的战斗力。也就是,怎么培养人,怎么用事情来锻炼人
  • 知道怎么去请战功。也就是打了小规模胜战要让别人知道,例如让主管知道,让合作伙伴知道,让竞争对手知道,来建立跟你打战有肉吃的感受,提升小队凝聚力
  • 如有可能,甚至知道怎么联动友军去夹击敌人。也就是怎么跨团队合作,怎么通过合作来达成局部战略目的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 参考文章:深度剖析:如何实现一个Virtual DOM 算法 作者:戴嘉华React中一个没人能解释清楚的问题——...
    waka阅读 5,952评论 0 21
  • 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现4.1 步骤一:用JS...
    RThong阅读 388评论 0 2
  • Virtual DOM是React中的一个很重要的概念,在日常开发中,前端工程师们需要将后台的数据呈现到界面中,同...
    SherHoooo阅读 976评论 3 5
  • 前言 React 好像已经火了很久很久,以致于我们对于 Virtual DOM 这个词都已经很熟悉了,网上也有非常...
    NARUTO_86阅读 17,146评论 6 65
  • 变化这件事谈论页面的变化之前,咱们先看下数据和页面(视觉层面的页面)的关系。数据是隐藏在页面底下,通过渲染展示给用...
    Www刘阅读 443评论 0 1