成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

虛擬Dom詳解 - (一)

ashe / 2119人閱讀

摘要:為此也做了一些學(xué)習(xí)簡單的侃一侃虛擬到底是什么虛擬詳解二什么是虛擬虛擬首次產(chǎn)生是框架最先提出和使用的,其卓越的性能很快得到廣大開發(fā)者的認(rèn)可,繼之后也在其核心引入了虛擬的概念。所謂的虛擬到底是什么也就是通過語言來描述一段代碼。

隨著VueReact的風(fēng)聲水起,伴隨著諸多框架的成長,虛擬DOM漸漸成了我們經(jīng)常議論和討論的話題。什么是虛擬DOM,虛擬DOM是如何渲染的,那么Vue的虛擬DomReact的虛擬DOM到底有什么區(qū)別等等等...一系列的話題都在不斷的討論中。為此也做了一些學(xué)習(xí)簡單的侃一侃虛擬DOM到底是什么?

虛擬Dom詳解 - (二)

什么是虛擬Dom

虛擬DOM首次產(chǎn)生是React框架最先提出和使用的,其卓越的性能很快得到廣大開發(fā)者的認(rèn)可,繼React之后vue2.0也在其核心引入了虛擬DOM的概念。在沒有虛擬DOM的時(shí)候,我們在創(chuàng)建頁面的時(shí)候一般都是使用HTML標(biāo)簽一個(gè)一個(gè)的去搭建我們的頁面,既然有了DOM節(jié)點(diǎn)以后,為什么不直接使用原生DOM,那么原生DOM到底有什么弊端呢?原因是這個(gè)樣子的,原生DOM中一個(gè)Node節(jié)點(diǎn)有N多的屬性,一旦對(duì)DOM進(jìn)行操作的時(shí)候會(huì)影響頁面性能的核心問題主要在于DOM操作導(dǎo)致了頁面的重繪或重排,為了減少由于重繪和重排對(duì)網(wǎng)頁性能的影響,所以無論在什么項(xiàng)目中盡可能少的去操作DOM節(jié)點(diǎn)是性能優(yōu)化的一大重點(diǎn)。

所謂的虛擬DOM到底是什么?也就是通過JavaScript語言來描述一段HTML代碼。其實(shí)使用JavaScript描述一段HTML代碼是很簡單的:

HTML:

節(jié)點(diǎn)一

JavaScript:

const createElement = () => {
  return {
    "tag":"div",
    "prop":{
      "id":"app"
    },
    "children":[
      {
        "tag":"p",
        "prop":{
          "class":"text"
        },
        "children":["節(jié)點(diǎn)一"]
      }
    ]
  }
}

上面的代碼中,只是簡單的使用了JavaScript語言簡單描述了一下HTML部分相對(duì)應(yīng)的代碼,此時(shí)我們只需要再寫入一個(gè)創(chuàng)建DOM的方法,按照文檔描述將創(chuàng)建好的DOM按照層級(jí)添加到里面頁面中就好了。

上述JavaScript中所描述的數(shù)據(jù)類型也就可以簡單的理解為是虛擬DOM,雖然這個(gè)虛擬DOM是那么的簡陋,但是足可以說明情況啦,像VueReact當(dāng)需要對(duì)頁面進(jìn)行渲染更新的時(shí)候,則是對(duì)比的就是虛擬DOM更新前后的差異只對(duì)有差異的部分進(jìn)行更新,大大減少了對(duì)DOM的操作。這里也就是我們經(jīng)常所說的DIFF算法。

通過上述描述可以總結(jié)得出,由于原生DOM節(jié)點(diǎn)中的屬性和方法過于復(fù)雜,操作時(shí)過于影響性能,所以使用Object來描述頁面中的HTML結(jié)構(gòu),以達(dá)到對(duì)性能的提升。

如何創(chuàng)建虛擬DOM

如果熟悉VueReact的朋友可能會(huì)知道一點(diǎn),首先說下Vue,在使用中Vue中的虛擬DOM是使用template完成的,也就是平時(shí)我們項(xiàng)目中書寫最多的模板,Vue通過vue-loader對(duì)其進(jìn)行編譯處理最后形成我們所需要的虛擬DOM,然而在React中則是不是這樣的,React是沒有template的,React則是使用的是JSX對(duì)進(jìn)行編譯,最后產(chǎn)生虛擬DOM,無論是Vue還是React最終的想要得到的就是虛擬DOM

