偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Virtual DOM到底有什么迷人之處?如何搭建一款迷你版Virtual DOM庫?

開發(fā) 前端
手動(dòng)操作DOM比較麻煩。還需要考慮瀏覽器兼容性問題,雖然有JQuery等庫簡(jiǎn)化DOM操作,但是隨著項(xiàng)目的復(fù)雜DOM操作復(fù)雜提升。

[[406455]]

為什么使用Virtual DOM

  • 手動(dòng)操作DOM比較麻煩。還需要考慮瀏覽器兼容性問題,雖然有JQuery等庫簡(jiǎn)化DOM操作,但是隨著項(xiàng)目的復(fù)雜DOM操作復(fù)雜提升。
  • 為了簡(jiǎn)化DOM的復(fù)雜操作于是出現(xiàn)了各種MVVM框架,MVVM框架解決了視圖和狀態(tài)的同步問題
  • 為了簡(jiǎn)化視圖的操作我們可以使用模板引擎,但是模板引擎沒有解決跟蹤狀態(tài)變化的問題,于是Virtual DOM出現(xiàn)了
  • Virtual DOM的好處是當(dāng)狀態(tài)改變時(shí)不需要立即更新DOM,只需要?jiǎng)?chuàng)建一個(gè)虛擬樹來描述DOM,Virtual DOM內(nèi)部將弄清楚如何有效的更新DOM(利用Diff算法實(shí)現(xiàn))。

Virtual DOM的特性

  1. Virtual DOM可以維護(hù)程序的狀態(tài),跟蹤上一次的狀態(tài)。
  2. 通過比較前后兩次的狀態(tài)差異更新真實(shí)DOM。

實(shí)現(xiàn)一個(gè)基礎(chǔ)的Virtual DOM庫

我們可以仿照snabbdom庫https://github.com/snabbdom/snabbdom.git自己動(dòng)手實(shí)現(xiàn)一款迷你版Virtual DOM庫。

首先,我們創(chuàng)建一個(gè)index.html文件,寫一下我們需要展示的內(nèi)容,內(nèi)容如下:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.     <meta charset="UTF-8"
  6.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  7.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  8.     <title>vdom</title> 
  9.     <style> 
  10.         .main { 
  11.             color: #00008b; 
  12.         } 
  13.         .main1{ 
  14.             font-weight: bold; 
  15.         } 
  16.     </style> 
  17. </head> 
  18.  
  19. <body> 
  20.     <div id="app"></div> 
  21.     <script src="./vdom.js"></script> 
  22.     <script> 
  23.         function render() { 
  24.             return h('div', { 
  25.                 style: useObjStr({ 
  26.                     'color''#ccc'
  27.                     'font-size''20px' 
  28.                 }) 
  29.             }, [ 
  30.                 h('div', {}, [h('span', { 
  31.                     onClick: () => { 
  32.                         alert('1'); 
  33.                     } 
  34.                 }, '文本'), h('a', { 
  35.                     href: 'https://www.baidu.com'
  36.                     class: 'main main1' 
  37.                 }, '點(diǎn)擊'
  38.                 ]), 
  39.             ]) 
  40.         } 
  41.          
  42.         // 頁面改變 
  43.         function render1() { 
  44.             return h('div', { 
  45.                 style: useStyleStr({ 
  46.                     'color''#ccc'
  47.                     'font-size''20px' 
  48.                 }) 
  49.             }, [ 
  50.                 h('div', {}, [h('span', { 
  51.                     onClick: () => { 
  52.                         alert('1'); 
  53.                     } 
  54.                 }, '文本改變了'
  55.                 ]), 
  56.             ]) 
  57.         } 
  58.  
  59.         // 首次加載 
  60.         mountNode(render, '#app'); 
  61.  
  62.         // 狀態(tài)改變 
  63.         setTimeout(()=>{ 
  64.             mountNode(render1, '#app'); 
  65.         },3000) 
  66.     </script> 
  67. </body> 
  68.  
  69. </html>

我們?cè)赽ody標(biāo)簽內(nèi)創(chuàng)建了一個(gè)id是app的DOM元素,用于被掛載節(jié)點(diǎn)。接著我們引入了一個(gè)vdom.js文件,這個(gè)文件就是我們將要實(shí)現(xiàn)的迷你版Virtual DOM庫。最后,我們?cè)趕cript標(biāo)簽內(nèi)定義了一個(gè)render方法,返回為一個(gè)h方法。調(diào)用mountNode方法掛載到id是app的DOM元素上。h方法中數(shù)據(jù)結(jié)構(gòu)我們是借鑒snabbdom庫,第一個(gè)參數(shù)是標(biāo)簽名,第二個(gè)參數(shù)是屬性,最后一個(gè)參數(shù)是子節(jié)點(diǎn)。還有,你可能會(huì)注意到在h方法中我們使用了useStyleStr方法,這個(gè)方法主要作用是將style樣式轉(zhuǎn)化成頁面能識(shí)別的結(jié)構(gòu),實(shí)現(xiàn)代碼我會(huì)在最后給出。

