虚拟dom
-
用js对象来表示dom树的结构,然后用这个对象来构建一个真正的dom树插入文档中;
-
当状态有变时,重新构造一个新的对象树,然后比较新的和旧的树,记录两个数的差异;
-
把差异部分应用到真正的dom树上,更新视图。
核心算法实现(diff算法)
-
用js对象表示dom树
var e = {
tagName: 'ul',
props: {
id: 'list'
},
children: [
{tagName: 'li', props: {class: 'li1'}, children: ['item1']},
{tagName: 'li', props: {class: 'li2'}, children: ['item2']},
{tagName: 'li', props: {class: 'li3'}, children: [
{tagName: 'h2', props: {class: 'h'}, children: ['hello qq']}
]},
]
}
-
把js对象渲染成dom树
function dom(tagName, props, children){
function Element(tagName, props, children){ this.tagName = tagName; this.props = props; this.children = children;
}
Element.prototype.render = function(){
const el = document.createElement(this.tagName);
const props = this.props; for(let key in props){
el.setAttribute(key, props[key]);
}
const children = this.children || [];
children.forEach(child => {
const c = child.tagName ? new Element(child.tagName, child.props, child.children).render() : document.createTextNode(child);
el.appendChild(c);
}) return el;
}
return new Element(tagName, props, children);
}
-
比较两个虚拟dom树的差异,同层节点进行比较(时间复杂度O(n));
对每一个树在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比,把差异部分记录到一个对象里面。
// 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
})
}
-
因为步骤一所构建的 JavaScript 对象树和render出来真正的DOM树的信息、结构是一样的。所以我们可以对那棵DOM树也进行深度优先的遍历,遍历的时候从步骤二生成的paches对象中找出当前遍历的节点差异,然后进行 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)
}
})
}
Vue之MVVM简单实现
<!DOCTYPE html> <html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MVVM</title> </head> <body>
<div id='app'> <h2>{{ song }}</h2> <h3>{{ obj.name }}</h3> <h3>{{ obj.a }}</h3> <h3>{{ obj.obj1.name1 }}</h3> <input type="text" v-model='msg'> <h3>{{ msg }}</h3> <h2>{{ total }}</h2>
</div>
<script src="index.js"></script>
<script>
let vm = new Vm({
el: '#app',
data(){ return {
song: 'Time',
obj: {
name: 'xxx',
a: 20,
b: 3,
obj1: {
name1: 'll'
}
},
msg: 'hello'
}
},
computed: {
total() {
return this.obj.a * this.obj.b;
}
},
mounted() {
console.log(this.$el)
console.log('everything is done')
}
})
</script> </body> </html>
function Vm(opts = {}){
this.$opts = opts;
let data = this.$data = this.$opts.data();
initComputed.call(this);
// 数据监测 observer(data);
for (let key in data) {
Object.defineProperty(this, key, {
configurable: true,
get() { return this.$data[key];
},
set(newVal) { this.$data[key] = newVal;
}
});
}
// 数据编译
new Compile(opts.el, this);
opts.mounted.call(this);
}
function initComputed(){
let vm = this;
let computed = this.$opts.computed;
Object.keys(computed).forEach(key => {
Object.defineProperty(vm, key, {
get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
set(){}
})
})
}
function observer(data) {
if(!data || typeof data !== 'object') return;
return new Observer(data);
}
function Observer(data) {
let dep = new Dep();
for (let key in data) {
let val = data[key];
observer(val);
Object.defineProperty(data, key, {
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target); return val;
},
set(newVal) { if(val === newVal) return;
val = newVal;
observer(newVal);
dep.notify();
}
})
}
}
function Compile(el, vm){
vm.$el = document.querySelector(el);
var fragment = document.createDocumentFragment();
var child;
while(child = vm.$el.firstChild) {
fragment.appendChild(child);
}
function replace(fragment){
Array.from(fragment.childNodes).forEach(item => {
let text = item.textContent;
let reg = //{/{(.*?)/}/}/g;
if(item.nodeType === 3 && reg.test(text)){ // 重点重点重点!!!!!! // 去掉空格!!!!!!!! function replaceTxt() {
item.textContent = text.replace(reg, (matched, placeholder) => {
console.log(placeholder); // 匹配到的分组 如:song, album.name, singer... new Watcher(vm, placeholder.trim(), replaceTxt); // 监听变化,进行匹配替换内容 return placeholder.trim().split('.').reduce((val, key) => {
return val[key];
}, vm);
});
};
replaceTxt();
}
if(item.nodeType === 1){
let itemAttr = item.attributes;
Array.from(itemAttr).forEach(attr => {
let name = attr.name;
let exp = attr.value;
if(name.includes('v-')){
item.value = vm[exp];
}
new Watcher(vm, exp, newVal => {
item.value = newVal;
})
item.addEventListener('input', e => {
vm[exp] = e.target.value;
})
})
}
if(item.childNodes && item.childNodes.length){
replace(item);
}
})
}
replace(fragment);
vm.$el.appendChild(fragment);
}
// 发布订阅 function Dep(){
this.subs = [];
}
Dep.prototype = {
addSub(sub){ this.subs.push(sub);
},
notify(){ this.subs.forEach(sub => {
sub.update()
});
}
}
function Watcher(vm, exp, fn){
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let arr = exp.split('.');
let val = vm;
arr.forEach(key => {
val = val[key];
});
Dep.target = null;
}
Watcher.prototype.update = function(){
let arr = this.exp.split('.');
let val = this.vm;
arr.forEach(key => {
val = val[key]; // 通过get获取到新的值 });
this.fn(val);
}
// let watcher = new Watcher(() => {
// console.log('watch') // })
// let dep = new Dep();
// dep.addSub(watcher);
// dep.addSub(watcher);
// dep.notify();
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/7511.html