国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

Vue指令工作原理實(shí)現方法

發(fā)布時(shí)間:2021-08-17 12:16 來(lái)源: 閱讀:0 作者:Gerryli 欄目: JavaScript 歡迎投稿:712375056

Vue簡(jiǎn)介

現在的大前端時(shí)代,是一個(gè)動(dòng)蕩紛爭的時(shí)代,江湖中已經(jīng)分成了很多門(mén)派,主要以Vue,React還有Angular為首,形成前端框架三足鼎立的局勢。Vue在前端框架中的地位就像曾經(jīng)的jQuery,由于其簡(jiǎn)單易懂、開(kāi)發(fā)效率高,已經(jīng)成為了前端工程師必不可少的技能之一。

Vue是一種漸進(jìn)式JavaScript框架,完美融合了第三方插件和UI組件庫,它和jQuery最大的區別在于,Vue無(wú)需開(kāi)發(fā)人員直接操作DOM節點(diǎn),就可以改變頁(yè)面渲染內容,在應用開(kāi)發(fā)者具有一定的HTML、CSS、JavaScript的基礎上,能夠快速上手,開(kāi)發(fā)出優(yōu)雅、簡(jiǎn)潔的應用程序模塊。

前言

自定義指令是 vue 中使用頻率僅次于組件,其包含 bind 、 inserted 、 update 、 componentUpdated 、 unbind 五個(gè)生命周期鉤子。本文將對 vue 指令的工作原理進(jìn)行相應介紹,從本文中,你將得到:

  • 指令的工作原理
  • 指令使用的注意事項

基本使用

官網(wǎng)案例:

<div id='app'>
  <input type="text" v-model="inputValue" v-focus>
</div>
<script>
  Vue.directive('focus', {
    // 第一次綁定元素時(shí)調用
    bind () {
      console.log('bind')
    },
    // 當被綁定的元素插入到 DOM 中時(shí)……
    inserted: function (el) {
      console.log('inserted')
      el.focus()
    },
    // 所在組件VNode發(fā)生更新時(shí)調用
    update () {
      console.log('update')
    },
    // 指令所在組件的 VNode 及其子 VNode 全部更新后調用
    componentUpdated () {
      console.log('componentUpdated')
    },
    // 只調用一次,指令與元素解綁時(shí)調用
    unbind () {
      console.log('unbind')
    }
  })
  new Vue({
    data: {
      inputValue: ''
    }
  }).$mount('#app')
</script>

指令工作原理

初始化

初始化全局 API 時(shí),在 platforms/web 下,調用 createPatchFunction 生成 VNode 轉換為真實(shí) DOM 的 patch 方法,初始化中比較重要一步是定義了與 DOM 節點(diǎn)相對應的 hooks 方法,在 DOM 的創(chuàng )建( create )、激活( avtivate )、更新( update )、移除( remove )、銷(xiāo)毀( destroy )過(guò)程中,分別會(huì )輪詢(xún)調用對應的 hooks 方法,這些 hooks 中一部分是指令聲明周期的入口。

// src/core/vdom/patch.js
const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}

  const { modules, nodeOps } = backend
  for (i = 0; i < hooks.length; ++i) {
    cbs[hooks[i]] = []
    // modules對應vue中模塊,具體有class, style, domListener, domProps, attrs, directive, ref, transition
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        // 最終將hooks轉換為{hookEvent: [cb1, cb2 ...], ...}形式
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  // ....
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // ...
  }
}

模板編譯

模板編譯就是解析指令參數,具體解構后的 ASTElement 如下所示:

{
  tag: 'input',
  parent: ASTElement,
  directives: [
    {
      arg: null, // 參數
      end: 56, // 指令結束字符位置
      isDynamicArg: false, // 動(dòng)態(tài)參數,v-xxx[dynamicParams]='xxx'形式調用
      modifiers: undefined, // 指令修飾符
      name: "model",
      rawName: "v-model", // 指令名稱(chēng)
      start: 36, // 指令開(kāi)始字符位置
      value: "inputValue" // 模板
    },
    {
      arg: null,
      end: 67,
      isDynamicArg: false,
      modifiers: undefined,
      name: "focus",
      rawName: "v-focus",
      start: 57,
      value: ""
    }
  ],
  // ...
}

生成渲染方法