思路理清楚了,展示頁面的代碼也寫完了。下面我們將重點(diǎn)看下vdom.js,如何一步一步地實(shí)現(xiàn)它。

第一步

我們看到index.html文件中首先需要調(diào)用mountNode方法,所以,我們先在vdom.js文件中定義一個(gè)mountNode方法。

  1. // Mount node 
  2. function mountNode(render, selector) { 
  3.  

接著,我們會(huì)看到mountNode方法第一個(gè)參數(shù)是render方法,render方法返回了h方法,并且看到第一個(gè)參數(shù)是標(biāo)簽,第二個(gè)參數(shù)是屬性,第三個(gè)參數(shù)是子節(jié)點(diǎn)。

那么,我們接著在vdom.js文件中再定義一個(gè)h方法。

  1.  function h(tag, props, children) { 
  2.     return { tag, props, children }; 

還沒有結(jié)束,我們需要根據(jù)傳入的三個(gè)參數(shù)tag、props、children來掛載到頁面上。

我們需要這樣操作。我們?cè)趍ountNode方法內(nèi)封裝一個(gè)mount方法,將傳給mountNode方法的參數(shù)經(jīng)過處理傳給mount方法。

  1. // Mount node 
  2. function mountNode(render, selector) { 
  3.   mount(render(), document.querySelector(selector)) 

接著,我們定義一個(gè)mount方法。

  1. function mount(vnode, container) { 
  2.     const el = document.createElement(vnode.tag); 
  3.     vnode.el = el; 
  4.     // props 
  5.     if (vnode.props) { 
  6.         for (const key in vnode.props) { 
  7.             if (key.startsWith('on')) { 
  8.                 el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key],{ 
  9.                     passive:true 
  10.                 }) 
  11.             } else { 
  12.                 el.setAttribute(key, vnode.props[key]); 
  13.             } 
  14.         } 
  15.     } 
  16.     if (vnode.children) { 
  17.         if (typeof vnode.children === "string") { 
  18.             el.textContent = vnode.children; 
  19.         } else { 
  20.             vnode.children.forEach(child => { 
  21.                 mount(child, el); 
  22.             }); 
  23.         } 
  24.     } 
  25.      
  26.     container.appendChild(el); 

第一個(gè)參數(shù)是調(diào)用傳進(jìn)來的render方法,它返回的是h方法,而h方返回一個(gè)同名參數(shù)的對(duì)象{ tag, props, children },那么我們就可以通過vnode.tag、vnode.props、vnode.children取到它們。

我們看到先是判斷屬性,如果屬性字段開頭含有,on標(biāo)識(shí)就是代表事件,那么就從屬性字段第三位截取,利用addEventListenerAPI創(chuàng)建一個(gè)監(jiān)聽事件。否則,直接利用setAttributeAPI設(shè)置屬性。

接著,再判斷子節(jié)點(diǎn),如果是字符串,我們直接將字符串賦給文本節(jié)點(diǎn)。否則就是節(jié)點(diǎn),我們就遞歸調(diào)用mount方法。

最后,我們將使用appendChildAPI把節(jié)點(diǎn)內(nèi)容掛載到真實(shí)DOM中。

頁面正常顯示。

第二步

我們知道Virtual DOM有以下兩個(gè)特性:

  1. Virtual DOM可以維護(hù)程序的狀態(tài),跟蹤上一次的狀態(tài)。
  2. 通過比較前后兩次的狀態(tài)差異更新真實(shí)DOM。

這就利用到了我們之前提到的diff算法。

我們首先定義一個(gè)patch方法。因?yàn)橐獙?duì)比前后狀態(tài)的差異,所以第一個(gè)參數(shù)是舊節(jié)點(diǎn),第二個(gè)參數(shù)是新節(jié)點(diǎn)。

  1. function patch(n1, n2) { 
  2.     

下面,我們還需要做一件事,那就是完善mountNode方法,為什么這樣操作呢?是因?yàn)楫?dāng)狀態(tài)改變時(shí),只更新狀態(tài)改變的DOM,也就是我們所說的差異更新。這時(shí)就需要配合patch方法做diff算法。

