深入浅出Vue.js 虚拟DOM & diff算法
Posted 甄建雨要努力学习啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出Vue.js 虚拟DOM & diff算法相关的知识,希望对你有一定的参考价值。
VirtualDom
vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。
这是Vue中vnode实例对象中设计包含的所有属性和方法
class VNode { constructor(tag,data,children,text,elm,context,componentOptions,asyncFactory){ this.tag = tag //节点名称 eg:p、ul、div this.data = data //节点上的数据 eg:attrs、class、style this.children = children //当前节点的子节点列表 this.text = text //文本 this.elm = elm //vnode对应的真实的dom节点 this.ns = undefined // this.context = context //当前组件的实例 this.functionalContext = undefined // this.functionalOptions = undefined // this.functionalScopeId = undefined // this.key = data && data.key //vnode标记 在diff过程中提高diff效率 this.componentOptions = componentOptions //组件节点的选项参数 eg:propsData、tag、children this.componentInstance = undefined //组件的实例 this.parent = undefined // this.raw = false // this.isStatic = false // this.isRootInsert = false // this.isComment = false // 是否为注释 this.isCloned = false // 是否为克隆 this.isOnce = false // this.asyncFactory = asyncFactory // this.asyncMeta = undefined // this.isAsyncPlaceholder = false // } getChild(){ return this.componentInstance } }
VirtualDom 简单实现demo
class Element { constructor(type,props,children){ this.type = type this.props = props this.children = children } } function createElement(type,props,children){ return new Element(type,props,children) } /** * 渲染虚拟dom */ function render(domObj){ let el = document.createElement(domObj.type) for(let key in domObj.props){ setAttr(el,key,domObj.props[key]) } domObj.children.forEach((child)=>{ child = (child instanceof Element) ? render(child) : document.createTextNode(child) el.appendChild(child) }) return el } /** * setAttr方法实现 */ function setAttr(node,key,value){ switch(key){ case ‘value‘: if(node.tagName.toLowerCase() == ‘input‘ || node.tagName.toLowerCase() == ‘textarea‘){ node.value = value }else{ node.setAttribute(key,value) } break; case ‘style‘: node.style.cssText = value break; default: node.setAttribute(key,value) break; } } /** * 渲染dom方法 */ function renderDom(el,target){ target.appendChild(el) } // 执行以下看看效果 // 像不像render函数的写法 哈哈哈 let vNode = createElement(‘ul‘,{class:‘list‘},[ createElement(‘li‘,{class:‘item‘},[‘乔丹‘]), createElement(‘li‘,{class:‘item‘},[‘科比‘]), createElement(‘li‘,{class:‘item‘},[‘韦德‘]) ]); let el = render(vNode) let root = document.getElementById(‘zjy‘) renderDom(el,root)
diff算法 简单实现
//判断是否为string function isString(str){ return typeof str === ‘string‘ } //不同attr function diffAttr(oldAttr,newAttr){ let patch = {} for(let key in oldAttr){ if(oldAttr[key] !== newAttr[key]){ patch[key] = newAttr[key] } } for(let key in newAttr){ if(!oldAttr.hasOwnProperty(key)){ patch[key] = newAttr[key] } } return patch } //不同children let num = 0 function diffChildren(oldChildren,newChildren,patches){ oldChildren.forEach((child,index)=>{ walk(child,newChildren[index],++num,patches) }) } function diff(oldTree,newTree){ let patches = {} //存放补丁的对象 let index = 0 walk(oldTree,newTree,index,patches) return patches } /** * walk方法中diff的情况相当于只是列举了几种情况,我在控制台执行了下,的确有的情况没有体现出来,例如删除 新增子节点, * 所以下面的patch补丁方法中进行补丁的类别完全是按照diff中创在的不同type类型执行的不同操作 */ function walk(oldNode,newNode,index,patches){ let current = [] if(!newNode){ current.push({type:‘REMOVE‘,index}) }else if(isString(oldNode) && isString(newNode)){ if(oldNode !== newNode){ current.push({tyep:‘TEXT‘,text:newNode}) } }else if(oldNode.type === newNode.type){ let attr = diffAttr(oldNode.props,newNode.props) if(Object.keys(attr).length > 0){ current.push({type:‘ATTR‘,attr}) } diffChildren(oldNode.children,newNode.children,patches) }else{ current.push({type:‘REPLACE‘,newNode}) } if(current.length){ patches[index] = current } } /** * patch补丁(patch补丁简单实现的原由是因为diff方法简单实现造成的,有兴趣的可以去了解下vue中实现的diff算法 超长~~) */ let allPatches let index = 0 function patch(node,patches){ allPatches = patches walk(node) } function walk(node){ let current = allPatches[index++] let childNodes = node.childNodes childNodes.forEach((child)=>{ walk(child) }) if(current){ doPatch(node,current) } } function doPatch(node,patches){ patches.forEach((patch)=>{ switch(patch.type){ case ‘ATTR‘: for(let key in patch.attr){ let value = patch.attr[key] if(value){ setAttr(node,key,value) }else{ node.removeAttribute(key) } } break; case ‘TEXT‘: node.textContent = patch.text break; case ‘REPLACE‘: let newNode = patch.newNode newNode = (newNode instanceof Element) ? render(newNode) : document.createTextNode(newNode) node.parentNode.replaceChild(newNode,node) break; case ‘REMOVE‘: node.parentNode.removeChild(node) break; default: break; } }) }
以上是关于深入浅出Vue.js 虚拟DOM & diff算法的主要内容,如果未能解决你的问题,请参考以下文章