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

資訊專欄INFORMATION COLUMN

vue:虛擬dom的實(shí)現(xiàn)

BLUE / 966人閱讀

摘要:會(huì)檢測(cè)出最大的靜態(tài)子樹不需要?jiǎng)討B(tài)性的子樹并且從渲染函數(shù)中萃取出來。成功的水合作用。如果新節(jié)點(diǎn)不是克隆的,則表示呈現(xiàn)函數(shù)。方法解析在此虛擬的

Vitual DOM是一種虛擬dom技術(shù),本質(zhì)上是基于javascript實(shí)現(xiàn)的,相對(duì)于dom對(duì)象,javascript對(duì)象更簡單,處理速度更快,dom樹的結(jié)構(gòu),屬性信息都可以很容易的用javascript對(duì)象來表示:

let element={
    tagName:"ul",//節(jié)點(diǎn)標(biāo)簽名
    props:{//dom的屬性,用一個(gè)對(duì)象存儲(chǔ)鍵值對(duì)
        id:"list"
    },
    children:[//該節(jié)點(diǎn)的子節(jié)點(diǎn)
        {tagName:"li",props:{class:"item"},children:["aa"]},
        {tagName:"li",props:{class:"item"},children:["bb"]},
        {tagName:"li",props:{class:"item"},children:["cc"]}
    ]
}
對(duì)應(yīng)的html寫法是:
  • aa
  • aa
  • aa

Virtual DOM并沒有完全實(shí)現(xiàn)DOMVirtual DOM最主要的還是保留了Element之間的層次關(guān)系和一些基本屬性. 你給我一個(gè)數(shù)據(jù),我根據(jù)這個(gè)數(shù)據(jù)生成一個(gè)全新的Virtual DOM,然后跟我上一次生成的Virtual DOMdiff,得到一個(gè)Patch,然后把這個(gè)Patch打到瀏覽器的DOM上去。

我們可以通過javascript對(duì)象表示的樹結(jié)構(gòu)來構(gòu)建一棵真正的dom樹,當(dāng)數(shù)據(jù)狀態(tài)發(fā)生變化時(shí),可以直接修改這個(gè)javascript對(duì)象,接著對(duì)比修改后的javascript對(duì)象,記錄下需要對(duì)頁面做的dom操作,然后將其應(yīng)用到真正的dom樹,實(shí)現(xiàn)視圖的更新,這個(gè)過程就是Virtual DOM的核心思想。

VNode的數(shù)據(jù)結(jié)構(gòu)圖:

VNode生成最關(guān)鍵的點(diǎn)是通過render2種生成方式,第一種是直接在vue對(duì)象的option中添加render字段。第二種是寫一個(gè)模板或指定一個(gè)el根元素,它會(huì)首先轉(zhuǎn)換成模板,經(jīng)過html語法解析器生成一個(gè)ast抽象語法樹,對(duì)語法樹做優(yōu)化,然后把語法樹轉(zhuǎn)換成代碼片段,最后通過代碼片段生成function添加到optionrender字段中。

ast語法優(yōu)的過程,主要做了2件事:

會(huì)檢測(cè)出靜態(tài)的class名和attributes,這樣它們?cè)诔跏蓟秩竞缶陀肋h(yuǎn)不會(huì)再被比對(duì)了。

會(huì)檢測(cè)出最大的靜態(tài)子樹(不需要?jiǎng)討B(tài)性的子樹)并且從渲染函數(shù)中萃取出來。這樣在每次重渲染時(shí),它就會(huì)直接重用完全相同的vnode,同時(shí)跳過比對(duì)。

src/core/vdom/create-element.js

const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {
  // 兼容不傳data的情況
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  // 如果alwaysNormalize是true
  // 那么normalizationType應(yīng)該設(shè)置為常量ALWAYS_NORMALIZE的值
  if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
  // 調(diào)用_createElement創(chuàng)建虛擬節(jié)點(diǎn)
  return _createElement(context, tag, data, children, normalizationType)
}