相比之前,我們加上了對(duì)是否掛載節(jié)點(diǎn)進(jìn)行了判斷。如果沒有掛載的話,就直接調(diào)用mount方法掛載節(jié)點(diǎn)。否則,調(diào)用patch方法進(jìn)行差異更新。

  1. let isMounted = false
  2. let oldTree; 
  3.  
  4. // Mount node 
  5. function mountNode(render, selector) { 
  6.     if (!isMounted) { 
  7.         mount(oldTree = render(), document.querySelector(selector)); 
  8.         isMounted = true
  9.     } else { 
  10.         const newTree = render(); 
  11.         patch(oldTree, newTree); 
  12.         oldTree = newTree; 
  13.     } 
  14.  

那么下面我們將主動(dòng)看下patch方法,這也是在這個(gè)庫中最復(fù)雜的方法。

  1. function patch(n1, n2) { 
  2.     // Implement this 
  3.     // 1. check if n1 and n2 are of the same type 
  4.     if (n1.tag !== n2.tag) { 
  5.         // 2. if notreplace 
  6.         const parent = n1.el.parentNode; 
  7.         const anchor = n1.el.nextSibling; 
  8.         parent.removeChild(n1.el); 
  9.         mount(n2, parent, anchor); 
  10.         return 
  11.     } 
  12.  
  13.     const el = n2.el = n1.el; 
  14.  
  15.     // 3. if yes 
  16.     // 3.1 diff props 
  17.     const oldProps = n1.props || {}; 
  18.     const newProps = n2.props || {}; 
  19.     for (const key in newProps) { 
  20.         const newValue = newProps[key]; 
  21.         const oldValue = oldProps[key]; 
  22.         if (newValue !== oldValue) { 
  23.             if (newValue != null) { 
  24.                 el.setAttribute(key, newValue); 
  25.             } else { 
  26.                 el.removeAttribute(key); 
  27.             } 
  28.         } 
  29.     } 
  30.     for (const key in oldProps) { 
  31.         if (!(key in newProps)) { 
  32.             el.removeAttribute(key); 
  33.         } 
  34.     } 
  35.     // 3.2 diff children 
  36.     const oc = n1.children; 
  37.     const nc = n2.children; 
  38.     if (typeof nc === 'string') { 
  39.         if (nc !== oc) { 
  40.             el.textContent = nc; 
  41.         } 
  42.     } else if (Array.isArray(nc)) { 
  43.         if (Array.isArray(oc)) { 
  44.             // array diff 
  45.             const commonLength = Math.min(oc.length, nc.length); 
  46.             for (let i = 0; i < commonLength; i++) { 
  47.                 patch(oc[i], nc[i]); 
  48.             } 
  49.             if (nc.length > oc.length) { 
  50.                 nc.slice(oc.length).forEach(c => mount(c, el)); 
  51.             } else if (oc.length > nc.length) { 
  52.                 oc.slice(nc.length).forEach(c => { 
  53.                     el.removeChild(c.el); 
  54.                 }) 
  55.             } 
  56.         } else { 
  57.             el.innerHTML = ''
  58.             nc.forEach(c => mount(c, el)); 
  59.         } 
  60.     } 

我們從patch方法入?yún)㈤_始,兩個(gè)參數(shù)分別是在mountNode方法中傳進(jìn)來的舊節(jié)點(diǎn)oldTree和新節(jié)點(diǎn)newTree,首先我們進(jìn)行對(duì)新舊節(jié)點(diǎn)的標(biāo)簽進(jìn)行對(duì)比。

如果新舊節(jié)點(diǎn)的標(biāo)簽不相等,就移除舊節(jié)點(diǎn)。另外,利用nextSiblingAPI取指定節(jié)點(diǎn)之后緊跟的節(jié)點(diǎn)(在相同的樹層級(jí)中)。然后,傳給mount方法第三個(gè)參數(shù)。這時(shí)你可能會(huì)有疑問,mount方法不是有兩個(gè)參數(shù)嗎?對(duì),但是這里我們需要傳進(jìn)去第三個(gè)參數(shù),主要是為了對(duì)同級(jí)節(jié)點(diǎn)進(jìn)行處理。

  1. if (n1.tag !== n2.tag) { 
  2.       // 2. if notreplace 
  3.       const parent = n1.el.parentNode; 
  4.       const anchor = n1.el.nextSibling; 
  5.       parent.removeChild(n1.el); 
  6.       mount(n2, parent, anchor); 
  7.       return 
  8.   } 

所以,我們重新修改下mount方法。我們看到我們只是加上了對(duì)anchor參數(shù)是否為空的判斷。

如果anchor參數(shù)不為空,我們使用insertBeforeAPI,在參考節(jié)點(diǎn)之前插入一個(gè)擁有指定父節(jié)點(diǎn)的子節(jié)點(diǎn)。insertBeforeAPI第一個(gè)參數(shù)是用于插入的節(jié)點(diǎn),第二個(gè)參數(shù)將要插在這個(gè)節(jié)點(diǎn)之前,如果這個(gè)參數(shù)為 null 則用于插入的節(jié)點(diǎn)將被插入到子節(jié)點(diǎn)的末尾。

如果anchor參數(shù)為空,直接在父節(jié)點(diǎn)下的子節(jié)點(diǎn)列表末尾添加子節(jié)點(diǎn)。

  1. function mount(vnode, container, anchor) { 
  2.     const el = document.createElement(vnode.tag); 
  3.     vnode.el = el; 
  4.     // props 
  5.     if (vnode.props) { 
  6.         for (const key in vnode.props) { 
  7.             if (key.startsWith('on')) { 
  8.                 el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key],{ 
  9.                     passive:true 
  10.                 }) 
  11.             } else { 
  12.                 el.setAttribute(key, vnode.props[key]); 
  13.             } 
  14.         } 
  15.     } 
  16.     if (vnode.children) { 
  17.         if (typeof vnode.children === "string") { 
  18.             el.textContent = vnode.children; 
  19.         } else { 
  20.             vnode.children.forEach(child => { 
  21.                 mount(child, el); 
  22.             }); 
  23.         } 
  24.     } 
  25.     if (anchor) { 
  26.         container.insertBefore(el, anchor); 
  27.     } else { 
  28.         container.appendChild(el); 
  29.     } 

