摘要:第一篇文章中主要講解了虛擬基本實(shí)現(xiàn),簡(jiǎn)單的回顧一下,虛擬是使用數(shù)據(jù)描述的一段虛擬節(jié)點(diǎn)樹,通過函數(shù)生成其真實(shí)節(jié)點(diǎn)。并添加到其對(duì)應(yīng)的元素容器中。在創(chuàng)建真實(shí)節(jié)點(diǎn)的同時(shí)并為其注冊(cè)事件并添加一些附屬屬性。
第一篇文章中主要講解了虛擬DOM基本實(shí)現(xiàn),簡(jiǎn)單的回顧一下,虛擬DOM是使用json數(shù)據(jù)描述的一段虛擬Node節(jié)點(diǎn)樹,通過render函數(shù)生成其真實(shí)DOM節(jié)點(diǎn)。并添加到其對(duì)應(yīng)的元素容器中。在創(chuàng)建真實(shí)DOM節(jié)點(diǎn)的同時(shí)并為其注冊(cè)事件并添加一些附屬屬性。
虛擬Dom詳解 - (一)
在上篇文章中也曾經(jīng)提到過,當(dāng)狀態(tài)變更的時(shí)候用修改后的新渲染的的JavaScript對(duì)象和舊的虛擬DOM的JavaScript對(duì)象作對(duì)比,記錄著兩棵樹的差異,把差別反映到真實(shí)的DOM結(jié)構(gòu)上最后操作真正的DOM的時(shí)候只操作有差異的部分的更改。然而上篇文章中也只是簡(jiǎn)簡(jiǎn)單單的提到過一句卻沒有進(jìn)行實(shí)質(zhì)性的實(shí)現(xiàn),這篇文章主要講述一下虛擬DOM是如何做出更新的。那就開始吧...O(∩_∩)O
在虛擬DOM中實(shí)現(xiàn)更新的話是使用DIFF算法進(jìn)行更新的,我想大多數(shù)小伙伴都應(yīng)該聽說過這個(gè)詞,DIFF是整個(gè)虛擬DOM部分最核心的部分,因?yàn)楫?dāng)虛擬DOM節(jié)點(diǎn)狀態(tài)發(fā)生改變以后不可能去替換整個(gè)DOM節(jié)點(diǎn)樹,若是這樣的話會(huì)出現(xiàn)打兩個(gè)DOM操作,無非是對(duì)性能的極大影響,真的如此的話還不如直接操作DOM來的實(shí)際一些。
第一篇文章中是通過render對(duì)虛擬DOM節(jié)點(diǎn)樹進(jìn)行渲染的,但是在render函數(shù)中只做了一件事情,只是對(duì)虛擬DOM進(jìn)行了新建也就是初始化工作,其實(shí)回過頭來想一下,無論是新建操作還是修改操作,都應(yīng)該通過render函數(shù)來做,在react中所有的DOM渲染都是通過其中的render函數(shù)完成的,那么也就得出了這個(gè)結(jié)論。
// 渲染虛擬DOM // 虛擬DOM節(jié)點(diǎn)樹 // 承載DOM節(jié)點(diǎn)的容器,父元素 function render(vnode,container) { // 首次渲染 mount(vnode,container); };
既然更新和創(chuàng)建操作都是通過render函數(shù)來做的,在方法中又應(yīng)該如何區(qū)分當(dāng)前的操作到底是新建還是更新呢?畢竟在react我們并沒有給出明確的標(biāo)識(shí)來告訴其方法,當(dāng)前是進(jìn)行的哪個(gè)操作。在執(zhí)行render函數(shù)的時(shí)候有兩個(gè)參數(shù),一個(gè)是傳入的vnode節(jié)點(diǎn)樹,還有一個(gè)就是承載真實(shí)DOM節(jié)點(diǎn)的容器,其實(shí)我們可以把其虛擬DOM節(jié)點(diǎn)樹掛載在其容器中,若容器中存在其節(jié)點(diǎn)樹則是更新操作,反之則是新建操作。
// 渲染虛擬DOM // 虛擬DOM節(jié)點(diǎn)樹 // 承載DOM節(jié)點(diǎn)的容器,父元素 function render(vnode, container) { if (!container.vnode) { // 首次渲染 mount(vnode, container); } else { // 舊的虛擬DOM節(jié)點(diǎn) // 新的DOM節(jié)點(diǎn) // 承載DOM節(jié)點(diǎn)的容器 patch(container.vnode, vnode, container); } container.vnode = vnode; };
既然已經(jīng)確定了現(xiàn)在的render函數(shù)所需要進(jìn)行的操作了,那么接下來就應(yīng)該進(jìn)行下一步操作了,如果想要做更新的話必須要知道如下幾個(gè)參數(shù),原有的虛擬DOM節(jié)點(diǎn)是什么樣的,新的虛擬DOM又是什么樣的,上一步操作中我們已經(jīng)把原有的虛擬DOM節(jié)點(diǎn)已經(jīng)保存在了父容器中,直接使用即可。
// 更新函數(shù) // 舊的虛擬DOM節(jié)點(diǎn) // 新的DOM節(jié)點(diǎn) // 承載DOM節(jié)點(diǎn)的容器 function patch(oldVNode, newVNode, container) { // 新節(jié)點(diǎn)的VNode類型 let newVNodeFlag = newVNode.flag; // 舊節(jié)點(diǎn)的VNode類型 let oldVNodeFlag = oldVNode.flag; // 如果新節(jié)點(diǎn)與舊節(jié)點(diǎn)的類型不一致 // 如果不一致的情況下,相當(dāng)于其節(jié)點(diǎn)發(fā)生了變化 // 直接進(jìn)行替換操作即可 // 這里判斷的是如果一個(gè)是 TEXT 一個(gè)是 Element // 類型判斷 if (newVNodeFlag !== oldVNodeFlag) { replaceVNode(oldVNode, newVNode, container); } // 由于在新建時(shí)創(chuàng)建Element和Text的時(shí)候使用的是兩個(gè)函數(shù)進(jìn)行操作的 // 在更新的時(shí)候也是同理的 // 也應(yīng)該針對(duì)不同的修改進(jìn)行不同的操作 // 如果新節(jié)點(diǎn)與舊節(jié)點(diǎn)的HTML相同 else if (newVNodeFlag == vnodeTypes.HTML) { // 替換元素操作 patchMethos.patchElement(oldVNode, newVNode, container); } // 如果新節(jié)點(diǎn)與舊節(jié)點(diǎn)的TEXT相同 else if (newVNodeFlag == vnodeTypes.TEXT) { // 替換文本操作 patchMethos.patchText(oldVNode, newVNode, container); } } // 更新VNode方法集 const patchMethos = { // 替換文本操作 // 舊的虛擬DOM節(jié)點(diǎn) // 新的DOM節(jié)點(diǎn) // 承載DOM節(jié)點(diǎn)的容器 patchText(oldVNode,newVNode,container){ // 獲取到el,并將 oldVNode 賦值給 newVNode let el = (newVNode.el = oldVNode.el); // 如果 newVNode.children 不等于 oldVNode.children // 其他情況就是相等則沒有任何操作,不需要更新 if(newVNode.children !== oldVNode.children){ // 直接進(jìn)行替換操作 el.nodeValue = newVNode.children; } } }; // 替換虛擬DOM function replaceVNode(oldVNode, newVNode, container) { // 在原有節(jié)點(diǎn)中刪除舊節(jié)點(diǎn) container.removeChild(oldVNode.el); // 重新渲染新節(jié)點(diǎn) mount(newVNode, container); }
上述方法簡(jiǎn)單的實(shí)現(xiàn)了對(duì)Text更新的一個(gè)替換操作,由于Text替換操作比較簡(jiǎn)單,所以這里就先實(shí)現(xiàn),僅僅完成了對(duì)Text的更新是遠(yuǎn)遠(yuǎn)不夠的,當(dāng)Element進(jìn)行操作的時(shí)也是需要更新的。相對(duì)來說Text的更新要比Element更新要簡(jiǎn)單很多的,Element更新比較復(fù)雜所以放到了后面,因?yàn)楸容^重要嘛,哈哈~
首先想要進(jìn)行Element替換之前要確定哪些Data數(shù)據(jù)進(jìn)行了變更,然后才能對(duì)其進(jìn)行替換操作,這樣的話需要確定要更改的數(shù)據(jù),然后替換掉原有數(shù)據(jù),才能進(jìn)行下一步更新操作。
// 更新VNode方法集 const patchMethos = { // 替換元素操作 // 舊的虛擬DOM節(jié)點(diǎn) // 新的DOM節(jié)點(diǎn) // 承載DOM節(jié)點(diǎn)的容器 patchElement(oldVNode,newVNode,container){ // 如果 newVNode 的標(biāo)簽名稱與 oldVNode 標(biāo)簽名稱不一樣 // 既然標(biāo)簽都不一樣則直接替換就好了,不需要再進(jìn)行其他多余的操作 if(newVNode.tag !== oldVNode.tag){ replaceVNode(oldVNode,newVNode,container); return; } // 更新el let el = (newVNode.el = oldVNode.el); // 獲取舊的Data數(shù)據(jù) let oldData = oldVNode.data; // 獲取新的Data數(shù)據(jù) let newData = newVNode.data; // 如果新的Data數(shù)據(jù)存在 // 進(jìn)行更新和新增 if(newData){ for(let attr in newData){ let oldVal = oldData[attr]; let newVal = newData[attr]; domAttributeMethod.patchData(el,attr,oldVal,newVal); } } // 如果舊的Data存在 // 檢測(cè)更新 if(oldData){ for(let attr in oldData){ let oldVal = oldData[attr]; let newVal = newData[attr]; // 如果舊數(shù)據(jù)存在,新數(shù)據(jù)中不存在 // 則表示已刪除,需要進(jìn)行更新操作 if(oldVal && !newVal.hasOwnProperty(attr)){ // 既然新數(shù)據(jù)中不存在,則新數(shù)據(jù)則傳入Null domAttributeMethod.patchData(el,attr,oldVal,null); } } } } }; // dom添加屬性方法 const domAttributeMethod = { // 修改Data數(shù)據(jù)方法 patchData (el,key,prv,next){ switch(key){ case "style": this.setStyle(el,key,prv,next); // 添加了這里,看我看我 (●"?"●) // 添加遍歷循環(huán) // 循環(huán)舊的data this.setOldVal(el,key,prv,next); break; case "class": this.setClass(el,key,prv,next); break; default : this.defaultAttr(el,key,prv,next); break; } }, // 遍歷舊數(shù)據(jù) setOldVal(el,key,prv,next){ // 遍歷舊數(shù)據(jù) for(let attr in prv){ // 如果舊數(shù)據(jù)存在,新數(shù)據(jù)中不存在 if(!next.hasOwnProperty(attr)){ // 直接賦值為字符串 el.style[attr] = ""; } } }, // 修改事件注冊(cè)方法 addEvent(el,key,prev,next){ // 添加了這里,看我看我 (●"?"●) // prev 存在刪除原有事件,重新綁定新的事件 if(prev){ el.removeEventListener(key.slice(1),prev); } if(next){ el.addEventListener(key.slice(1),next); } } }
上面的操作其實(shí)只是替換Data部分,但是其子元素沒有進(jìn)行替換,所以還需要對(duì)子元素進(jìn)行替換處理。替換子元素有共分為6種情況:
舊元素只有一個(gè)
舊元素為空
舊元素為多個(gè)
新元素只有一個(gè)
新元素為空
新元素為多個(gè)
// 更新VNode方法集 const patchMethos = { // 替換元素操作 // 舊的虛擬DOM節(jié)點(diǎn) // 新的DOM節(jié)點(diǎn) // 承載DOM節(jié)點(diǎn)的容器 patchElement(oldVNode,newVNode,container){ // 如果 newVNode 的標(biāo)簽名稱與 oldVNode 標(biāo)簽名稱不一樣 // 既然標(biāo)簽都不一樣則直接替換就好了,不需要再進(jìn)行其他多余的操作 if(newVNode.tag !== oldVNode.tag){ replaceVNode(oldVNode,newVNode,container); return; } // 更新el let el = (newVNode.el = oldVNode.el); // 獲取舊的Data數(shù)據(jù) let oldData = oldVNode.data; // 獲取新的Data數(shù)據(jù) let newData = newVNode.data; // 如果新的Data數(shù)據(jù)存在 // 進(jìn)行更新和新增 if(newData){ for(let attr in newData){ let oldVal = oldData[attr]; let newVal = newData[attr]; domAttributeMethod.patchData(el,attr,oldVal,newVal); } } // 如果舊的Data存在 // 檢測(cè)更新 if(oldData){ for(let attr in oldData){ let oldVal = oldData[attr]; let newVal = newData[attr]; // 如果舊數(shù)據(jù)存在,新數(shù)據(jù)中不存在 // 則表示已刪除,需要進(jìn)行更新操作 if(oldVal && !newVal.hasOwnProperty(attr)){ // 既然新數(shù)據(jù)中不存在,則新數(shù)據(jù)則傳入Null domAttributeMethod.patchData(el,attr,oldVal,null); } } } // 添加了這里 // 更新子元素 // 舊子元素類型 // 新子元素類型 // 舊子元素的children // 新子元素的children // el元素,容器 this.patchChildren( oldVNode.childrenFlag, newVNode.childrenFlag, oldVNode.children, newVNode.children, el, ); }, // 更新子元素 // 舊子元素類型 // 新子元素類型 // 舊子元素的children // 新子元素的children // el元素,容器 patchChildren(...arg){ let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg; switch(oldChildrenFlag){ // 如果舊元素的子元素為一個(gè) case childTeyps.SINGLE: this.upChildSingle(...arg); break; // 如果舊元素的子元素為空 case childTeyps.EMPTY: this.upChildEmpty(...arg); break; // 如果舊元素的子元素為多個(gè) case childTeyps.MULTIPLE: this.upChildMultiple(...arg); break; } }, upChildSingle(...arg){ let [oldChildrenFlag,newChildrenFlag,oldChildren,newChildren,container] = arg; // 循環(huán)新的子元素 switch(newChildrenFlag){ // 如果新元素的子元素為一個(gè) case childTeyps.SINGLE: patch(oldChildren,newChildren,container); break; // 如果新元素的子元素為空 case childTeyps.EMPTY: container.removeChild(oldChildren.el); break; // 如果新元素的子元素多個(gè) case childTeyps.MULTIPLE: container.removeChild(oldChildren.el); for(let i = 0;i上面代碼比較亂,因?yàn)榍短琢硕鄬友h(huán),大致邏輯就是使用上述六種情況一一對(duì)接配對(duì)并且使用其對(duì)應(yīng)的解決方案。
上述六中情況,switch匹配邏輯:
新數(shù)據(jù) 舊數(shù)據(jù) 舊元素只有一個(gè) 新元素只有一個(gè) 舊元素只有一個(gè) 新元素為空 舊元素只有一個(gè) 新元素為多個(gè) 舊元素為空 新元素只有一個(gè) 舊元素為空 新元素為空 舊元素為空 新元素為多個(gè) 舊元素為多個(gè) 新元素只有一個(gè) 舊元素為多個(gè) 新元素為空 舊元素為多個(gè) 新元素為多個(gè) 最為復(fù)雜的就是最后一種情況,新舊元素各為多個(gè),然而對(duì)于這一部分react和vue的處理方式都是不一樣的。以下借鑒的是react的diff算法。
在進(jìn)行虛擬DOM替換時(shí),當(dāng)元素之間的順序沒有發(fā)生變化則原有元素是不需要進(jìn)行任何改動(dòng)的,也就是說,若原有順序是123456,新順序?yàn)?b>654321則他們之間的順序發(fā)生了變化這個(gè)時(shí)候需要對(duì)其進(jìn)行變更處理,若其順序出現(xiàn)了插入情況192939495969在每個(gè)數(shù)字后面添加了一個(gè)9,其實(shí)這個(gè)時(shí)候也是不需要進(jìn)行更新操作的,其實(shí)他們之間的順序還是和原來一致,只是添加了一些元素值而已,如果變成了213456,這是時(shí)候只需要改變12就好,其他的是不需要做任何改動(dòng)的。 接下來需要添加最關(guān)鍵的邏輯了。
// 更新VNode方法集 // 添加 oldMoreAndNewMore 方法 const patchMethos = { upChildMultiple(...arg) { let [oldChildrenFlag, newChildrenFlag, oldChildren, newChildren, container] = arg; // 循環(huán)新的子元素 switch (newChildrenFlag) { // 如果新元素的子元素為一個(gè) case childTeyps.SINGLE: for (let i = 0; i < oldChildren.length; i++) { // 遍歷刪除舊元素 container.removeChild(oldChildren[i].el); } // 添加新元素 mount(newChildren, container); break; // 如果新元素的子元素為空 case childTeyps.EMPTY: for (let i = 0; i < oldChildren.length; i++) { // 刪除所有子元素 container.removeChild(oldChildren[i].el); } break; // 如果新元素的子元素多個(gè) case childTeyps.MULTIPLE: // 修改了這里 (●"?"●) this.oldMoreAndNewMore(...arg); break; }, oldMoreAndNewMore(...arg) { let [oldChildrenFlag, newChildrenFlag, oldChildren, newChildren, container] = arg; let lastIndex = 0; for (let i = 0; i < newChildren.length; i++) { let newVnode = newChildren[i]; let j = 0; // 新的元素是否找到 let find = false; for (; j < oldChildren.length; j++) { let oldVnode = oldChildren[j]; // key相同為同一個(gè)元素 if (oldVnode.key === newVnode.key) { find = true; patch(oldVnode, newVnode, container); if (j < lastIndex) { if(newChildren[i-1].el){ // 需要移動(dòng) let flagNode = newChildren[i-1].el.nextSibling; container.insertBefore(oldVnode.el, flagNode); } break; } else { lastIndex = j; } } } // 如果沒有找到舊元素,需要新增 if (!find) { // 需要插入的標(biāo)志元素 let flagNode = i === 0 ? oldChildren[0].el : newChildren[i-1].el; mount(newVnode, container, flagNode); } // 移除元素 for (let i = 0; i < oldChildren.length; i++) { // 舊節(jié)點(diǎn) const oldVNode = oldChildren[i]; // 新節(jié)點(diǎn)key是否在舊節(jié)點(diǎn)中存在 const has = newChildren.find(next => next.key === oldVNode.key); if (!has) { // 如果不存在刪除 container.removeChild(oldVNode.el) } } } } }; // 修改mount函數(shù) // flagNode 標(biāo)志node 新元素需要插入到哪里 function mount(vnode, container, flagNode) { // 所需渲染標(biāo)簽類型 let { flag } = vnode; // 如果是節(jié)點(diǎn) if (flag === vnodeTypes.HTML) { // 調(diào)用創(chuàng)建節(jié)點(diǎn)方法 mountMethod.mountElement(vnode, container, flagNode); } // 如果是文本 else if (flag === vnodeTypes.TEXT) { // 調(diào)用創(chuàng)建文本方法 mountMethod.mountText(vnode, container); }; }; // 修改mountElement const mountMethod = { // 創(chuàng)建HTML元素方法 // 修改了這里 (●"?"●) 添加 flagNode 參數(shù) mountElement(vnode, container, flagNode) { // 屬性,標(biāo)簽名,子元素,子元素類型 let { data, tag, children, childrenFlag } = vnode; // 創(chuàng)建的真實(shí)節(jié)點(diǎn) let dom = document.createElement(tag); // 添加屬性 data && domAttributeMethod.addData(dom, data); // 在VNode中保存真實(shí)DOM節(jié)點(diǎn) vnode.el = dom; // 如果不為空,表示有子元素存在 if (childrenFlag !== childTeyps.EMPTY) { // 如果為單個(gè)元素 if (childrenFlag === childTeyps.SINGLE) { // 把子元素傳入,并把當(dāng)前創(chuàng)建的DOM節(jié)點(diǎn)以父元素傳入 // 其實(shí)就是要把children掛載到 當(dāng)前創(chuàng)建的元素中 mount(children, dom); } // 如果為多個(gè)元素 else if (childrenFlag === childTeyps.MULTIPLE) { // 循環(huán)子節(jié)點(diǎn),并創(chuàng)建 children.forEach((el) => mount(el, dom)); }; }; // 添加元素節(jié)點(diǎn) 修改了這里 (●"?"●) flagNode ? container.insertBefore(dom, flagNode) : container.appendChild(dom); } }最終使用:
const VNODEData = [ "div", {id:"test",key:789}, [ createElement("p",{ key:1, style:{ color:"red", background:"pink" } },"節(jié)點(diǎn)一"), createElement("p",{ key:2, "@click":() => console.log("click me!!!") },"節(jié)點(diǎn)二"), createElement("p",{ key:3, class:"active" },"節(jié)點(diǎn)三"), createElement("p",{key:4},"節(jié)點(diǎn)四"), createElement("p",{key:5},"節(jié)點(diǎn)五") ] ]; let VNODE = createElement(...VNODEData); render(VNODE,document.getElementById("app")); const VNODEData1 = [ "div", {id:"test",key:789}, [ createElement("p",{ key:6 },"節(jié)點(diǎn)六"), createElement("p",{ key:1, style:{ color:"red", background:"pink" } },"節(jié)點(diǎn)一"), createElement("p",{ key:5 },"節(jié)點(diǎn)五"), createElement("p",{ key:2 },"節(jié)點(diǎn)二"), createElement("p",{ key:4 },"節(jié)點(diǎn)四"), createElement("p",{ key:3, class:"active" },"節(jié)點(diǎn)三") ] ]; setTimeout(() => { let VNODE = createElement(...VNODEData1); render(VNODE,document.getElementById("app")); },1000)上面代碼用了大量的邏輯來處理其中使用大量計(jì)算,會(huì)比較兩棵樹之間的同級(jí)節(jié)點(diǎn)。這樣就徹底的降低了復(fù)雜度,并且不會(huì)帶來什么損失。因?yàn)樵趙eb應(yīng)用中不太可能把一個(gè)組件在DOM樹中跨層級(jí)地去移動(dòng)。
在計(jì)算中會(huì)盡可能的引用之前的元素,進(jìn)行位置替換,其實(shí)無論是React還是Vue在渲染列表的時(shí)候需要給其元素賦值一個(gè)key屬性,因?yàn)樵谶M(jìn)行diff算法時(shí),會(huì)優(yōu)先使用其原有元素,進(jìn)行位置調(diào)整,也是對(duì)性能優(yōu)化的一大亮點(diǎn)。
結(jié)語
本文也只是對(duì)diff算法的簡(jiǎn)單實(shí)現(xiàn),也許不能滿足所有要求,React的基本實(shí)現(xiàn)原理則是如此,希望這篇文章能對(duì)大家理解diff算法有所幫助。
非常感謝大家用這么長(zhǎng)時(shí)間來閱讀本文章,文章中代碼篇幅過長(zhǎng),若有錯(cuò)誤請(qǐng)?jiān)谠u(píng)論區(qū)指出,我會(huì)及時(shí)做出改正。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/106751.html
摘要:為此也做了一些學(xué)習(xí)簡(jiǎn)單的侃一侃虛擬到底是什么虛擬詳解二什么是虛擬虛擬首次產(chǎn)生是框架最先提出和使用的,其卓越的性能很快得到廣大開發(fā)者的認(rèn)可,繼之后也在其核心引入了虛擬的概念。所謂的虛擬到底是什么也就是通過語言來描述一段代碼。 隨著Vue和React的風(fēng)聲水起,伴隨著諸多框架的成長(zhǎng),虛擬DOM漸漸成了我們經(jīng)常議論和討論的話題。什么是虛擬DOM,虛擬DOM是如何渲染的,那么Vue的虛擬Dom...
摘要:打開瀏覽器輸入,會(huì)看到構(gòu)建的項(xiàng)目的主頁目錄結(jié)構(gòu)使用編輯器打開推薦使用,下面具體看看目錄結(jié)構(gòu)在中,根據(jù)我們?cè)跇?gòu)建項(xiàng)目的時(shí)候的選項(xiàng),有以下幾個(gè)命令。 構(gòu)建一個(gè) vue 項(xiàng)目最簡(jiǎn)單的方式就是使用腳手架工具 vue-cli 。前端的三大框架都有自己的腳手架工具,其作用就是用配置好的模板迅速搭建起一個(gè)項(xiàng)目工程來,省去自己配置 webpack 配置文件的基本內(nèi)容,大大降低了初學(xué)者構(gòu)建項(xiàng)目的難度。這...
摘要:添加事件偵聽器時(shí)使用模式。只當(dāng)事件是從偵聽器綁定的元素本身觸發(fā)時(shí)才觸發(fā)回調(diào)。只當(dāng)點(diǎn)擊鼠標(biāo)右鍵時(shí)觸發(fā)只當(dāng)點(diǎn)擊鼠標(biāo)中鍵時(shí)觸發(fā)以模式添加偵聽器,減少額外的監(jiān)聽,提高性能表示永遠(yuǎn)不會(huì)調(diào)用。記住,指令函數(shù)能夠接受所有合法的表達(dá)式。 思維導(dǎo)圖 showImg(https://segmentfault.com/img/bVbphXZ?w=1920&h=2408); 指令 v-for 關(guān)于key 官方...
摘要:添加事件偵聽器時(shí)使用模式。只當(dāng)事件是從偵聽器綁定的元素本身觸發(fā)時(shí)才觸發(fā)回調(diào)。只當(dāng)點(diǎn)擊鼠標(biāo)右鍵時(shí)觸發(fā)只當(dāng)點(diǎn)擊鼠標(biāo)中鍵時(shí)觸發(fā)以模式添加偵聽器,減少額外的監(jiān)聽,提高性能表示永遠(yuǎn)不會(huì)調(diào)用。記住,指令函數(shù)能夠接受所有合法的表達(dá)式。 思維導(dǎo)圖 showImg(https://segmentfault.com/img/bVbphXZ?w=1920&h=2408); 指令 v-for 關(guān)于key 官方...
摘要:添加事件偵聽器時(shí)使用模式。只當(dāng)事件是從偵聽器綁定的元素本身觸發(fā)時(shí)才觸發(fā)回調(diào)。只當(dāng)點(diǎn)擊鼠標(biāo)右鍵時(shí)觸發(fā)只當(dāng)點(diǎn)擊鼠標(biāo)中鍵時(shí)觸發(fā)以模式添加偵聽器,減少額外的監(jiān)聽,提高性能表示永遠(yuǎn)不會(huì)調(diào)用。記住,指令函數(shù)能夠接受所有合法的表達(dá)式。 思維導(dǎo)圖 showImg(https://segmentfault.com/img/bVbphXZ?w=1920&h=2408); 指令 v-for 關(guān)于key 官方...
閱讀 1104·2022-06-21 15:13
閱讀 1923·2021-10-20 13:48
閱讀 1098·2021-09-22 15:47
閱讀 1425·2019-08-30 15:55
閱讀 3185·2019-08-30 15:53
閱讀 578·2019-08-29 12:33
閱讀 777·2019-08-28 18:15
閱讀 3537·2019-08-26 13:58