若想要知道虛擬DOM是如何創(chuàng)建的,那么就可簡單的實(shí)現(xiàn)一下其創(chuàng)建過程,在上面中可以得到一個(gè)描述DOM節(jié)點(diǎn)的數(shù)據(jù)文本,我們可以根據(jù)其需要對(duì)其進(jìn)行創(chuàng)建:

const vnodeTypes = {
  //  HTML節(jié)點(diǎn)類型
  "HTML":"HTML",
  //  文本類型
  "TEXT":"TEXT",
  //  組件類型
  "COMPONENT":"COMPONENT"
};
const childTeyps = {
  //  為空
  "EMPTY":"EMPTY",
  //  單個(gè)
  "SINGLE":"SINGLE",
  //  多個(gè)
  "MULTIPLE":"MULTIPLE"
};
//  新建虛擬DOM
//    所需創(chuàng)建標(biāo)簽名稱
//    標(biāo)簽屬性
//    標(biāo)簽子元素
function createElement (tag,data,children = null){
  //  當(dāng)前元素的標(biāo)簽類型
  let flag;
  //  子元素的標(biāo)簽類型
  let childrenFlag;
  if(typeof tag === "string"){
    //  如果是文本的則認(rèn)為是,普通的HTML標(biāo)簽
    //  將其元素的flag設(shè)置成HTML類型
    flag = vnodeTypes.HTML;
  }else if(typeof tag === "function"){
    //  如果為函數(shù),則認(rèn)為其為組件
    flag = vnodeTypes.COMPONENT;
  }
  else {
    //  否則是文本類型
    flag = vnodeTypes.TEXT;
  };
  //  判斷子元素情況
  if(children === null){
    //  如果 children 為空
    //  則子元素類型為空
    childrenFlag = childTeyps.EMPTY;
  }else if (Array.isArray(children)){
    //  如果 children 為數(shù)組
    //  獲取子元素長度
    let len = children.length;
    //  如果長度存在
    if(len){
      //  則設(shè)置子元素類型為多個(gè)
      childrenFlag = childTeyps.MULTIPLE;
    }else{
      //  否則設(shè)置為空
      childrenFlag = childTeyps.EMPTY;
    }
  }else {
    //  如果存在并且不為空
    //  則設(shè)置為單個(gè)
    childrenFlag = childTeyps.SINGLE;
    //  創(chuàng)建文本類型方法,并將 children 的值轉(zhuǎn)為字符串
    children = createTextVNode(children+"");
  }

  //  返回虛擬DOM
  return {
    flag, //  虛擬DOM類型
    tag,  //  標(biāo)簽
    data, //  虛擬DOM屬性
    children, //  虛擬DOM子節(jié)點(diǎn)
    childrenFlag,  //  虛擬DOM子節(jié)點(diǎn)類型
    el:null   //  掛載元素的父級(jí)
  };
};

//  新建文本類型虛擬DOM
function createTextVNode (text){
  return {
    //  節(jié)點(diǎn)類型設(shè)置為文本
    flag:vnodeTypes.TEXT,
    //  設(shè)置為沒有標(biāo)簽
    tag:null,
    //  沒有任何屬性
    data:null,
    //  子元素類型設(shè)置為單個(gè)
    childrenFlag:childTeyps.EMPTY,
    //  保存子節(jié)點(diǎn)內(nèi)容
    children:text
  };
};

通過上面的代碼可以簡單的實(shí)現(xiàn)對(duì)虛擬DOM的創(chuàng)建,可以通過調(diào)用createElement并傳入用來描述虛擬DOM的對(duì)象,就可以打印出已經(jīng)創(chuàng)建好的虛擬DOM節(jié)點(diǎn):

const VNODEData = [
    "div",
    {id:"test"},
    [
        createElement("p",{},"節(jié)點(diǎn)一")
    ]
];
let div = createElement(...VNODEData);
console.log(div);

結(jié)果:

{
    "flag": "HTML",
    "tag": "div",
    "data": {
        "id": "test"
    },
    "children": [{
        "flag": "HTML",
        "tag": "p",
        "data": {},
        "children": {
            "flag": "TEXT",
            "tag": null,
            "data": null,
            "childrenFlag": "EMPTY"
        },
        "childrenFlag": "SINGLE"
    }],
    "childrenFlag": "MULTIPLE"
}

通過上述方法打印出來的則是按照傳入的描述虛擬DOM的對(duì)象,已經(jīng)創(chuàng)建好了一個(gè)虛擬DOM樹,是不是一件很神奇的事情,其實(shí)仔細(xì)看下代碼也沒有什么特別重要的邏輯,只是該變了數(shù)據(jù)結(jié)構(gòu)而已(可以這樣理解,但是不能對(duì)外這么說,很丟人的,哈哈)。

既然虛擬DOM節(jié)點(diǎn)已經(jīng)出來了,下一步就是如何渲染出虛擬DOM了,渲染虛擬DOM則需要一個(gè)特定的方法,在VueReact中會(huì)在HTML有一個(gè)idapp的真實(shí)DOM節(jié)點(diǎn),最終渲染的時(shí)候被替換成了虛擬DOM節(jié)點(diǎn)生成的真是的DOM節(jié)點(diǎn),接下來就按照這個(gè)思路繼續(xù)實(shí)現(xiàn)一下,在VueReact都有render函數(shù),這里也就同樣使用這個(gè)名稱進(jìn)行命名了,在開始之前,首先要確認(rèn)一點(diǎn)的是,無論是首次渲染還是更新都是通過render函數(shù)來完成的,所以要對(duì)其進(jìn)行判斷,其余的就不多贅述了。

//  渲染虛擬DOM
//    虛擬DOM節(jié)點(diǎn)樹
//    承載DOM節(jié)點(diǎn)的容器,父元素
function render(vnode,container) {
  //  首次渲染
  mount(vnode,container);
};
//  首次渲染
function mount (vnode,container){
  //  所需渲染標(biāo)簽類型
  let {flag} = vnode;
  //  如果是節(jié)點(diǎn)
  if(flag === vnodeTypes.HTML){
    //  調(diào)用創(chuàng)建節(jié)點(diǎn)方法
    mountMethod.mountElement(vnode,container);
  } //  如果是文本
  else if(flag === vnodeTypes.TEXT){
    //  調(diào)用創(chuàng)建文本方法
    mountMethod.mountText(vnode,container);
  };
};
//  創(chuàng)建各種元素的方法
const mountMethod = {
  //  創(chuàng)建HTML元素方法
  mountElement(vnode,container){
    //  屬性,標(biāo)簽名,子元素,子元素類型
    let {tag,children,childrenFlag} = vnode;
    //  創(chuàng)建的真實(shí)節(jié)點(diǎn)
    let dom = document.createElement(tag);
    //  在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)
    container.appendChild(dom);
  },
  //  創(chuàng)建文本元素方法
  mountText(vnode,container){
    //  創(chuàng)建真實(shí)文本節(jié)點(diǎn)
    let dom = document.createTextNode(vnode.children);
    //  保存dom
    vnode.el = dom;
    //  添加元素
    container.appendChild(dom);
  }
};

通過上面的代碼,就可完成真實(shí)DOM的渲染工作了,雖然但是這也只是完成了其中的一小部分而已。但是很多東西沒有添加進(jìn)去,比如動(dòng)態(tài)添加style樣式,給元素綁定樣式,添加class等等等,一系列的問題都還沒有解決,現(xiàn)在工作也只是簡單的初始化而已。其實(shí)想要完成上述的功能也不是很難,要知道剛剛所說的所有東西都是添加到DOM節(jié)點(diǎn)上的,我們只需要在DOM節(jié)點(diǎn)上做文章就可以了,改進(jìn)mountElement方法:

