• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Proxy

    Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

    术语

    handler
    包含陷阱(traps)的占位符对象。
    traps
    提供属性访问的方法。这类似于操作系统中捕获器的概念。
    target
    代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)。

    语法

    let p = new Proxy(target, handler);
    

    参数

    target
    Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
    handler
    一个对象,其属性是当执行一个操作时定义代理的行为的函数。

    方法

    Proxy.revocable()
    创建一个可撤销的Proxy对象。

    handler 对象的方法

    handler 对象是一个占位符对象,它包含Proxy的捕获器。

    示例

    基础示例

    在以下简单的例子中,当对象中不存在属性名时,缺省返回数为37。例子中使用了get

    let handler = {
        get: function(target, name){
            return name in target ? target[name] : 37;
        }
    };
    
    let p = new Proxy({}, handler);
    
    p.a = 1;
    p.b = undefined;
    
    console.log(p.a, p.b);    // 1, undefined
    
    console.log('c' in p, p.c);    // false, 37
    

    无操作转发代理

    在以下例子中,我们使用了一个原生 JavaScript 对象,代理会将所有应用到它的操作转发到这个对象上。

    let target = {};
    let p = new Proxy(target, {});
    
    p.a = 37;   // 操作转发到目标
    
    console.log(target.a);    // 37. 操作已经被正确地转发
    

    验证

    通过代理,你可以轻松地验证向一个对象的传值。这个例子使用了set

    let validator = {
      set: function(obj, prop, value) {
        if (prop === 'age') {
          if (!Number.isInteger(value)) {
            throw new TypeError('The age is not an integer');
          }
          if (value > 200) {
            throw new RangeError('The age seems invalid');
          }
        }
    
        // The default behavior to store the value
        obj[prop] = value;
    
        // 表示成功
        return true;
      }
    };
    
    let person = new Proxy({}, validator);
    
    person.age = 100;
    
    console.log(person.age); 
    // 100
    
    person.age = 'young'; 
    // 抛出异常: Uncaught TypeError: The age is not an integer
    
    person.age = 300; 
    // 抛出异常: Uncaught RangeError: The age seems invalid
    

    扩展构造函数

    方法代理可以轻松地通过一个新构造函数来扩展一个已有的构造函数。这个例子使用了constructapply

    function extend(sup,base) {
      var descriptor = Object.getOwnPropertyDescriptor(
        base.prototype,"constructor"
      );
      base.prototype = Object.create(sup.prototype);
      var handler = {
        construct: function(target, args) {
          var obj = Object.create(base.prototype);
          this.apply(target,obj,args);
          return obj;
        },
        apply: function(target, that, args) {
          sup.apply(that,args);
          base.apply(that,args);
        }
      };
      var proxy = new Proxy(base,handler);
      descriptor.value = proxy;
      Object.defineProperty(base.prototype, "constructor", descriptor);
      return proxy;
    }
    
    var Person = function(name){
      this.name = name
    };
    
    var Boy = extend(Person, function(name, age) {
      this.age = age;
    });
    
    Boy.prototype.sex = "M";
    
    var Peter = new Boy("Peter", 13);
    console.log(Peter.sex);  // "M"
    console.log(Peter.name); // "Peter"
    console.log(Peter.age);  // 13
    

    操作 DOM 节点

    有时你希望切换两个不同的元素的属性或类名。下面展示了如何使用set

    let view = new Proxy({
      selected: null
    },
    {
      set: function(obj, prop, newval) {
        let oldval = obj[prop];
    
        if (prop === 'selected') {
          if (oldval) {
            oldval.setAttribute('aria-selected', 'false');
          }
          if (newval) {
            newval.setAttribute('aria-selected', 'true');
          }
        }
    
        // The default behavior to store the value
        obj[prop] = newval;
    
        // 表示成功
        return true;
      }
    });
    
    let i1 = view.selected = document.getElementById('item-1');
    console.log(i1.getAttribute('aria-selected')); // 'true'
    
    let i2 = view.selected = document.getElementById('item-2');
    console.log(i1.getAttribute('aria-selected')); // 'false'
    console.log(i2.getAttribute('aria-selected')); // 'true'
    

    值修正及附加属性

    以下products代理会计算传值并根据需要转换为数组。这个代理对象同时支持一个叫做latestBrowser的附加属性,这个属性可以同时作为 getter 和 setter。

    let products = new Proxy({
      browsers: ['Internet Explorer', 'Netscape']
    },
    {
      get: function(obj, prop) {
        // 附加属性
        if (prop === 'latestBrowser') {
          return obj.browsers[obj.browsers.length - 1];
        }
    
        // 缺省行为是返回属性值
        return obj[prop];
      },
      set: function(obj, prop, value) {
        // 附加属性
        if (prop === 'latestBrowser') {
          obj.browsers.push(value);
          return;
        }
    
        // 如果不是数组则进行转换
        if (typeof value === 'string') {
          value = [value];
        }
    
        // 缺省行为是保存属性值
        obj[prop] = value;
    
        // 表示成功
        return true;
      }
    });
    
    console.log(products.browsers); // ['Internet Explorer', 'Netscape']
    products.browsers = 'Firefox'; // ?传入一个 string (错误地)
    console.log(products.browsers); // ['Firefox'] <- ?没问题, ?得到的是一个 array
    
    products.latestBrowser = 'Chrome';
    console.log(products.browsers); // ['Firefox', 'Chrome']
    console.log(products.latestBrowser); // 'Chrome'
    

    通过属性查找数组中的特定对象

    以下代理为数组扩展了一些实用工具。可以看到,你可以灵活地“定义”属性,而不需要使用Object.defineProperties方法。以下例子可以用于通过单元格来查找表格中的一行。在这种情况下,target 是table.rows

    let products = new Proxy([
      { name: 'Firefox', type: 'browser' },
      { name: 'SeaMonkey', type: 'browser' },
      { name: 'Thunderbird', type: 'mailer' }
    ],
    {
      get: function(obj, prop) {
        // 缺省行为是返回属性值, prop ?通常是一个整数
        if (prop in obj) {
          return obj[prop];
        }
    
        // 获取 products 的 number; 它是 products.length 的别名
        if (prop === 'number') {
          return obj.length;
        }
    
        let result, types = {};
    
        for (let product of obj) {
          if (product.name === prop) {
            result = product;
          }
          if (types[product.type]) {
            types[product.type].push(product);
          } else {
            types[product.type] = [product];
          }
        }
    
        // 通过 name 获取 product
        if (result) {
          return result;
        }
    
        // 通过 type 获取 products
        if (prop in types) {
          return types[prop];
        }
    
        // 获取 product type
        if (prop === 'types') {
          return Object.keys(types);
        }
    
        return undefined;
      }
    });
    
    console.log(products[0]); // { name: 'Firefox', type: 'browser' }
    console.log(products['Firefox']); // { name: 'Firefox', type: 'browser' }
    console.log(products['Chrome']); // undefined
    console.log(products.browser); // [{ name: 'Firefox', type: 'browser' }, { name: 'SeaMonkey', type: 'browser' }]
    console.log(products.types); // ['browser', 'mailer']
    console.log(products.number); // 3
    

    一个完整的traps列表示例

    出于教学目的,这里为了创建一个完整的 traps 列表示例,我们将尝试代理化一个非原生对象,这特别适用于这类操作:由发布在 document.cookie页面上的“小型框架”创建的docCookies全局对象。

    /*
      var docCookies = ... get the "docCookies" object here:  
      https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
    */
    
    var docCookies = new Proxy(docCookies, {
      "get": function (oTarget, sKey) {
        return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
      },
      "set": function (oTarget, sKey, vValue) {
        if (sKey in oTarget) { return false; }
        return oTarget.setItem(sKey, vValue);
      },
      "deleteProperty": function (oTarget, sKey) {
        if (sKey in oTarget) { return false; }
        return oTarget.removeItem(sKey);
      },
      "enumerate": function (oTarget, sKey) {
        return oTarget.keys();
      },
      "ownKeys": function (oTarget, sKey) {
        return oTarget.keys();
      },
      "has": function (oTarget, sKey) {
        return sKey in oTarget || oTarget.hasItem(sKey);
      },
      "defineProperty": function (oTarget, sKey, oDesc) {
        if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
        return oTarget;
      },
      "getOwnPropertyDescriptor": function (oTarget, sKey) {
        var vValue = oTarget.getItem(sKey);
        return vValue ? {
          "value": vValue,
          "writable": true,
          "enumerable": true,
          "configurable": false
        } : undefined;
      },
    });
    
    /* Cookies 测试 */
    
    alert(docCookies.my_cookie1 = "First value");
    alert(docCookies.getItem("my_cookie1"));
    
    docCookies.setItem("my_cookie1", "Changed value");
    alert(docCookies.my_cookie1);