vue 推薦采用指令的方式去操作 DOM ,由于自定義指令可能會(huì )修改 DOM 或者屬性,所以避免指令對模板解析的影響,在生成渲染方法時(shí),首先處理的是指令,如 v-model ,本質(zhì)是一個(gè)語(yǔ)法糖,在拼接渲染函數時(shí),會(huì )給元素加上 value 屬性與 input 事件(以 input 為例,這個(gè)也可以用戶(hù)自定義)。

with (this) {
    return _c('div', {
        attrs: {
            "id": "app"
        }
    }, [_c('input', {
        directives: [{
            name: "model",
            rawName: "v-model",
            value: (inputValue),
            expression: "inputValue"
        }, {
            name: "focus",
            rawName: "v-focus"
        }],
        attrs: {
            "type": "text"
        },
        domProps: {
            "value": (inputValue) // 處理v-model指令時(shí)添加的屬性
        },
        on: {
            "input": function($event) { // 處理v-model指令時(shí)添加的自定義事件
                if ($event.target.composing)
                    return;
                inputValue = $event.target.value
            }
        }
    })])
}

生成VNode

vue 的指令設計是方便我們操作 DOM ,在生成 VNode 時(shí),指令并沒(méi)有做額外處理。

生成真實(shí)DOM

在 vue 初始化過(guò)程中,我們需要記住兩點(diǎn):

  • 狀態(tài)的初始化是 父 -> 子,如 beforeCreate 、 created 、 beforeMount ,調用順序是 父 -> 子
  • 真實(shí) DOM 掛載順序是 子 -> 父,如 mounted ,這是因為在生成真實(shí) DOM 過(guò)程中,如果遇到組件,會(huì )走組件創(chuàng )建的過(guò)程,真實(shí) DOM 的生成是從子到父一級級拼接。

在 patch 過(guò)程中,每此調用 createElm 生成真實(shí) DOM 時(shí),都會(huì )檢測當前 VNode 是否存在 data 屬性,存在,則會(huì )調用 invokeCreateHooks ,走初創(chuàng )建的鉤子函數,核心代碼如下:

// src/core/vdom/patch.js
function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    // ...
    // createComponent有返回值,是創(chuàng  )建組件的方法,沒(méi)有返回值,則繼續走下面的方法
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    // ....
    if (isDef(data)) {
        // 真實(shí)節點(diǎn)創(chuàng  )建之后,更新節點(diǎn)屬性,包括指令
        // 指令首次會(huì )調用bind方法,然后會(huì )初始化指令后續hooks方法
        invokeCreateHooks(vnode, insertedVnodeQueue)
    }
    // 從底向上,依次插入
    insert(parentElm, vnode.elm, refElm)
    // ...
  }

以上是指令鉤子方法的第一個(gè)入口,是時(shí)候揭露 directive.js 神秘的面紗了,核心代碼如下:

// src/core/vdom/modules/directives.js

// 默認拋出的都是updateDirectives方法
export default {
  create: updateDirectives,
  update: updateDirectives,
  destroy: function unbindDirectives (vnode: VNodeWithData) {
    // 銷(xiāo)毀時(shí),vnode === emptyNode
    updateDirectives(vnode, emptyNode)
  }
}

function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
  if (oldVnode.data.directives || vnode.data.directives) {
    _update(oldVnode, vnode)
  }
}

