最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2021年06月28日 09:31:30   作者:Gerryli  
自定義指令是 vue 中使用頻率僅次于組件,其包含 bind 、 inserted 、 update 、 componentUpdated 、 unbind 五個(gè)生命周期鉤子,接下來通過本文給大家詳細(xì)介紹Vue指令工作原理實(shí)現(xiàn)方法,需要的朋友參考下吧

Vue簡(jiǎn)介

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

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

前言

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

  • 指令的工作原理
  • 指令使用的注意事項(xiàng)

基本使用

官網(wǎng)案例:

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

指令工作原理

初始化

初始化全局 API 時(shí),在 platforms/web 下,調(diào)用 createPatchFunction 生成 VNode 轉(zhuǎn)換為真實(shí) DOM 的 patch 方法,初始化中比較重要一步是定義了與 DOM 節(jié)點(diǎn)相對(duì)應(yīng)的 hooks 方法,在 DOM 的創(chuàng)建( create )、激活( avtivate )、更新( update )、移除( remove )、銷毀( destroy )過程中,分別會(huì)輪詢調(diào)用對(duì)應(yīng)的 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對(duì)應(yīng)vue中模塊,具體有class, style, domListener, domProps, attrs, directive, ref, transition
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        // 最終將hooks轉(zhuǎn)換為{hookEvent: [cb1, cb2 ...], ...}形式
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }
  }
  // ....
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // ...
  }
}

模板編譯

模板編譯就是解析指令參數(shù),具體解構(gòu)后的 ASTElement 如下所示:

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

生成渲染方法

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

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 的指令設(shè)計(jì)是方便我們操作 DOM ,在生成 VNode 時(shí),指令并沒有做額外處理。

生成真實(shí)DOM

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

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

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

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

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

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

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

// 默認(rèn)拋出的都是updateDirectives方法
export default {
  create: updateDirectives,
  update: updateDirectives,
  destroy: function unbindDirectives (vnode: VNodeWithData) {
    // 銷毀時(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)
  // 插入后的回調(diào)
  const dirsWithInsert = [
  // 更新完成后回調(diào)
  const dirsWithPostpatch = []

  let key, oldDir, dir
  for (key in newDirs) {
    oldDir = oldDirs[key]
    dir = newDirs[key]
    // 新元素指令,會(huì)執(zhí)行一次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ì)執(zhí)行一次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插入到頁面中,會(huì)調(diào)用此回調(diào)方法
    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)
      }
    }
  }
}

對(duì)于首次創(chuàng)建,執(zhí)行過程如下:

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

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

  1. 響應(yīng)式數(shù)據(jù)發(fā)生改變,調(diào)用 dep.notify ,通知數(shù)據(jù)更新。
  2. 調(diào)用 patchVNode ,對(duì)新舊 VNode 進(jìn)行差異化更新,并全量更新當(dāng)前 VNode 屬性(包括指令,就會(huì)進(jìn)入 updateDirectives 方法)。
  3. 如果指令存在 update 鉤子方法,調(diào)用 update 鉤子方法,并初始化 componentUpdated 回調(diào),將 postpatch hooks 掛載到 VNode.data.hooks 中。
  4. 當(dāng)前節(jié)點(diǎn)及子節(jié)點(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
    // 全量更新節(jié)點(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)) {
    // 調(diào)用postpatch鉤子
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

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

注意事項(xiàng)

使用自定義指令時(shí),和普通模板數(shù)據(jù)綁定, v-model 還是存在一定的差別,如雖然我傳遞參數(shù)( v-xxx='param' )是一個(gè)引用類型,數(shù)據(jù)變化時(shí),并不能觸發(fā)指令的 bind 或者 inserted ,這是因?yàn)樵谥噶畹穆暶髦芷趦?nèi), bind 和 inserted 只是在初始化時(shí)調(diào)用一次,后面只會(huì)走 update componentUpdated 。

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

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

小結(jié)

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

GitHub

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

相關(guān)文章

  • Vue中props組件和slot標(biāo)簽的區(qū)別

    Vue中props組件和slot標(biāo)簽的區(qū)別

    props?和?slot?在?Vue?中的作用略有不同,props?更多地用于父子組件之間的數(shù)據(jù)傳遞,而?slot?則更多地用于組件的復(fù)用和擴(kuò)展。感興趣的同學(xué)可以參考閱讀
    2023-04-04
  • vue 解決文本框被鍵盤遮住的問題

    vue 解決文本框被鍵盤遮住的問題

    今天小編就為大家分享一篇vue 解決文本框被鍵盤遮住的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Vue 理解之白話 getter/setter詳解

    Vue 理解之白話 getter/setter詳解

    這篇文章主要介紹了Vue getter setter,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 10分鐘了解Vue3遞歸組件的用法

    10分鐘了解Vue3遞歸組件的用法

    遞歸?簡(jiǎn)單來講就是程序自己調(diào)用自身,vue中的遞歸組件就是,組件自身調(diào)用自身,下面這篇文章主要給大家介紹了關(guān)于Vue3遞歸組件的用法,需要的朋友可以參考下
    2022-03-03
  • vue上傳文件formData上傳的解決全流程

    vue上傳文件formData上傳的解決全流程

    這篇文章主要介紹了vue上傳文件formData上傳的解決全流程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 探索Vue高階組件的使用

    探索Vue高階組件的使用

    本篇文章主要介紹了探索Vue高階組件的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-01-01
  • Vue.JS入門教程之自定義指令

    Vue.JS入門教程之自定義指令

    這篇文章主要為大家詳細(xì)介紹了Vue.JS入門教程之自定義指令,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • axios實(shí)現(xiàn)文件上傳并獲取進(jìn)度

    axios實(shí)現(xiàn)文件上傳并獲取進(jìn)度

    這篇文章主要為大家詳細(xì)介紹了axios實(shí)現(xiàn)文件上傳并獲取進(jìn)度,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • vue父列表數(shù)據(jù)獲取子列表數(shù)據(jù)的實(shí)現(xiàn)步驟

    vue父列表數(shù)據(jù)獲取子列表數(shù)據(jù)的實(shí)現(xiàn)步驟

    本文檔將介紹如何通過點(diǎn)擊父列表(表格)中的數(shù)據(jù)行來獲取到子列表(表格)的數(shù)據(jù),代碼示例是基于Vue框架實(shí)現(xiàn)的一個(gè)組件,包含了父列表和子列表,通過點(diǎn)擊父列表的數(shù)據(jù)行來動(dòng)態(tài)獲取子列表的數(shù)據(jù),感興趣的朋友跟隨小編一起看看吧
    2024-06-06
  • vue實(shí)現(xiàn)動(dòng)態(tài)控制表格列的顯示隱藏

    vue實(shí)現(xiàn)動(dòng)態(tài)控制表格列的顯示隱藏

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)動(dòng)態(tài)控制表格列的顯示隱藏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04

最新評(píng)論

赫章县| 桐乡市| 小金县| 醴陵市| 恩施市| 温州市| 江城| 凤庆县| 如东县| 江北区| 大同县| 从化市| 芒康县| 滦平县| 特克斯县| 搜索| 遂昌县| 伊春市| 当涂县| 高青县| 扬州市| 高雄市| 喀什市| 亚东县| 赤壁市| 肇源县| 贡觉县| 金华市| 文安县| 湖南省| 平南县| 鄂尔多斯市| 咸宁市| 根河市| 漳浦县| 宣武区| 普安县| 临夏县| 汾阳市| 平远县| 连州市|