function _createElement (context, tag, data, children, normalizationType) {
  /**
   * 如果存在data.__ob__,說明data是被Observer觀察的數(shù)據(jù)
   * 不能用作虛擬節(jié)點(diǎn)的data
   * 需要拋出警告,并返回一個(gè)空節(jié)點(diǎn)
   * 被監(jiān)控的data不能被用作vnode渲染的數(shù)據(jù)的原因是:
   * data在vnode渲染過程中可能會(huì)被改變,這樣會(huì)觸發(fā)監(jiān)控,導(dǎo)致不符合預(yù)期的操作
   */
  if (data && data.__ob__) {
    process.env.NODE_ENV !== "production" && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}
` +
      "Always create fresh vnode data objects in each render!",
      context
    )
    return createEmptyVNode()
  }
  // 當(dāng)組件的is屬性被設(shè)置為一個(gè)falsy的值
  // Vue將不會(huì)知道要把這個(gè)組件渲染成什么
  // 所以渲染一個(gè)空節(jié)點(diǎn)
  if (!tag) {
    return createEmptyVNode()
  }
  // 作用域插槽
  if (Array.isArray(children) &&
      typeof children[0] === "function") {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  // 根據(jù)normalizationType的值,選擇不同的處理方法
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  // 如果標(biāo)簽名是字符串類型
  if (typeof tag === "string") {
    let Ctor
    // 獲取標(biāo)簽名的命名空間
    ns = config.getTagNamespace(tag)
    // 判斷是否為保留標(biāo)簽
    if (config.isReservedTag(tag)) {
      // 如果是保留標(biāo)簽,就創(chuàng)建一個(gè)這樣的vnode
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      // 如果不是保留標(biāo)簽,那么我們將嘗試從vm的components上查找是否有這個(gè)標(biāo)簽的定義
    } else if ((Ctor = resolveAsset(context.$options, "components", tag))) {
      // 如果找到了這個(gè)標(biāo)簽的定義,就以此創(chuàng)建虛擬組件節(jié)點(diǎn)
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // 兜底方案,正常創(chuàng)建一個(gè)vnode
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
    // 當(dāng)tag不是字符串的時(shí)候,我們認(rèn)為tag是組件的構(gòu)造類
    // 所以直接創(chuàng)建
  } else {
    vnode = createComponent(tag, data, context, children)
  }
  // 如果有vnode
  if (vnode) {
    // 如果有namespace,就應(yīng)用下namespace,然后返回vnode
    if (ns) applyNS(vnode, ns)
    return vnode
  // 否則,返回一個(gè)空節(jié)點(diǎn)
  } else {
    return createEmptyVNode()
  }
}

方法的功能是給一個(gè)Vnode對(duì)象對(duì)象添加若干個(gè)子Vnode,因?yàn)檎麄€(gè)Virtual DOM是一種樹狀結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都可能會(huì)有若干子節(jié)點(diǎn)。然后創(chuàng)建一個(gè)VNode對(duì)象,如果是一個(gè)reserved tag(比如html,head等一些合法的html標(biāo)簽)則會(huì)創(chuàng)建普通的DOM VNode,如果是一個(gè)component tag(通過vue注冊(cè)的自定義component),則會(huì)創(chuàng)建Component VNode對(duì)象,它的VnodeComponentOptions不為Null.
創(chuàng)建好Vnode,下一步就是要把Virtual DOM渲染成真正的DOM,是通過patch來實(shí)現(xiàn)的,源碼如下:

src/core/vdom/patch.js

  return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { // oldVnoe:dom||當(dāng)前vnode,vnode:vnoder=對(duì)象類型,hydration是否直接用服務(wù)端渲染的dom元素
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) {
      // 空掛載(可能是組件),創(chuàng)建新的根元素。
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    } else {
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch 現(xiàn)有的根節(jié)點(diǎn)
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
      } else {
        if (isRealElement) {
            // 安裝到一個(gè)真實(shí)的元素。
            // 檢查這是否是服務(wù)器渲染的內(nèi)容,如果我們可以執(zhí)行。
            // 成功的水合作用。
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== "production") {
              warn(
                "The client-side rendered virtual DOM tree is not matching " +
                "server-rendered content. This is likely caused by incorrect " +
                "HTML markup, for example nesting block-level elements inside " +
                "

, or missing . Bailing hydration and performing " + "full client-side render." ) } } // 不是服務(wù)器呈現(xiàn),就是水化失敗。創(chuàng)建一個(gè)空節(jié)點(diǎn)并替換它。 oldVnode = emptyNodeAt(oldVnode) } // 替換現(xiàn)有的元素 const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm( vnode, insertedVnodeQueue, // 極為罕見的邊緣情況:如果舊元素在a中,則不要插入。 // 離開過渡。只有結(jié)合過渡+時(shí)才會(huì)發(fā)生。 // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // 遞歸地更新父占位符節(jié)點(diǎn)元素。 if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // 調(diào)用插入鉤子,這些鉤子可能已經(jīng)被創(chuàng)建鉤子合并了。 // 例如使用“插入”鉤子的指令。 const insert = ancestor.data.hook.insert if (insert.merged) { // 從索引1開始,以避免重新調(diào)用組件掛起的鉤子。 for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }

patch支持的3個(gè)參數(shù),其中oldVnode是一個(gè)真實(shí)的DOM或者一個(gè)VNode對(duì)象,它表示當(dāng)前的VNode,vnodeVNode對(duì)象類型,它表示待替換的VNode,hydrationbool類型,它表示是否直接使用服務(wù)器端渲染的DOM元素,下面流程圖表示patch的運(yùn)行邏輯:

patch運(yùn)行邏輯看上去比較復(fù)雜,有2個(gè)方法createElmpatchVnode是生成dom的關(guān)鍵,源碼如下:

/**
 * @param vnode根據(jù)vnode的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建真實(shí)的dom節(jié)點(diǎn),如果vnode有children則會(huì)遍歷這些子節(jié)點(diǎn),遞歸調(diào)用createElm方法,
 * @param insertedVnodeQueue記錄子節(jié)點(diǎn)創(chuàng)建順序的隊(duì)列,每創(chuàng)建一個(gè)dom元素就會(huì)往隊(duì)列中插入當(dāng)前的vnode,當(dāng)整個(gè)vnode對(duì)象全部轉(zhuǎn)換成為真實(shí)的dom 樹時(shí),會(huì)依次調(diào)用這個(gè)隊(duì)列中vnode hook的insert方法
 */

  let inPre = 0
  function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
    vnode.isRootInsert = !nested // 過渡進(jìn)入檢查
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      if (process.env.NODE_ENV !== "production") {
        if (data && data.pre) {
          inPre++
        }
        if (
          !inPre &&
          !vnode.ns &&
          !(
            config.ignoredElements.length &&
            config.ignoredElements.some(ignore => {
              return isRegExp(ignore)
                ? ignore.test(tag)
                : ignore === tag
            })
          ) &&
          config.isUnknownElement(tag)
        ) {
          warn(
            "Unknown custom element: <" + tag + "> - did you " +
            "register the component correctly? For recursive components, " +
            "make sure to provide the "name" option.",
            vnode.context
          )
        }
      }
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== "production" && data && data.pre) {
        inPre--
      }
    } else if (isTrue(vnode.isComment)) {
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }

方法會(huì)根據(jù)vnode的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建真實(shí)的DOM節(jié)點(diǎn),如果vnodechildren,則會(huì)遍歷這些子節(jié)點(diǎn),遞歸調(diào)用createElm方法,InsertedVnodeQueue是記錄子節(jié)點(diǎn)創(chuàng)建順序的隊(duì)列,每創(chuàng)建一個(gè)DOM元素就會(huì)往這個(gè)隊(duì)列中插入當(dāng)前的VNode,當(dāng)整個(gè)VNode對(duì)象全部轉(zhuǎn)換成為真實(shí)的DOM樹時(shí),會(huì)依次調(diào)用這個(gè)隊(duì)列中的VNode hookinsert方法。

/**
     * 比較新舊vnode節(jié)點(diǎn),根據(jù)不同的狀態(tài)對(duì)dom做合理的更新操作(添加,移動(dòng),刪除)整個(gè)過程還會(huì)依次調(diào)用prepatch,update,postpatch等鉤子函數(shù),在編譯階段生成的一些靜態(tài)子樹,在這個(gè)過程
     * @param oldVnode 中由于不會(huì)改變而直接跳過比對(duì),動(dòng)態(tài)子樹在比較過程中比較核心的部分就是當(dāng)新舊vnode同時(shí)存在children,通過updateChildren方法對(duì)子節(jié)點(diǎn)做更新,
     * @param vnode
     * @param insertedVnodeQueue
     * @param removeOnly
     */
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    if (oldVnode === vnode) {
      return
    }

    const elm = vnode.elm = oldVnode.elm

    if (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }
      return
    }

      // 用于靜態(tài)樹的重用元素。
        // 注意,如果vnode是克隆的,我們只做這個(gè)。
        // 如果新節(jié)點(diǎn)不是克隆的,則表示呈現(xiàn)函數(shù)。
        // 由熱重加載api重新設(shè)置,我們需要進(jìn)行適當(dāng)?shù)闹匦落秩尽?    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }

    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode)
    }

    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, "")
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        nodeOps.setTextContent(elm, "")
      }
    } else if (oldVnode.text !== vnode.text) {
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

updateChildren方法解析在此:vue:虛擬DOM的patch

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

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

相關(guān)文章

  • Vue.js-Render函數(shù)

    摘要:函數(shù)通過參數(shù)來創(chuàng)建虛擬,結(jié)構(gòu)精簡。其中,訪問的用法,使用場(chǎng)景集中在函數(shù)。使用代替模板功能在函數(shù)中,不再需要內(nèi)置的指令,比如。方法時(shí)快速改變數(shù)組結(jié)構(gòu),返回一個(gè)新數(shù)組。 學(xué)習(xí)筆記:Render函數(shù) Render函數(shù) Vue2與Vue1最大的區(qū)別就在于Vue2使用了虛擬DOM來更新DOM節(jié)點(diǎn),提升渲染性能。 Vue2與Vue1最大的區(qū)別就在于Vue2使用了虛擬DOM來更新DOM節(jié)點(diǎn),提升...

    ccj659 評(píng)論0 收藏0
  • 虛擬Dom詳解 - (一)

    摘要:為此也做了一些學(xué)習(xí)簡單的侃一侃虛擬到底是什么虛擬詳解二什么是虛擬虛擬首次產(chǎn)生是框架最先提出和使用的,其卓越的性能很快得到廣大開發(fā)者的認(rèn)可,繼之后也在其核心引入了虛擬的概念。所謂的虛擬到底是什么也就是通過語言來描述一段代碼。 隨著Vue和React的風(fēng)聲水起,伴隨著諸多框架的成長,虛擬DOM漸漸成了我們經(jīng)常議論和討論的話題。什么是虛擬DOM,虛擬DOM是如何渲染的,那么Vue的虛擬Dom...

    ashe 評(píng)論0 收藏0
  • Vue虛擬DOM及diff算法

    摘要:的算法是基于的實(shí)現(xiàn),并在些基礎(chǔ)上作了很多的調(diào)整和改進(jìn)。此時(shí)和之間的是新增的,調(diào)用,把這些虛擬全部插進(jìn)的后邊,可以認(rèn)為新節(jié)點(diǎn)先遍歷完。 虛擬dom 為什么出現(xiàn):瀏覽器解析一個(gè)html大致分為五步:創(chuàng)建DOM tree –> 創(chuàng)建Style Rules -> 構(gòu)建Render tree -> 布局Layout –> 繪制Painting。每次對(duì)真實(shí)dom進(jìn)行操作的時(shí)候,瀏覽器都會(huì)從構(gòu)建...

    李昌杰 評(píng)論0 收藏0
  • Svelte 前端框架探索

    摘要:苗條的框架正是作者的初始目的,苗條包括代碼編寫量打包大小等等。是已經(jīng)編譯后的組件有什么缺點(diǎn)是一個(gè)剛起步不久的前端框架,無論在維護(hù)人員還是社區(qū)上都是大大不如三大框架,這里列舉一下本人認(rèn)為的存在的缺點(diǎn)。 Svelte 的作者也是 rollup 的作者 Rich Harris,前端界的輪子哥。sevlte 項(xiàng)目首次提交于 2016 年 11 月 16 日,目前版本是 3.6.1(2019-0...

    Euphoria 評(píng)論0 收藏0
  • vue常用知識(shí)點(diǎn)總結(jié)

    摘要:這里借鑒了一下的處理方式,我們把單獨(dú)模塊的包裝成一個(gè)函數(shù),提供一個(gè)全局的回調(diào)方法,加載完成時(shí)候再調(diào)用回調(diào)函數(shù)。 感謝本文引用鏈接的各位大佬們,小菜鳥我只是個(gè)搬運(yùn)工 1.談一談你理解的vue是什么樣子的? vue是數(shù)據(jù)、視圖分離的一個(gè)框架,讓數(shù)據(jù)與視圖間不會(huì)發(fā)生直接聯(lián)系。MVVM 組件化:把整體拆分為各個(gè)可以復(fù)用的個(gè)體 數(shù)據(jù)驅(qū)動(dòng):通過數(shù)據(jù)變化直接影響bom展示,避免dom操作。 可以在...

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

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

0條評(píng)論

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