日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

Vue源码分析系列四:Virtual DOM

發布時間:2023/12/31 vue 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue源码分析系列四:Virtual DOM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

當我們操作Dom其實是一件非常耗性能的事,每個元素都涵蓋了許多的屬性,因為瀏覽器的標準就把 DOM 設計的非常復雜。而Virtual Dom就是用一個原生的JS對象去描述一個DOM節點,即VNode,所以它比創建一個真實的Dom元素所產生代價要小得多。而我們主流的框架React和Vue正是采用了這種做法,那我們來看下如何實現一個簡單的Virtual Dom。完整代碼GitHub。喜歡的話希望點個小星星哦 ^_^~~~

核心

  • 用 JavaScript 對象結構表示 DOM 樹的結構;然后用這個樹構建一個真正的 DOM 樹
  • 當狀態變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異
  • 把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,視圖就更新了
  • 構建vDOM

    首先我們需要構建vDom, 用js對象來描述真正的dom tree,構建好了vDom之后就需要將其render到我們的頁面上了

    // createElement.js// give some default value. export default (tagName, {attrs = {}, children = []} = {}) => {return {tagName,attrs,children} }// main.jsimport createElement from './vdom/createElement'const createVApp = (count) => createElement('div', {attrs: {id: 'app',dataCount: count},children: [createElement('input'), // dom重繪使得Input失焦String(count), // 文本節點createElement('img', {attrs: {src: 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1555610261877&di=6619e67b4f45768a359a296c55ec1cc3&imgtype=0&src=http%3A%2F%2Fimg.bimg.126.net%2Fphoto%2Fmr7DezX-Q4GLNBM_VPVaWA%3D%3D%2F333829322379622331.jpg'}})] })let count = 0; let vApp = createVApp(count);復制代碼

    下面這個就是構建的 vDom 啦!

    然后我我們看看render 方法,這個方法就是將我們的 vDom 轉化成真是的 element.

    // render.jsconst renderElem = ({ tagName, attrs, children}) => {// create root elementlet $el = document.createElement(tagName);// set attributedsfor (const [k, v] of Object.entries(attrs)) {$el.setAttribute(k, v);}// set children (Array)for (const child of children) {const $child = render(child);$el.appendChild($child);}return $el; }const render = (vNode) => {// if element node is text, and createTextNodeif (typeof vNode === 'string') {return document.createTextNode(vNode);}// otherwise return renderElemreturn renderElem(vNode); }export default render復制代碼

    然后我們回到main.js中

    // 引入 render.js 模塊const $app = render(vApp); // 開始構建真實的domlet $rootEl = mount($app, document.getElementById('app'));// 創建 mount.jsexport default ($node, $target) => {// use $node element replace $target element!$target.replaceWith($node);return $node; } 復制代碼

    最后你就可以看到效果了. 是不是很帥 ? O(∩_∩)O哈哈 ~~~~

    現在我們來做一些好玩的事兒?;氐?main.js 中,我們加入如下這段代碼:

    setInterval(() => {count++;$rootEl = mount(render(createVApp(count)), $rootEl); // $rootEl 就是整顆real dom }, 1000) 復制代碼

    然后回到我們的頁面,發現什么了嗎? 你可以嘗試在 input 里面輸入一些東西,然后發現了什么異常了嗎 ?

    查看源代碼,原來,每隔一秒我們就刷新了一次頁面??墒俏覀冎桓淖兞?count ,就重繪一次頁面,未免也夸張了吧,假如我們填寫一個表單,填的手都要斷了,結果刷新了頁面,你猜會怎么著? 會不會想砸電腦呢 ? 別急,diff 算法能幫我們解決這給令人頭疼的問題 !

    diff

    diff 算法的概念我就在這兒就不介紹了,大家可以在網上搜到很多答案。直接上代碼 !

    // diff.jsimport render from './render'const zip = (xs, ys) => {const zipped = [];for (let i = 0; i < Math.min(xs.length, ys.length); i++) {zipped.push([xs[i], ys[i]]);}return zipped; };const diffAttributes = (oldAttrs, newAttrs) => {const patches = [];// set new attributes// oldAttrs = {dataCount: 0, id: 'app'}// newAttrs = {dataCount: 1, id: 'app'}// Object.entries(newAttrs) => [['dataCount', 1], ['id', 'app']]for(const [k, v] of Object.entries(newAttrs)) {patches.push($node => {$node.setAttribute(k, v);return $node;})}// remove old attributefor(const k in oldAttrs) {if (!(k in newAttrs)) {// $node 是整顆真實的 dom treepatches.push($node => {$node.removeAttribute(k);return $node;}) }}return $node => {for (const patch of patches) {patch($node);}} }const diffChildren = (oldVChildren, newVChildren) => {const childPatches = [];for (const [oldVChild, newVChild] of zip(oldVChildren, newVChildren)) {childPatches.push(diff(oldVChild, newVChild));}const additionalPatches = [];for (const additionalVChild of additionalPatches.slice(oldVChildren.length)) {additionalPatches.push($node => {$node.appendChild(render(additionalVChild));return $node;})}return $parent => {for (const [patch, child] of zip(childPatches, $parent.childNodes)) {patch(child);}for (const patch of additionalPatches) {patch($parent);}return $parent;} }const diff = (vOldNode, vNewNode) => {// remove allif (vNewNode === 'undefined') {return $node => {// Node.remove() 方法,把對象從它所屬的DOM樹中刪除。$node.remove();return undefined;};}// when element is textnode (like count)if (typeof vOldNode === 'string' || typeof vNewNode === 'string') {if (vOldNode !== vNewNode) {return $node => {const $newNode = render(vNewNode);$node.replaceWith($newNode);return $newNode;};} else {return $node => undefined;}}if (vOldNode.tagName !== vNewNode.tagName) {return $node => {const $newNode = render(vNewNode);$node.replaceWith($newNode);return $newNode;};}const patchAttrs = diffAttributes(vOldNode.attrs, vNewNode.attrs);const patchChildren = diffChildren(vOldNode.children, vNewNode.children);return $node => {patchAttrs($node);patchChildren($node);return $node;}; };export default diff;// main.js setInterval(() => {count++;// 每隔一秒,重繪一次頁面,input失焦(缺點)// $rootEl = mount(render(createVApp(count)), $rootEl)// 衍生出 diff 算法const vNewApp = createVApp(count); // 新的 vDomconst patch = diff(vApp, vNewApp); // 對比差異$rootEl = patch($rootEl);vApp = vNewApp; // 每一秒之后都有更新,保存起來以供下次比對。 }, 1000) 復制代碼

    廢話少說,先看效果 (: ~~

    可以發現,input 沒有情況,也就是說頁面沒有刷新,setInterval每次將count++, 頁面上也只更新了變化了的屬性以及文本,這就是diff算法的威力。

    分析一波

    • diff

    diff 函數接收兩個參數,vOldNode 和 vNewNode.

  • 判斷 vNewNode 是不是 undefined,假如整顆樹都給刪了呢 ? 那就 $node.remove() 移出就好了
  • 如果只是改了標簽名,那好辦,直接 render ,然后 replaceWith 就好了。
  • 如果新老節點是 'string' 類型,那還得判斷新老節點是否相等 !
  • 所有得到的差異結果都扔進 patches 中, 注意,是個函數哦 , 接收的參數就是 $rootEl
    • diffAttributes

    比對屬性好辦,就是拿到新的 vDom 的屬性,然后遍歷老的 vDom 的屬性,判斷老的 vDom 的屬性是否存在于新的 vDom 中。關鍵點我將它描述出來

  • Object.entries()方法返回一個給定對象自身可枚舉屬性的鍵值對數組,其排列與使用 for...in 循環遍歷該對象時返回的順序一致(區別在于 for-in 循環也枚舉原型鏈中的屬性)
  • 通過for of 遍歷oldAttrs,拿到所有老的 vDom 中的key
  • 通過 in 操作符 來判斷 2 中的 key 是否存在于 newAttrs 中.
  • 最后返回一個函數,接收 $rootEl,遍歷屬性對比出來的 patches.每一項是一個函數.
    • diffChildren

    最后就是要對比 children 了。

  • 接收倆參數,oldVChildren 和 newVChildren
  • 這里最主要的還是 zip 函數了。得到新老節點的 child, 將每個節點的老的節點和新的節點存放到一個數組中,如圖:
  • 然后遍歷這個 zipped 數組.繼續diff, 并且保存 diff 后的結果
  • for (const [oldVChild, newVChild] of zip(oldVChildren, newVChildren)) {childPatches.push(diff(oldVChild, newVChild)); }復制代碼

    結語

    Virtual DOM 最核心的部分就是 diff 算法了,這里還是比較復雜的,需要多加練習反復琢磨,好了,今天的介紹就到這了,如果喜歡你就點點贊哦 !

    總結

    以上是生活随笔為你收集整理的Vue源码分析系列四:Virtual DOM的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。