下面,我們?cè)倩氐絧atch方法。如果新舊節(jié)點(diǎn)的標(biāo)簽相等,我們首先要遍歷新舊節(jié)點(diǎn)的屬性。我們先遍歷新節(jié)點(diǎn)的屬性,判斷新舊節(jié)點(diǎn)的屬性值是否相同,如果不相同,再進(jìn)行進(jìn)一步處理。判斷新節(jié)點(diǎn)的屬性值是否為null,否則直接移除屬性。然后,遍歷舊節(jié)點(diǎn)的屬性,如果屬性名不在新節(jié)點(diǎn)屬性表中,則直接移除屬性。

分析完了對(duì)新舊節(jié)點(diǎn)屬性的對(duì)比,接下來,我們來分析第三個(gè)參數(shù)子節(jié)點(diǎn)。

首先,我們分別定義兩個(gè)變量oc、nc,分別賦予舊節(jié)點(diǎn)的children屬性和新節(jié)點(diǎn)的children屬性。如果新節(jié)點(diǎn)的children屬性是字符串,并且新舊節(jié)點(diǎn)的內(nèi)容不相同,那么就直接將新節(jié)點(diǎn)的文本內(nèi)容賦予即可。

接下來,我們看到利用Array.isArray()方法判斷新節(jié)點(diǎn)的children屬性是否是數(shù)組,如果是數(shù)組的話,就執(zhí)行下面這些代碼。

  1. else if (Array.isArray(nc)) { 
  2.         if (Array.isArray(oc)) { 
  3.             // array diff 
  4.             const commonLength = Math.min(oc.length, nc.length); 
  5.             for (let i = 0; i < commonLength; i++) { 
  6.                 patch(oc[i], nc[i]); 
  7.             } 
  8.             if (nc.length > oc.length) { 
  9.                 nc.slice(oc.length).forEach(c => mount(c, el)); 
  10.             } else if (oc.length > nc.length) { 
  11.                 oc.slice(nc.length).forEach(c => { 
  12.                     el.removeChild(c.el); 
  13.                 }) 
  14.             } 
  15.         } else { 
  16.             el.innerHTML = ''
  17.             nc.forEach(c => mount(c, el)); 
  18.         } 
  19.     } 

