[TOC]
*****
## 1 vdom意义
### 1-1 传统界面操作
>[info] (1)传统web界面操作中需要使用js操作DOM,
随着应用程序的复杂,dom操作复杂度提升。
为了有效的组织这种复杂操作,提出了mvc,mvvm等结果
### 1-2 vdom的意义
>[info] (2)在mvvm中通过数据绑定可以实现视图与数据的互动效果
不需要手动更新页面。只需要在模板中声明视图组件和绑定数据。
双向绑定引擎vm可以自动实现数据与视图的同步更新
>[info] (3)mvvm只是简化了数据与视图的关系。为了简化模板引擎渲染,
可以使用vdom。生成新的视图替换旧的视图。
>[info] (4)vdom的核心是维护数据与视图的关系
## 2 vdom思路
>[info] (1)原生dom包含属性较多。
直接操作dom可能导致页面重排,影响渲染性能
可以将dom解析为js对象,操作js对象则简单
~~~
;模板
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
;js对象
var element = {
tagName: 'ul', // 节点标签名
props: { // DOM的属性,用一个对象存储键值对
id: 'list'
},
children: [ // 该节点的子节点
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
~~~
>[info] (2) 这样就可以将数据的变化直接修改js对象,
对比修改后js的对象,记录下需要对页面真正的dom操作。
然后将其应用到真正的dom树,实现页面的更新
视图的结构是整个全新渲染,最后操作dom只是修改局部
>[info] (3) 核心步骤如下
~~~
1 将dom树转换为js对象结构,生成真正dom树,插入到文档中
2 数据发生变化后,重新生成虚拟dom树,进行比较
3 将比较结果保存到dom树上
~~~
>[info] (4) 可以将js对象看做真实dom的缓存部分。
## 3 vdom之element
>[info] 使用js对象记录dom节点,只需要记录节点类型,属性和子节点
~~~
;element.js
function Element (tagName, props, children) {
this.tagName = tagName
this.props = props
this.children = children
}
module.exports = function (tagName, props, children) {
return new Element(tagName, props, children)
}
~~~
>[info] element.js简单使用
~~~
var el = require('./element')
var ul = el('ul', {id: 'list'}, [
el('li', {class: 'item'}, ['Item 1']),
el('li', {class: 'item'}, ['Item 2']),
el('li', {class: 'item'}, ['Item 3'])
])
~~~
>[info] element的生成真正dom
~~~
Element.prototype.render = function () {
var el = document.createElement(this.tagName) // 根据tagName构建
var props = this.props
for (var propName in props) { // 设置节点的DOM属性
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
}
~~~
>[info] 添加渲染结果到文档
~~~
var ulRoot = ul.render()
document.body.appendChild(ulRoot)
~~~
## 4 vdom之diff
>[info] 对比两个树的diff是vdom算法的核心之一,
只是对同层的元素进行对比
![](https://box.kancloud.cn/2016-05-07_572d98881781d.png)
>[info] 对新旧两个树遍历,记录差异。
![](https://box.kancloud.cn/2016-05-07_572db1e3c23c9.png)
>[info] 遍历节点把节点对比信息存储到对象
~~~
// diff 函数,对比两棵树
function diff (oldTree, newTree) {
var index = 0 // 当前节点的标志
var patches = {} // 用来记录每个节点差异的对象
dfsWalk(oldTree, newTree, index, patches)
return patches
}
// 对两棵树进行深度优先遍历
function dfsWalk (oldNode, newNode, index, patches) {
// 对比oldNode和newNode的不同,记录下来
patches[index] = [...]
diffChildren(oldNode.children, newNode.children, index, patches)
}
// 遍历子节点
function diffChildren (oldChildren, newChildren, index, patches) {
var leftNode = null
var currentNodeIndex = index
oldChildren.forEach(function (child, i) {
var newChild = newChildren[i]
currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标识
? currentNodeIndex + leftNode.count + 1
: currentNodeIndex + 1
dfsWalk(child, newChild, currentNodeIndex, patches) // 深度遍历子节点
leftNode = child
})
}
~~~
>[info] 上面的记录信息
~~~
patches[0] = [{difference}, {difference}, ...] // 用数组存储新旧节点的不同
~~~
同理p是patches[1],ul是patches[3],类推。
>[info] 差异类型
对于节点的对比结果可能分为以下几种类型
替换掉原来的节点,
移动删除新增子节点
修改节点属性
修改节点文本
~~~
var REPLACE = 0
var REORDER = 1
var PROPS = 2
var TEXT = 3
~~~
>[info] 差异类型举例
~~~
;替换差异
patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}]
;属性修改
patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}, {
type: PROPS,
props: {
id: "container"
}
}]
;文本修改
patches[2] = [{
type: TEXT,
content: "Virtual DOM2"
}]
~~~
>[info] 子节点列表对比算法
~~~
;旧节点顺序
a b c d e f g h i
;新节点顺序
a b c h d f g h i j
~~~
>[info] 列表中节点的操作可以看做移动 插入 删除三种
移动可以看出删除和插入的合并。因此,可以简化为插入和删除操作。
抽象出来就是字符串的最小编辑距离问题。
简单实现如下
~~~
patches[0] = [{
type: REORDER,
moves: [{remove or insert}, {remove or insert}, ...]
}]
~~~
## 5 vdom之patch
>[info] 可以对js对象进行对比后结果patches对象中
转换为对应的dom操作实现dom树的更新
~~~
function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
}
function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index] // 从patches拿出当前节点的差异
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) { // 深度遍历子节点
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches) // 对当前节点进行DOM操作
}
}
function applyPatches (node, currentPatches) {
currentPatches.forEach(function (currentPatch) {
switch (currentPatch.type) {
case REPLACE:
node.parentNode.replaceChild(currentPatch.node.render(), node)
break
case REORDER:
reorderChildren(node, currentPatch.moves)
break
case PROPS:
setProps(node, currentPatch.props)
break
case TEXT:
node.textContent = currentPatch.content
break
default:
throw new Error('Unknown patch type ' + currentPatch.type)
}
})
}
~~~
## 6 总结
>[info] vdom算法主要包含以上三个函数element,diff,patch
使用思路如下
~~~
// 1. 构建虚拟DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
])
// 2. 通过虚拟DOM构建真正的DOM
var root = tree.render()
document.body.appendChild(root)
// 3. 生成新的虚拟DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
// 4. 比较两棵虚拟DOM树的不同
var patches = diff(tree, newTree)
// 5. 在真正的DOM元素上应用变更
patch(root, patches)
~~~
## 7 参考
[vdom算法](https://segmentfault.com/a/1190000004029168)
[vdom完整代码](https://github.com/livoras/simple-virtual-dom)
- 概述
- 框架结构
- 编译入口(\entries)
- web-compiler.js(web编译)
- web-runtime.js(web运行时)
- web-runtime-wih-compiler.js(web编译运行)
- web-server-renderer.js(web服务器渲染)
- 核心实现 (\core)
- index.js(核心入口)
- config.js(核心配置)
- core\util(核心工具)
- core\observer(双向绑定)
- core\vdom(虚拟DOM)
- core\global-api(核心api)
- core\instance(核心实例)
- 模板编译(\compiler)
- compiler\parser(模板解析)
- events.js(事件解析)
- helper.js(解析助手)
- directives\ref.js(ref指令)
- optimizer.js(解析优化)
- codegen.js(渲染生成)
- index.js(模板编译入口)
- web渲染(\platforms\web)
- compiler(web编译目录)
- runtime(web运行时目录)
- server(web服务器目录)
- util(web工具目录)
- 服务器渲染(\server)
- render-stream.js(流式渲染)
- render.js(服务器渲染函数)
- create-renderer.js(创建渲染接口)
- 框架流程
- Vue初始化
- Vue视图数据绑定
- Vue数据变化刷新
- Vue视图操作刷新
- 框架工具
- 基础工具(\shared)
- 模板编译助手
- 核心实例工具
- Web渲染工具
- 基础原理
- dom
- string
- array
- function
- object
- es6
- 模块(Module)
- 类(Class)
- 函数(箭头)
- 字符串(扩展)
- 代理接口(Proxy)
- 数据绑定基础
- 数据绑定实现
- mvvm简单实现
- mvvm简单使用
- vdom算法
- vdom实现
- vue源码分析资料