const mountMethod = {
  //  創(chuàng)建HTML元素方法
  mountElement(vnode,container){
    //  屬性,標(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)
    container.appendChild(dom);
  }
};
//  dom添加屬性方法
const domAttributeMethod = {
  addData (dom,data){
    //  掛載屬性
    for(let key in data){
      //  dom節(jié)點(diǎn),屬性名,舊值(方便做更新),新值
      this.patchData(dom,key,null,data[key]);
    }
  },
  patchData (el,key,prv,next){
    switch(key){
      case "style":
        this.setStyle(el,key,prv,next);
        break;
      case "class":
        this.setClass(el,key,prv,next);
        break;
      default :
        this.defaultAttr(el,key,prv,next);
        break;
    }
  },
  setStyle(el,key,prv,next){
    for(let attr in next){
      el.style[attr] = next[attr];
    }
  },
  setClass(el,key,prv,next){
    el.setAttribute("class",next);
  },
  defaultAttr(el,key,prv,next){
    if(key[0] === "@"){
      this.addEvent(el,key,prv,next);
    }
    else {
      this.setAttribute(el,key,prv,next);
    }
  },
  addEvent(el,key,prv,next){
    if(next){
      el.addEventListener(key.slice(1),next);
    }
  },
  setAttribute(el,key,prv,next){
    el.setAttribute(key,next);
  }
};

最終使用:

const VNODEData = [
    "div",
    {id:"test"},
    [
      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"));

以上就簡單的實(shí)現(xiàn)了對(duì)虛擬DOM的創(chuàng)建以及屬性的以及事件的掛載,算是有一個(gè)很大的跨越了,只是完成初始化是遠(yuǎn)遠(yuǎn)不夠的,還需要對(duì)其進(jìn)一步處理,so~有時(shí)間的話會(huì)繼續(xù)對(duì)虛擬DOM的更新進(jìn)行說明。也就是其DIFF算法部分。單一職責(zé),一篇博客只做一件事,哈哈~

總結(jié)

虛擬DOM在目前流行的幾大框架中都作為核心的一部分使用,可見其性能的高效,本文只是簡單的做一個(gè)簡單的剖析,說到頭來其實(shí)虛擬DOM就是使用JavaScript對(duì)象來表示DOM樹的信息和結(jié)構(gòu),這個(gè)JavaScript對(duì)象可以構(gòu)建一個(gè)真正的DOM樹。當(dāng)狀態(tài)變更的時(shí)候用修改后的新渲染的的JavaScript對(duì)象和舊的虛擬DOMJavaScript對(duì)象作對(duì)比,記錄著兩棵樹的差異。把差別反映到真實(shí)的DOM結(jié)構(gòu)上最后操作真正的DOM的時(shí)候只操作有差異的部分就可以了。

下次再見,若有哪里有錯(cuò)誤請(qǐng)大佬們及時(shí)指出,文章中若有錯(cuò)誤請(qǐng)?jiān)谠u(píng)論區(qū)留言,我會(huì)盡快做出改正。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106468.html

相關(guān)文章

  • 虛擬Dom詳解 - (二)

    摘要:第一篇文章中主要講解了虛擬基本實(shí)現(xiàn),簡單的回顧一下,虛擬是使用數(shù)據(jù)描述的一段虛擬節(jié)點(diǎn)樹,通過函數(shù)生成其真實(shí)節(jié)點(diǎn)。并添加到其對(duì)應(yīng)的元素容器中。在創(chuàng)建真實(shí)節(jié)點(diǎn)的同時(shí)并為其注冊事件并添加一些附屬屬性。 第一篇文章中主要講解了虛擬DOM基本實(shí)現(xiàn),簡單的回顧一下,虛擬DOM是使用json數(shù)據(jù)描述的一段虛擬Node節(jié)點(diǎn)樹,通過render函數(shù)生成其真實(shí)DOM節(jié)點(diǎn)。并添加到其對(duì)應(yīng)的元素容器中。在創(chuàng)建...

    sevi_stuo 評(píng)論0 收藏0
  • Vue指令詳解

    摘要:添加事件偵聽器時(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 官方...

    itvincent 評(píng)論0 收藏0
  • Vue指令詳解

    摘要:添加事件偵聽器時(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 官方...

    malakashi 評(píng)論0 收藏0
  • Vue指令詳解

    摘要:添加事件偵聽器時(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 官方...

    Richard_Gao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<