從vue原始碼來看Proxy的用途

有把但沒什麼卵用發表於2017-02-09

從vue原始碼來看Proxy的用途

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


MDN表述該物件構造器是用於對某物件定義使用者自定義行為的。那麼,這到底是什麼意思呢?我們下面直接來分析一下Vue原始碼中應用到Proxy物件的地方,來理解該物件構造器的作用。

Proxy在Vue中的應用

首先我們來看一看Vue原始碼的 1430行

/* not type checking this file because flow doesn`t play well with Proxy */

  var initProxy;

  {
    var 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
    );

    var warnNonPresent = function(target, key) {
      warn(
        "Property or method "" + key + "" is not defined on the instance but " +
        "referenced during render. Make sure to declare reactive data " +
        "properties in the data option.",
        target
      );
    };

    var hasProxy = typeof Proxy !== `undefined` &&
    Proxy.toString().match(/native code/);

    if (hasProxy) {
      var isBuiltInModifier = makeMap(`stop,prevent,self,ctrl,shift,alt,meta`);
      config.keyCodes = new Proxy(config.keyCodes, {
        set: function set(target, key, value) {
          if (isBuiltInModifier(key)) {
            warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key));
            return false
          } else {
            target[key] = value;
            return true
          }
        }
      });
    }

    var hasHandler = {
      has: function has(target, key) {
        var has = key in target;
        var isAllowed = allowedGlobals(key) || key.charAt(0) === `_`;
        if (!has && !isAllowed) {
          warnNonPresent(target, key);
        }
        return has || !isAllowed
      }
    };

    var getHandler = {
      get: function get(target, key) {
        if (typeof key === `string` && !(key in target)) {
          warnNonPresent(target, key);
        }
        return target[key]
      }
    };

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

首先,我們要明白一件事情,vue放棄了對ie9以下的支援。即使這樣,由於Proxy作為一個es6的新特性,支援度依然不高,作者也只是嘗試著使用了一下。下面我們來一步一步講解。

原始碼講解

 var 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
    );

該處定義了所允許的全域性物件型別。


var warnNonPresent = function(target, key) {
      warn(
        "Property or method "" + key + "" is not defined on the instance but " +
        "referenced during render. Make sure to declare reactive data " +
        "properties in the data option.",
        target
      );
 };

該處定義了一個報警方法,傳入鍵名或方法名,log顯示一條警告


 var hasProxy = typeof Proxy !== `undefined` &&
    Proxy.toString().match(/native code/);

該處是對es6特性Proxy的檢測, 其檢測手段是確認Proxy是原生實現並未被使用者程式碼所覆蓋。


    var isBuiltInModifier = makeMap(`stop,prevent,self,ctrl,shift,alt,meta`);
    config.keyCodes = new Proxy(config.keyCodes, {
        set: function set(target, key, value) {
          if (isBuiltInModifier(key)) {
            warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key));
            return false
          } else {
            target[key] = value;
            return true
          }
        }
      });

該段程式碼對config.keyCodes物件進行了代理,其意義在於禁止使用者修改Vue內建的一些按鍵值,這些按鍵值和按鍵名是對應的。如果用過Vue的使用者應該知道,Vue在事件物件上對一些常用按鍵和常用操作進行了內建(這個內建過程被用eval函式壓縮了,由一堆程式碼段構成的,沒什麼看頭),作者肯定不希望也不允許使用者修改配置的時候覆蓋了內建內容。於是,當我們做config.keyCodes[`stop`] = xxx這樣的操作的時候,Vue就會告訴你說“你這個人,不老實,為什麼想著改我東西”,直接打出禁止改寫內建配置的警告。如果非內建內容,那麼可以直接設定上。


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

這句話就不再解釋了,類似上面。不過讀者可能會問has是什麼? 那麼下面我們來講解一下Proxy的各個引數

Proxy所需引數

兩個, var b = new Proxy(a, { has: fn xxx, get: fn xxx, set: fn xxx …. })

target 被代理的物件

handler 處理器物件

用來定義我們對該物件的各種操作

完整的handler處理器物件內容:

{
    get: `咋獲取`,
    set: `咋設定`,
    deleteProperty: `咋刪除`,
    enumerate: `咋列舉`,
    ownKeys: `咋獲取所有該物件的屬性鍵 `,
    has: `問你有沒有, 比如 "xxx" in target`,
    defineProperty: `如何defineProperty, 這個我們也是可以代理的`,
    getOwnPropertyDescriptor: `獲取屬性描述的代理`,
    getPrototypeOf: `找原型時候的代理`,
    setPrototypeOf: `設定物件原型的時候的代理`,
    isExtensible: `判斷物件是否可擴充套件的時候的代理`,
    preventExtensions: `設定阻止物件擴充套件的時候的代理`,
    apply: `執行呼叫操作的時候的代理`,
    construct: `執行例項化的時候的代理`
}

以上皆是函式~~

所以Proxy的作用是?

攔截,預警,上報,擴充套件功能,統計,強化物件…能想得到的都能沾到點邊,這裡Vue的程式碼主要將代理運用於攔截。並且由於規範依然在發展,所以大家慎用。。。

資料

MDN js Proxy Object

MDN Proxy Handler

sec-proxy-object-internal-methods-and-internal-slots

相關文章