人人都能懂的Vue源碼系列(五)—initProxy

上篇文章中,主要講了mergeOptions方法的處理邏輯,不明白的同窗們能夠點擊這裏查看。今天回到init方法,接下來看源碼vue

if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}
複製代碼

上面的代碼邏輯很簡單,主要就是爲Vue實例的_renderProxy屬性賦值,不一樣的代碼運行環境賦值的結果不一樣。es6

  1. 當前環境是開發環境,則調用initProxy方法
  2. 若是不是開發環境,則vue實例的_renderProxy屬性指向vue實例自己。

initProxy

initProxy方法究竟怎麼樣代理vm屬性呢?經過源碼來一探究竟。編程

initProxy = function initProxy (vm) {
    if (hasProxy) {
      // determine which proxy handler to use
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
複製代碼

經過判斷hasProxy,來執行不一樣的處理邏輯,咱們先來看hasProxy的源碼。函數

const hasProxy =
    typeof Proxy !== 'undefined' &&
    Proxy.toString().match(/native code/)
複製代碼

從變量的名字咱們知道它的做用就是判斷當前環境中Proxy是否可用,同窗們若是不熟悉Proxy的用法,能夠點擊這裏。這個判斷方法在咱們平時寫代碼中也能夠進行借鑑。post

回到代碼中,若是當前環境存在Proxy,則執行塊內的語句。ui

const options = vm.$options
  const handlers = options.render && options.render._withStripped
    ? getHandler
    : hasHandler
複製代碼

上面兩行代碼的邏輯是,若是options上存在render屬性,且render屬性上存在_withStripped屬性,則proxy的traps(traps其實也就是自定義方法)採用getHandler方法,不然採用hasHandler方法。更多關於traps相關的內容可點擊這裏spa

接下來看看getHandler和hasHandler方法代理

getHandler

const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        warnNonPresent(target, key)
      }
      return target[key]
    }
  }
複製代碼

getHandler方法主要是針對讀取代理對象的某個屬性時進行的操做。當訪問的屬性不是string類型或者屬性值在被代理的對象上不存在,則拋出錯誤提示,不然就返回該屬性值。code

該方法能夠在開發者錯誤的調用vm屬性時,提供提示做用。regexp

const hasHandler = {
    has (target, key) {
      const has = key in target
      const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
      if (!has && !isAllowed) {
        warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }
複製代碼

hasHandler

hasHandler方法的應用場景在於查看vm實例是否擁有某個屬性—好比調用for in循環遍歷vm實例屬性時,會觸發hasHandler方法。

首先使用in操做符判斷該屬性是否在vm實例上存在

const has = key in target
複製代碼

接下來經過下列語句,肯定屬性名稱是否可用

const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
複製代碼

allowedGlobals的定義以下:

const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
    'require' // for Webpack/Browserify
  )
複製代碼

咱們結合makeMap函數一塊兒來看

export function makeMap ( str: string, expectsLowerCase?: boolean ): (key: string) => true | void {
  const map = Object.create(null)
  const list: Array<string> = str.split(',')
  for (let i = 0; i < list.length; i++) {
    map[list[i]] = true
  }
  return expectsLowerCase
    ? val => map[val.toLowerCase()]
    : val => map[val]
}
複製代碼

先來分析makeMap,其做用是經過傳入的string參數來生成映射表。如傳入下列參數

'Infinity,undefined,NaN,isFinite,isNaN,'
....
複製代碼

經過makeMap方法能夠生成下面這樣的一個映射表

{
  Infinity: true,
  undefined: true
  ......
}
複製代碼

這個方法咱們在平常寫代碼的時候也能夠進行借鑑。結合上面的分析,咱們知道allowedGlobals最終存儲的是一個表明特殊屬性名稱的映射表。

回到源碼,結合has和isAllowed屬性,咱們知道當讀取對象屬性時,若是屬性名在vm上不存在,且不在特殊屬性名稱映射表中,或沒有以_符號開頭,則拋出異常。最後回到initProxy代碼中

if (hasProxy) {
      ...
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
複製代碼

若是Proxy屬性存在,則把包裝後的vm屬性賦值給_renderProxy屬性值,不然把vm是實例自己賦值給_renderProxy屬性。

總結

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

代理對象是es6的新特性,它主要用來自定義對象一些基本操做(如查找,賦值,枚舉等)。

上述源碼部分主要應用了lookup和enumeration部分的自定義能力。proxy是一個強大的特性,爲咱們提供了不少"元編程"能力。只不過目前規範尚未很完善,使用的時候要稍加註意。最後按照老規矩,以一張圖完成今天的講解。

render proxy屬性
相關文章
相關標籤/搜索