我們看到里面又判斷舊節(jié)點(diǎn)的children屬性是否是數(shù)組。

如果是,我們?nèi)⌒屡f子節(jié)點(diǎn)數(shù)組的長度兩者的最小值。然后,我們將其循環(huán)遞歸patch方法。為什么取最小值呢?是因?yàn)槿绻〉氖撬麄児灿械拈L度。然后,每次遍歷遞歸時(shí),判斷nc.length和oc.length的大小,循環(huán)執(zhí)行對(duì)應(yīng)的方法。

如果不是,直接將節(jié)點(diǎn)內(nèi)容清空,重新循環(huán)執(zhí)行mount方法。

這樣,我們搭建的迷你版Virtual DOM庫就這樣完成了。

頁面如下所示。

源碼

index.html

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.     <meta charset="UTF-8"
  6.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  7.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  8.     <title>vdom</title> 
  9.     <style> 
  10.         .main { 
  11.             color: #00008b; 
  12.         } 
  13.         .main1{ 
  14.             font-weight: bold; 
  15.         } 
  16.     </style> 
  17. </head> 
  18.  
  19. <body> 
  20.     <div id="app"></div> 
  21.     <script src="./vdom.js"></script> 
  22.     <script> 
  23.         function render() { 
  24.             return h('div', { 
  25.                 style: useObjStr({ 
  26.                     'color''#ccc'
  27.                     'font-size''20px' 
  28.                 }) 
  29.             }, [ 
  30.                 h('div', {}, [h('span', { 
  31.                     onClick: () => { 
  32.                         alert('1'); 
  33.                     } 
  34.                 }, '文本'), h('a', { 
  35.                     href: 'https://www.baidu.com'
  36.                     class: 'main main1' 
  37.                 }, '點(diǎn)擊'
  38.                 ]), 
  39.             ]) 
  40.         } 
  41.          
  42.         // 頁面改變 
  43.         function render1() { 
  44.             return h('div', { 
  45.                 style: useStyleStr({ 
  46.                     'color''#ccc'
  47.                     'font-size''20px' 
  48.                 }) 
  49.             }, [ 
  50.                 h('div', {}, [h('span', { 
  51.                     onClick: () => { 
  52.                         alert('1'); 
  53.                     } 
  54.                 }, '文本改變了'
  55.                 ]), 
  56.             ]) 
  57.         } 
  58.  
  59.         // 首次加載 
  60.         mountNode(render, '#app'); 
  61.  
  62.         // 狀態(tài)改變 
  63.         setTimeout(()=>{ 
  64.             mountNode(render1, '#app'); 
  65.         },3000) 
  66.     </script> 
  67. </body> 
  68.  
  69. </html> 

vdom.js

  1.  // vdom --- 
  2.  function h(tag, props, children) { 
  3.     return { tag, props, children }; 
  4.  
  5. function mount(vnode, container, anchor) { 
  6.     const el = document.createElement(vnode.tag); 
  7.     vnode.el = el; 
  8.     // props 
  9.     if (vnode.props) { 
  10.         for (const key in vnode.props) { 
  11.             if (key.startsWith('on')) { 
  12.                 el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key],{ 
  13.                     passive:true 
  14.                 }) 
  15.             } else { 
  16.                 el.setAttribute(key, vnode.props[key]); 
  17.             } 
  18.         } 
  19.     } 
  20.     if (vnode.children) { 
  21.         if (typeof vnode.children === "string") { 
  22.             el.textContent = vnode.children; 
  23.         } else { 
  24.             vnode.children.forEach(child => { 
  25.                 mount(child, el); 
  26.             }); 
  27.         } 
  28.     } 
  29.     if (anchor) { 
  30.         container.insertBefore(el, anchor); 
  31.     } else { 
  32.         container.appendChild(el); 
  33.     } 
  34.  
  35. // processing strings 
  36. function useStyleStr(obj) { 
  37.     const reg = /^{|}/g; 
  38.     const reg1 = new RegExp('"',"g"); 
  39.     const str = JSON.stringify(obj); 
  40.     const ustr = str.replace(reg, '').replace(','';').replace(reg1,''); 
  41.     return ustr; 
  42.  
  43. function patch(n1, n2) { 
  44.     // Implement this 
  45.     // 1. check if n1 and n2 are of the same type 
  46.     if (n1.tag !== n2.tag) { 
  47.         // 2. if notreplace 
  48.         const parent = n1.el.parentNode; 
  49.         const anchor = n1.el.nextSibling; 
  50.         parent.removeChild(n1.el); 
  51.         mount(n2, parent, anchor); 
  52.         return 
  53.     } 
  54.  
  55.     const el = n2.el = n1.el; 
  56.  
  57.     // 3. if yes 
  58.     // 3.1 diff props 
  59.     const oldProps = n1.props || {}; 
  60.     const newProps = n2.props || {}; 
  61.     for (const key in newProps) { 
  62.         const newValue = newProps[key]; 
  63.         const oldValue = oldProps[key]; 
  64.         if (newValue !== oldValue) { 
  65.             if (newValue != null) { 
  66.                 el.setAttribute(key, newValue); 
  67.             } else { 
  68.                 el.removeAttribute(key); 
  69.             } 
  70.         } 
  71.     } 
  72.     for (const key in oldProps) { 
  73.         if (!(key in newProps)) { 
  74.             el.removeAttribute(key); 
  75.         } 
  76.     } 
  77.     // 3.2 diff children 
  78.     const oc = n1.children; 
  79.     const nc = n2.children; 
  80.     if (typeof nc === 'string') { 
  81.         if (nc !== oc) { 
  82.             el.textContent = nc; 
  83.         } 
  84.     } else if (Array.isArray(nc)) { 
  85.         if (Array.isArray(oc)) { 
  86.             // array diff 
  87.             const commonLength = Math.min(oc.length, nc.length); 
  88.             for (let i = 0; i < commonLength; i++) { 
  89.                 patch(oc[i], nc[i]); 
  90.             } 
  91.             if (nc.length > oc.length) { 
  92.                 nc.slice(oc.length).forEach(c => mount(c, el)); 
  93.             } else if (oc.length > nc.length) { 
  94.                 oc.slice(nc.length).forEach(c => { 
  95.                     el.removeChild(c.el); 
  96.                 }) 
  97.             } 
  98.         } else { 
  99.             el.innerHTML = ''
  100.             nc.forEach(c => mount(c, el)); 
  101.         } 
  102.     } 
  103.  
  104. let isMounted = false
  105. let oldTree; 
  106.  
  107. // Mount node 
  108. function mountNode(render, selector) { 
  109.     if (!isMounted) { 
  110.         mount(oldTree = render(), document.querySelector(selector)); 
  111.         isMounted = true
  112.     } else { 
  113.         const newTree = render(); 
  114.         patch(oldTree, newTree); 
  115.         oldTree = newTree; 
  116.     } 
  117.  

 

責(zé)任編輯:姜華 來源: 前端歷劫之路
相關(guān)推薦

2022-05-06 19:42:53

DOM

2020-10-12 08:56:47

Virtual dom

2021-05-26 05:22:09

Virtual DOMSnabbdom虛擬DOM

2021-07-04 10:07:04

Virtual DO閱讀源碼虛擬DOM

2024-10-15 09:48:56

2021-01-11 07:51:16

DOM對(duì)象節(jié)點(diǎn)樹

2021-06-25 06:47:38

VueVue2.x迷你版響應(yīng)式原理

2022-01-26 14:29:04

區(qū)塊鏈加密貨幣技術(shù)

2021-01-18 07:15:22

虛擬DOM真實(shí)DOMJavaScript

2011-12-18 12:36:59

摩托

2021-02-02 07:37:39

NextTickvueDOM

2014-05-26 16:16:59

Shadow DomWeb Compone

2018-06-26 14:29:44

LinuxUnix不同

2023-12-01 15:39:13

Linux操作系統(tǒng)

2019-10-14 10:09:33

Wi-Fi 6Wi-Fi無線網(wǎng)絡(luò)

2021-09-06 10:45:18

XDRMDR

2022-09-14 09:45:15

指標(biāo)標(biāo)簽

2012-07-25 15:45:28

ERPSCM

2024-02-26 07:36:09

lockJava語言

2022-09-01 21:02:31

手機(jī)衛(wèi)星5G
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)