人人都能懂的Vue源碼系列—07—initProxy

前幾篇文章中,咱們主要講了merge options的一些操做。今天咱們回到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的代碼函數

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

從變量的名字咱們知道它的做用就是判斷當前環境中Proxy是否可用。同窗們若是不熟悉Proxy的用法,能夠點擊這裏。這個判斷方法在咱們平時寫代碼中也能夠進行借鑑。
回到代碼中,若是當前環境存在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相關的內容可點擊這裏
接下來看看getHandler和hasHandler方法spa

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

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

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方法的應用場景在於查看vm實例是否擁有某個屬性。好比調用for in循環遍歷vm實例屬性時,會觸發hasHandler方法。
首先使用in操做符判斷該屬性是否在vm實例上存在。code

const has = key in target

接下來經過下列語句,看屬性名稱是否可用?對象

const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'

allowedGlobals的定義以下ip

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
  ......
}

這個方法很是有用,咱們在平常寫代碼的時候也能夠進行借鑑。
回到hasHandler源碼中。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屬性

相關文章
相關標籤/搜索