function _update (oldVnode, vnode) {
  const isCreate = oldVnode === emptyNode
  const isDestroy = vnode === emptyNode
  const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
  const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
  // 插入后的回調
  const dirsWithInsert = [
  // 更新完成后回調
  const dirsWithPostpatch = []

  let key, oldDir, dir
  for (key in newDirs) {
    oldDir = oldDirs[key]
    dir = newDirs[key]
    // 新元素指令,會(huì )執行一次inserted鉤子方法
    if (!oldDir) {
      // new directive, bind
      callHook(dir, 'bind', vnode, oldVnode)
      if (dir.def && dir.def.inserted) {
        dirsWithInsert.push(dir)
      }
    } else {
      // existing directive, update
      // 已經(jīng)存在元素,會(huì )執行一次componentUpdated鉤子方法
      dir.oldValue = oldDir.value
      dir.oldArg = oldDir.arg
      callHook(dir, 'update', vnode, oldVnode)
      if (dir.def && dir.def.componentUpdated) {
        dirsWithPostpatch.push(dir)
      }
    }
  }

  if (dirsWithInsert.length) {
    // 真實(shí)DOM插入到頁(yè)面中,會(huì )調用此回調方法
    const callInsert = () => {
      for (let i = 0; i < dirsWithInsert.length; i++) {
        callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
      }
    }
    // VNode合并insert hooks
    if (isCreate) {
      mergeVNodeHook(vnode, 'insert', callInsert)
    } else {
      callInsert()
    }
  }

  if (dirsWithPostpatch.length) {
    mergeVNodeHook(vnode, 'postpatch', () => {
      for (let i = 0; i < dirsWithPostpatch.length; i++) {
        callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
      }
    })
  }

  if (!isCreate) {
    for (key in oldDirs) {
      if (!newDirs[key]) {
        // no longer present, unbind
        callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
      }
    }
  }
}

對于首次創(chuàng )建,執行過(guò)程如下:

  1. oldVnode === emptyNode , isCreate 為 true ,調用當前元素中所有 bind 鉤子方法。
  2. 檢測指令中是否存在 inserted 鉤子,如果存在,則將 insert 鉤子合并到 VNode.data.hooks 屬性中。
  3. DOM 掛載結束后,會(huì )執行 invokeInsertHook ,所有已掛載節點(diǎn),如果 VNode.data.hooks 中存在 insert 鉤子。則會(huì )調用,此時(shí)會(huì )觸發(fā)指令綁定的 inserted 方法。

一般首次創(chuàng )建只會(huì )走 bind inserted 方法,而 update componentUpdated 則與 bind 和 inserted 對應。在組件依賴(lài)狀態(tài)發(fā)生改變時(shí),會(huì )用 VNode diff 算法,對節點(diǎn)進(jìn)行打補丁式更新,其調用流程:

  1. 響應式數據發(fā)生改變,調用 dep.notify ,通知數據更新。
  2. 調用 patchVNode ,對新舊 VNode 進(jìn)行差異化更新,并全量更新當前 VNode 屬性(包括指令,就會(huì )進(jìn)入 updateDirectives 方法)。
  3. 如果指令存在 update 鉤子方法,調用 update 鉤子方法,并初始化 componentUpdated 回調,將 postpatch hooks 掛載到 VNode.data.hooks 中。
  4. 當前節點(diǎn)及子節點(diǎn)更新完畢后,會(huì )觸發(fā) postpatch hooks ,即指令的 componentUpdated 方法

核心代碼如下:

// src/core/vdom/patch.js
function patchVnode (
    oldVnode,
    vnode,
    insertedVnodeQueue,
    ownerArray,
    index,
    removeOnly
  ) {
    // ...
    const oldCh = oldVnode.children
    const ch = vnode.children
    // 全量更新節點(diǎn)的屬性
    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 (isDef(data)) {
    // 調用postpatch鉤子
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

unbind 方法是在節點(diǎn)銷(xiāo)毀時(shí),調用 invokeDestroyHook ,這里不做過(guò)多描述。

注意事項

使用自定義指令時(shí),和普通模板數據綁定, v-model 還是存在一定的差別,如雖然我傳遞參數( v-xxx='param' )是一個(gè)引用類(lèi)型,數據變化時(shí),并不能觸發(fā)指令的 bind 或者 inserted ,這是因為在指令的聲明周期內, bind 和 inserted 只是在初始化時(shí)調用一次,后面只會(huì )走 update componentUpdated 。

指令的聲明周期執行順序為 bind -> inserted -> update -> componentUpdated ,如果指令需要依賴(lài)于子組件的內容時(shí),推薦在 componentUnpdated 中寫(xiě)相應業(yè)務(wù)邏輯。

vue 中,很多方法都是循環(huán)調用,如 hooks 方法,事件回調等,一般調用都用 try catch 包裹,這樣做的目的是為了防止一個(gè)處理方法報錯,導致整個(gè)程序崩潰,這一點(diǎn)在我們開(kāi)發(fā)過(guò)程中可以借鑒使用。

小結

開(kāi)始看整個(gè) vue 源碼時(shí),對很多細枝末節方法都不怎么了解,通過(guò)梳理具體每個(gè)功能的實(shí)現時(shí),漸漸能夠看到整個(gè) vue 全貌,同時(shí)也能避免開(kāi)發(fā)使用中的一些坑點(diǎn)。

以上就是Vue指令工作原理實(shí)現方法的詳細內容,更多關(guān)于Vue指令原理的資料請關(guān)注腳本之家其它相關(guān)文章!

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。

精品人妻一区二区三区四区在线| 最近中文字幕免费MV在线视频| 啦啦啦视频在线播放免费| 国产欧美综合一区二区三区| 国产GV猛男GV无码男同网站| 国产精品青青青在线观看|