vue 高阶内容

  1. 1. vue 高阶内容
    1. 1.1. vuejs
      1. 1.1.1. 虚拟DOM(Virtual-DOM)
        1. 1.1.1.1. 真实DOM与渲染流程
        2. 1.1.1.2. Virtual-DOM基础
          1. 1.1.1.2.1. 虚拟DOM的好处
        3. 1.1.1.3. 算法实现

vue 高阶内容

vuejs

vue.js 有个几个核心思:

  • 虚拟dom
  • 响应式数据
  • 组件通信

虚拟DOM(Virtual-DOM)

真实DOM与渲染流程

首先要想了解虚拟DOM,就需要了解什么是真实DOM;
如下图为webkit渲染引擎工作流程(概括)

webkit渲染流程

无论是什么浏览器的内核引擎,基本的渲染工作流程分为5个步骤:

创建DOM树 —> 创建style rules(常见css 样式树) —> 构建Render 树 —> 布局Layout —> Painting

  • 第一步,构建DOM树:用HTML解析器解析HTML 标签等相关元素,构建出一颗DOM树
  • 第二步,生成样式表:用css解析器,分析css文件和元素上inline样式(行内样式),生成页面的样式表;
  • 第三部,构建Render树:将DOM树和样式树关联以来,构建出一颗render树(Attachment)。每个DOM节点都有一个attach方法,接受样式信息,返回一个render对象,这些render对象最终会被构建成一颗render树
  • 第四步,确定节点坐标:根绝render树结构,为每个render树上的节点确定一个在显示器上的精确坐标
  • 第五步,绘制页面:根据render树与节点坐标,然后调用每个节点的paint方法,将他们绘制出来
  • DOM树的构建是文档加载完成开始吗?

    构建DOM树是一个渐进式的构成,为了给用户一个更好的体验,渲染引擎会将内容尽快的显示出来而不是等待所有HTML文档解析完成后才开始构建render树和布局
  • render树是DOM树和CSS样式表构建完成后开始构建的吗?

    因为浏览器的解析是渐进式的过程,所有这3个过程在实际进行的时候并不是完全独立的,会有交叉的过程,会边加载,边解析,边渲染
  • CSS的解析注意点?

    CSS的解析是从右往左解析嵌套的样式越多,解析的越慢
  • JS操作真实DOM代价?

    原生JS操作DOM时,浏览器会按照上面的构建顺序进行构建,当操作了10个DOM节点的时候,浏览器在获取到第一个DOM节点时会直接进行上述的构建顺序构建,并不会获知后面的9个需要变更的DOM节点,当第一个执行完成后依次进行剩下的9个构建,造成了性能浪费,即使计算器硬件一直在迭代,操作的DOM的代价也是很昂贵的,频繁的操作会造成页面的卡顿,影响用的体验

Virtual-DOM基础

虚拟DOM的好处

虚拟DOM是为了解决浏览器性能问题而设计出来的。如果一次操作更新10个DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attachDOM树上,再进行后续操作,避免大量无所谓的计算量。所以用JS对象模拟DOM节点的好处就是页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终JS对象映射到真实DOM,有浏览器去绘制

算法实现

  • 如何用JS对象模拟DOM
    这是一个真实的DOM
1
2
3
4
5
6
7
8
9
<div id="virtual-dom">
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
<div>Hello World</div>
</div>

JavaScript来表示一个DOM节点很容易,需要记住节点的类型、属性、还有子节点:
element.js 表示节点对象的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Element virdual-dom 对象定义
* @param {String} tagName - dom 元素名称
* @param {Object} props - dom 属性
* @param {Array<Element|String>} - 子节点
*/
function Element(tagName, props, children) {
this.tagName = tagName
this.props = props
this.children = children
// dom 元素的 key 值,用作唯一标识符
if(props.key){
this.key = props.key
}
var count = 0
children.forEach(function (child, i) {
if (child instanceof Element) {
count += child.count
} else {
children[i] = '' + child
}
count++
})
// 子元素个数
this.count = count
}

function createElement(tagName, props, children){
return new Element(tagName, props, children);
}

module.exports = createElement;

更具element对象的设定,则上面的DOM结构就可以简单表示为:

1
2
3
4
5
6
7
8
9
var el = require("./element.js")
var ul = el('div', {id:'virtual-dom'},[
el('ul', {id:'list'}, [
el('li', {class:'item'}, ['Item 1']),
el('li', {class:'item'}, ['Item 2']),
el('li', {class:'item'}, ['Item 3'])
]),
el('div', {}, ['Hello World'])
])

现在ul就是我们用JavaScript对象表示的DOM结构,输出查看ul对应的数据结构:
ul数据结构

  • 渲染用JS表示的DOM对象
    由于这是JS表示的DOM对象,在浏览器中是无法表示的,因此需要将ul渲染成页面上真实的DOM结构需要进行渲染函数渲染:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* render 将virdual-dom 对象渲染为实际 DOM 元素
*/
Element.prototype.render = function () {
var el = document.createElement(this.tagName)
var props = this.props
// 设置节点的DOM属性
for (var propName in props) {
var propValue = props[propName]
el.setAttribute(propName, propValue)
}

var children = this.children || []
children.forEach(function (child) {
var childEl = (child instanceof Element)
? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
: document.createTextNode(child) // 如果字符串,只构建文本节点
el.appendChild(childEl)
})
return el
}

我们通过查看以上render方法,会根据tagName构建一个真正的DOM节点,然后设置这个节点的属性,最后递归的把自己的子节点也构建起来.
我们将构建好的DOM结构添加到页面body上面,如下

1
2
3
ulRoot = ul.render();
document.body.appendChild(ulRoot);

真正渲染到body中真正的DOM结构

ul真实DOM结构

  • 比较两颗虚拟DOM树的差异-diff 算法

diff算法用来比较两颗V-dom树的差异。如果要对2颗树完全比较,那么diff算法的事件发展度为O(n^3), 但是对于前端来说,很少会进行跨越层级的移动DOM元素,所以V-dom只会对同一个层级的元素进行对比。如图所示,div只会和同级的div对比,第二层级的只会跟第二层级的对比,因此复杂度就降为了O(n)
diff算法