。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> </style> </head> <body> <div id="root" class="tt"> <div title="tt1">hello1</div> <div title="tt2">hello2</div> <div title="tt3">hello3</div> <ul> <li>1</li> <li>2</li> <li>3</li> </ul> </div> <script> // 用內存去表述DOM // 將真實DOM轉化為虛擬DOM // <div /> => {tag:'div'} 元素轉化 // 文本節點 => {tag:undefined,value:'文本節點'} 文本節點轉化 // <div title="1" class="c" /> => { tag:'div',data:{ title:'1',class:"c" } } 多屬性轉化 // <div><div /></div> => {tag:'div',children:[{ tag:'div' }]} // 用構造函數來 進行以上轉換 // 這一次我們用class語法 class VNode { // 構造函數 constructor( tag,data,value,type ){ // tag:用來表述 標簽 data:用來描述屬性 value:用來描述文本 type:用來描述類型 this.tag = tag && tag.toLowerCase();//文本節點時 tagName是undefined this.data = data; this.value = value; this.type = type; this.children = []; } appendChild( vnode ){ this.children.push( vnode ); } } /** 利用遞歸 來遍歷DOM元素 生成虛擬DOM Vue中的源碼使用 棧結構 ,使用棧存儲 父元素來實現遞歸生成 */ function getVNode( node ){ let nodeType = node.nodeType; let _vnode = null; if( nodeType === 1){ // 元素 let nodeName = node.nodeName;//元素名 什么標簽? let attrs = node.attributes;//屬性 偽數組 元素上的屬性 let _attrObj = {}; for(let i=0;i<attrs.length;i++){//attrs[ i ] 屬性節點(nodeType == 2) 是對象 _attrObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;//attrs[ i ].nodeName:屬性名 attrs[ i ].nodeValue:屬性值 } _vnode = new VNode( nodeName,_attrObj,undefined,nodeType);//標簽名(DIV UI LI...)、所有屬性對象、value值(只有文本標簽有)、type類型(是元素還是文本) // 考慮node的子元素 let childNodes = node.childNodes; for(let i = 0;i<childNodes.length;i++){ _vnode.appendChild( getVNode( childNodes[ i ] ) );//遞歸 } }else if( nodeType === 3 ){ // 文本節點 _vnode = new VNode( undefined,undefined,node.nodeValue,nodeType);//無標簽名、無屬性、有value、有type } return _vnode; } let root = document.querySelector("#root"); let vroot = getVNode ( root );//虛擬DOM console.log(vroot); // 將vNode轉化為真正的DOM function parseNode(vnode){ // 在真正的vue中 也是使用遞歸+棧 數據類型 // 創建真實的DOM let type = vnode.type;//拿到虛擬DOM的type,元素?文本? let _node = null;//用來放創建出來的元素 真實node if(type === 3){ // 文本節點 return document.createTextNode(vnode.value);//直接創建文本節點 }else if(type === 1){ // 元素節點 _node = document.createElement(vnode.tag);//用tag名創建對應的標簽 // 屬性 let data = vnode.data;//鍵值對類型 真正的vue中葯比這復雜的多(事件、指令等) Object.keys(data).forEach( key => { let attrName = key;//屬性名 let attrValue = data[key];//屬性值 _node.setAttribute(attrName,attrValue);//社會元素的屬性 }) // 子元素 let children = vnode.children; children.forEach(subvnode => { _node.appendChild( parseNode(subvnode) );//將子元素放進去 遞歸轉換子元素 }); return _node; }; } let dom2 = parseNode(vroot);//虛擬dom轉換成真實dom console.log(dom2);//打印出來的DOM和真實dom是一樣的 </script> </body> </html>
。
