關於vue過濾器的原理解析

又來學習源碼系列,今天就看一下vue中的過濾器具體是怎麼實現的,我覺得這是一個不常用但是很重要的知識點,開衝!

在這裏插入圖片描述

01 前言

過濾器實質不改變原始數據,只是對數據進行加工處理後返回過濾後的數據再進行調用處理。我們看一下官方的定義:

Vue.js 允許你自定義過濾器,可被用於一些常見的文本格式化。過濾器可以用在兩個地方:雙花括號插值和 v-bind 表達式 (後者從 2.1.0+ 開始支持)。過濾器應該被添加在 JavaScript 表達式的尾部,由“管道”符號指示:

<!-- 在雙花括號中 -->
{{ message | capitalize }}

<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>

你可以在一個組件的選項中定義本地的過濾器:

filters: {
  capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
  }
}

或者在創建 Vue 實例之前全局定義過濾器:

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

new Vue({
  // ...
})

過濾器函數總接收表達式的值 (之前的操作鏈的結果) 作爲第一個參數。在上述例子中,capitalize 過濾器函數將會收到 message 的值作爲第一個參數。過濾器可以串聯:

{{ message | filterA | filterB }}

在這個例子中,filterA 被定義爲接收單個參數的過濾器函數,表達式 message 的值將作爲參數傳入到函數中。然後繼續調用同樣被定義爲接收單個參數的過濾器函數 filterB,將 filterA 的結果傳遞到 filterB 中。

過濾器是 JavaScript 函數,因此可以接收參數:

{{ message | filterA('arg1', arg2) }}

這裏,filterA 被定義爲接收三個參數的過濾器函數。其中 message 的值作爲第一個參數,普通字符串 ‘arg1’ 作爲第二個參數,表達式 arg2 的值作爲第三個參數。

02 過濾器原理

{{ message | capitalize }}

上面的過濾器經過一頓操作之後就會變成:_s(_f("capitalize")(message))

  • _f:該函數其實就是resolveFilter的別名,作用是從_this.$options.filter找到過濾器並返回
  • _s:該函數就是toString函數的別名,作用是拿到過濾之後的結果並傳遞給toString()函數,結果會保存到VNode中的text屬性,返回結果直接渲染視圖

串聯過濾器

{{ message | filterA | filterB }}

上面的串聯過濾器經過一頓操作之後就會變成:

_s(_f("filterB")(_f("filterA")(message)))

這裏的意思就是message作爲第一個參數傳進filterA當中,然後經過filterA的處理結果就傳進filterB當中。即filterA過濾器的結果就是filterB過濾器的輸入

過濾器參數接收

{{ message | filterA | filterB("param") }}

以上的過濾器的編譯結果就是:

_s(_f("filterB")(_f("filterA")(message),"param"))

這裏有一點注意的是:這個param參數是filterB的第二個參數,它的第一個參數是經過filterA處理的結果。

_f函數的原理

_f函數其實就是尋找過濾器的,如果找到過濾器就返回過濾器,找不到就返回與參數相同的值。它的代碼其實很簡單:

import {identity, resolveAssets} from 'core/util/index'

export function resolveFilter(id){
  return resolveAssets(this.$options, 'filters', id, true) || identity
}

我們重點來看一下resolveAssets到底做了什麼事情。

export function resolveAsset (options, type, id, warnMissing){
  if(typeof(id) !== 'string'){
    return
  }
  
  const assets = options[type]
  if(hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if(hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitlize(camelizedId)
  if(hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  
  //檢查原型鏈
  const res assets[id] || assets[camelizedId] || PascalCaseId
  if(process.env.NODE_ENV!=='production'&& warnMissing&&!res){
    warn('Fail to resolve' + type.slice(0,-1)+':'+id, options)
  }
  return res
}

其實它的尋找過程也很簡單,主要是做了以下的操作(id是過濾器id):

  • 判斷過濾器id是否爲字符串,不是則終止
  • 用assets存儲過濾器
  • hasOwn函數檢查assets自身是否存在id屬性,存在則返回
  • hasOwn函數檢查assets自身是否存在駝峯化後的id屬性,存在則返回
  • hasOwn函數檢查assets自身是否存在將首字母大寫後的id屬性,存在則返回
  • 如果還是沒有,就是去原型鏈找,找不到就會打印警告

過濾器解析原理

我們想一下,解析器是怎麼解析過濾器的語法?其實在vue內部專門有這麼一個函數用來解析過濾器語法:parseFilters

它的原理就是解析過濾器列表,然後循環過濾器列表拼接字符串

03 小結

其實過濾器的作用有很多,相信大家對時間的格式化也有很多的處理方法,其中一個就是可以利用過濾器來格式化時間。而且我們知道,過濾器它是可以支持鏈式的操作,很方便地對變量進行多步處理,然後返回你想要的結果。

過濾器的原理其實很簡單,無非就是在編譯階段就把過濾器編譯成函數進行調用,串聯的過濾器就是嵌套調用。關於編譯成函數的時候就是利用vue的內部函數parseFilters,解析過濾器列表並進行拼接字符串

參考文章:

  • 深入淺出vue.js

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章