欢迎关注个人公众号睿Talk
,获取我最新的文章:javascript
目前最流行的两大前端框架,React和Vue,都不约而同的借助Virtual DOM技术提升页面的渲染效率。那么,什么是Virtual DOM?它是经过什么方式去提高页面渲染效率的呢?本系列文章会详细讲解Virtual DOM的建立过程,并实现一个简单的Diff算法来更新页面。本文的内容脱离于任何的前端框架,只讲最纯粹的Virtual DOM。敲单词太累了,下文Virtual DOM一概用VD表示。前端
这是VD系列文章的开篇,如下是本系列其它文章的传送门:
你不知道的Virtual DOM(一):Virtual Dom介绍
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新优化
你不知道的Virtual DOM(四):key的做用
你不知道的Virtual DOM(五):自定义组件
你不知道的Virtual DOM(六):事件处理&异步更新java
本质上来讲,VD只是一个简单的JS对象,而且最少包含tag、props和children三个属性。不一样的框架对这三个属性的命名会有点差异,但表达的意思是一致的。它们分别是标签名(tag)、属性(props)和子元素对象(children)。下面是一个典型的VD对象例子:react
{ tag: "div", props: {}, children: [ "Hello World", { tag: "ul", props: {}, children: [{ tag: "li", props: { id: 1, class: "li-1" }, children: ["第", 1] }] } ] }
VD跟dom对象有一一对应的关系,上面的VD是由如下的HTML生成的git
<div> Hello World <ul> <li id="1" class="li-1"> 第1 </li> </ul> </div>
一个dom对象,好比li
,由tag(li)
, props({id: 1, class: "li-1"})
和children(["第", 1])
三个属性来描述。github
VD 最大的特色是将页面的状态抽象为 JS 对象的形式,配合不一样的渲染工具,使跨平台渲染成为可能。如 React 就借助 VD 实现了服务端渲染、浏览器渲染和移动端渲染等功能。算法
此外,在进行页面更新的时候,借助VD,DOM 元素的改变能够在内存中进行比较,再结合框架的事务机制将屡次比较的结果合并后一次性更新到页面,从而有效地减小页面渲染的次数,提升渲染效率。咱们先来看下页面的更新通常会通过几个阶段。segmentfault
从上面的例子中,能够看出页面的呈现会分如下3个阶段:数组
这个例子里面,JS计算用了691
毫秒,生成渲染树578
毫秒,绘制73
毫秒。若是能有效的减小生成渲染树和绘制所花的时间,更新页面的效率也会随之提升。
经过VD的比较,咱们能够将多个操做合并成一个批量的操做,从而减小dom重排的次数,进而缩短了生成渲染树和绘制所花的时间。至于如何基于VD更有效率的更新dom,是一个颇有趣的话题,往后有机会将另写一篇文章介绍。浏览器
咱们先从如何生成VD提及。借助JSX编译器,能够将文件中的HTML转化成函数的形式,而后再利用这个函数生成VD。看下面这个例子:
function render() { return ( <div> Hello World <ul> <li id="1" class="li-1"> 第1 </li> </ul> </div> ); }
这个函数通过JSX编译后,会输出下面的内容:
function render() { return h( 'div', null, 'Hello World', h( 'ul', null, h( 'li', { id: '1', 'class': 'li-1' }, '\u7B2C1' ) ) ); }
这里的h是一个函数,能够起任意的名字。这个名字经过babel进行配置:
// .babelrc文件 { "plugins": [ ["transform-react-jsx", { "pragma": "h" // 这里可配置任意的名称 }] ] }
接下来,咱们只须要定义h函数,就能构造出VD
function flatten(arr) { return [].concat.apply([], arr); } function h(tag, props, ...children) { return { tag, props: props || {}, children: flatten(children) || [] }; }
h函数会传入三个或以上的参数,前两个参数一个是标签名,一个是属性对象,从第三个参数开始的其它参数都是children。children元素有多是数组的形式,须要将数组解构一层。好比:
function render() { return ( <ul> <li>0</li> { [1, 2, 3].map( i => ( <li>{i}</li> )) } </ul> ); } // JSX编译后 function render() { return h( 'ul', null, h( 'li', null, '0' ), /* * 须要将下面这个数组解构出来再放到children数组中 */ [1, 2, 3].map(i => h( 'li', null, i )) ); }
继续以前的例子。执行h函数后,最终会获得以下的VD对象:
{ tag: "div", props: {}, children: [ "Hello World", { tag: "ul", props: {}, children: [{ tag: "li", props: { id: 1, class: "li-1" }, children: ["第", 1] }] } ] }
下一步,经过遍历VD对象,生成真实的dom
// 建立dom元素 function createElement(vdom) { // 若是vdom是字符串或者数字类型,则建立文本节点,好比“Hello World” if (typeof vdom === 'string' || typeof vdom === 'number') { return doc.createTextNode(vdom); } const {tag, props, children} = vdom; // 1. 建立元素 const element = doc.createElement(tag); // 2. 属性赋值 setProps(element, props); // 3. 建立子元素 // appendChild在执行的时候,会检查当前的this是否是dom对象,所以要bind一下 children.map(createElement) .forEach(element.appendChild.bind(element)); return element; } // 属性赋值 function setProps(element, props) { for (let key in props) { element.setAttribute(key, props[key]); } }
createElement
函数执行完后,dom元素就建立完并展现到页面上了(页面比较丑,不要介意...)。
本文介绍了VD的基本概念,并讲解了如何利用JSX编译HTML标签,而后生成VD,进而建立真实dom的过程。下一篇文章将会实现一个简单的VD Diff算法,找出2个VD的差别并将更新的元素映射到dom中去:你不知道的Virtual DOM(二):Virtual Dom的更新
P.S.: 想看完整代码见这里,若是有必要建一个仓库的话请留言给我:代码