Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue - Virtual DOM & Diff #19

Open
yizihan opened this issue Mar 14, 2018 · 0 comments
Open

Vue - Virtual DOM & Diff #19

yizihan opened this issue Mar 14, 2018 · 0 comments
Labels

Comments

@yizihan
Copy link
Owner

yizihan commented Mar 14, 2018

虚拟DOM

DOM操作很慢,操作DOM后会触发浏览器的重排和重绘。

虚拟DOM通过JS模拟DOM结构,来对真实DOM发生的变化保持追踪。

当数据改变时,新生成的Virtual DOM会与旧的Virtual DOM进行对比,通过Diff算法找到区别,这些操作都是在快速的JS中完成的,最后对实际DOM进行最小的DOM操作来完成效果。

// DOM
<div id='parent'>
    <span class="child">item1</span>
</div>

// Virtual DOM
const dom = {
    tagName: 'div',
    props: {
        id: 'parent'
    },
    children: [
        {tagName: 'span', props: {class: 'child'}, children: ['item1']}
    ]
}

将Virtual DOM渲染成DOM

Element.prototype.render = function() {
    // 根据tagName构建真实DOM
    var el = document.createElement(this.tagName)   
    var props = this.props
    // 遍历虚拟DOM中的属性
    for (var propName in props) {
        // 获得属性值
        var propValue = props[propName]
        // 为真实DOM添加属性
        el.setAttribute(propName, propValue)
    }
    var children = this.children || []
    // 遍历子元素
    children.forEach(function(child) {
        var childEl = (child instanceof Element) 
            // 如果还是标签元素,则继续渲染
            ? child.render()
            // 文档元素
            : document.createTextNode(child)
        el.appendChild(childEl)
    })
    // 返回所有渲染后的真实DOM
    return el
}

snabbdom (2018-03-17)

snabbdom 是轻量的 Virtual DOM 实现,代码量少,模块化,结构清晰。

snabbdom 主要的接口有:

  • h(type, data, children),返回 Virtual DOM 树。
  • patch(oldVnode, newVnode),比较新/旧 Virtual DOM 树并更新。

示例

// 引入依赖
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>

// 初始化snabbdom
var snabbdom = window.snabbdom;
var patch = snabbdom.init([
		snabbdom_class,
		snabbdom_props,
		snabbdom_style,
		snabbdom_eventlisteners		
]);
var h = snabbdom.h;

var container = document.getElementById('container');
var data = [{name: 'aaa', age: 20}, {name: 'bbb', age: 30}];

var vnode
// 将数据渲染为虚拟DOM函数
function render(data) {
	// 将后台给的data对象调用h(),返回一个虚拟DOM对象
	// re-render时会重新生成,然后和之前生成的Diff
	var newVnode = h('table', {}, data.map(function(item) {
		var tds = []
		var i
		for (i in item) {
			if (item.hasOwnProperty(i)) {
				tds.push(h('td', {}, item[i] + ''))
			}
		}
		// 在table里面添加tr,tr里面包含所有的td及td包含的内容
		return h('tr', {}, tds)
	}))

	if (vnode) {
		// re-render渲染通道
		patch(vnode, newVnode)
	} else {
		// 初次渲染通道
		patch(container, newVnode)
	}
	// 替换vnode的值
	vnode = newVnode
}
// 执行初次渲染
render(data)

var btn = document.getElementById('btn')
btn.addEventListener('click', function() {
	var data = [{name: 'aaa', age: 20}, {name: 'ccc', age: 40}];
	// 只渲染发生修改的地方!!!
	render(data);
})

参考文章:解析 snabbdom 源码

比较innerHTML和Virtual DOM的重绘

  • innerHTML:render html string + 重新创建所有DOM元素
  • Virtual DOM:render Virtual DOM + diff + 必要的DOM更新

DOM Diff

Virtual DOM只会对同一层级的元素进行对比,不会跨层级比较。

设置key值可以最大化的利用节点(可以复用节点)。

// 1.构建虚拟DOM
var Tree = el('div', {'id': 'container'}, [
    el('h1', {style: 'color:blue'}, ['simple virtual dom'])
])

// 2.通过虚拟DOM渲染真正的DOM
var root = tree.render()
document.body.appendChild(root)

// 3.生成新的虚拟DOM
var newTree = el('div', {'di': 'container'}, [
    el('h1', {style: 'color: red'}, ['simple virtual dom'])
])

// 4.比较两个虚拟DOM的不同,记录差异,将不同的地方都放在patches数组
var pathces = diff(Tree, newTree)

// 5.在真正的DOM元素上应用变更
patch(root, patches)
@yizihan yizihan added the Vue label Mar 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant