Proxy 是 ES6 出的语法,字面理解是“拦截”,提供了一种机制,对目标对象进行拦截,提供了 13 种拦截方式,对外界的访问进行过滤与修改 尝鲜 案例 1 对设置拦截行为的对象 obj,去读写它的属性 案例 2 Proxy 如何使用 案例 3 目标对象是一个空对象,拦截函数是返回 35,所以对任何属性的访问都是 35 案例 4 如果 handler 没有设置任何拦截,等同于目标对象 案例 5 …

Proxy 是 ES6 出的语法,字面理解是“拦截”,提供了一种机制,对目标对象进行拦截,提供了 13 种拦截方式,对外界的访问进行过滤与修改

尝鲜

  • 案例 1

    对设置拦截行为的对象 obj,去读写它的属性

var obj = new Proxy(
  {},
  {
    get: function(target, key, receiver) {
      console.log(`getting ${key}!`);
      return Reflect.get(target, key, receiver);
    },
    set: function(target, key, value, receiver) {
      console.log(`setting ${key}!`);
      return Reflect.set(target, key, value, receiver);
    }
  }
);
obj.count = 1;
--obj.count;
// 输出
// setting count!
// getting count!
// setting count!
  • 案例 2

    Proxy 如何使用

// target:        (必需, Object) 目标对象
// handler:       (必需, String) 定制拦截行为
var proxy = new Proxy(target, handler);
  • 案例 3

    目标对象是一个空对象,拦截函数是返回 35,所以对任何属性的访问都是 35

var proxy1 = new Proxy(
  {},
  {
    get: function(target, property) {
      return 35;
    }
  }
);
console.log(proxy1.koa);
console.log(proxy1.express);
// 输出
// 35
// 35
  • 案例 4

    如果 handler 没有设置任何拦截,等同于目标对象

var target = {};
var handler = {};
var proxy2 = new Proxy(target, handler);
proxy2.a = 'b';
console.log(proxy2.a);
// 输出
// b
  • 案例 5

    将 Proxy 设置到对象属性上,就可以在对象属性上调用

var object = { proxy: new Proxy(target, handler) };
  • 案例 6

    Proxy 实例可以作为其他对象的原型对象

var proxy3 = new Proxy(
  {},
  {
    get: function(target, property) {
      return 35;
    }
  }
);

let obj2 = Object.create(proxy3);
console.log(proxy3.name);
// 输出
// 35
  • 案例 7

    同一个拦截器函数,可以设置多个拦截操作

var handler2 = {
  get: function(target, name) {
    if (name === 'prototype') {
      return Object.prototype;
    }
    return 'Hello, ' + name;
  },
  set: function(target, name, value) {
    return value;
  },
  apply: function(target, thisBinding, args) {
    console.log(thisBinding);
    return args[0];
  },

  construct: function(target, args) {
    return {
      value: args[1]
    };
  }
};

var fproxy = new Proxy(function(x, y) {
  return x + y;
}, handler2);

console.log(fproxy(1, 2));
console.log(new fproxy(1, 2));
console.log(fproxy.prototype === Object.prototype);
console.log(fproxy.foo === 'Hello, foo');
console.log((fproxy.koa = 77));
// 输出
// 1
// { value: 2 }
// true
// true
// 77

13 种拦截操作

// 拦截对象属性的读取,比如proxy.foo和proxy['foo'] target是目标对象,propKey是对象属性,receiver是Proxy实例
get(target, propKey, receiver)
// 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值,target是目标对象,propKey是对象属性,value是设置的值,receiver是Proxy实例
set(target, propKey, value, receiver)
// 拦截propKey in proxy的操作,返回一个布尔值。target是目标对象,propKey是对象属性
has(target, propKey)
// 拦截delete proxy[propKey]的操作,返回一个布尔值。target是目标对象,propKey是对象属性
deleteProperty(target, propKey)
// 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性,target是目标对象
ownKeys(target)
// 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。target是目标对象,propKey是对象属性
getOwnPropertyDescriptor(target, propKey)
// 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。target是目标对象,propKey是对象属性,propDesc是描述符
defineProperty(target, propKey, propDesc)
// 拦截Object.preventExtensions(proxy),返回一个布尔值。target是目标对象
preventExtensions(target)
// 拦截Object.getPrototypeOf(proxy),返回一个对象。target是目标对象
getPrototypeOf(target)
// 拦截Object.isExtensible(proxy),返回一个布尔值。target是目标对象
isExtensible(target)
// 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。target是目标对象
setPrototypeOf(target, proto)
// 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。target是目标对象, object目标对象上下文(this), args是传入的参数
apply(target, object, args)
// 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。target是目标对象,args是传入的参数,newTarget创造实例对象时,new命令作用的构造函数
construct(target, args,newTarget)

具体的拦截操作

  • get()拦截方法

    1. 案例 1

      访问目标对象不存在,报错,如果没有拦截函数,就会返回 undefined

    var person = {
      name: '小明'
    };
    var proxy4 = new Proxy(person, {
      get: function(target, property) {
        if (property in target) {
          return target[property];
        } else {
          throw new ReferenceError('Property "' + property + '" does not exist.');
        }
      }
    });
    console.log(proxy4.name);
    // console.log(proxy4.age)
    // 输出
    // 小明
    // Property "age" does not exist.
    
    1. 案例 2

      get()拦截方法是可以继承的

    let proto = new Proxy(
      {},
      {
        get(target, propertyKey, receiver) {
          console.log('GET ' + propertyKey);
          return target[propertyKey];
        }
      }
    );
    
    let obj3 = Object.create(proto);
    obj3.foo;
    // 输出
    // GET foo
    
    1. 案例 3

      实现数组读取负数的索引

    function createArray(...elements) {
      let handler = {
        get(target, propKey, receiver) {
          let index = Number(propKey);
          if (index < 0) {
            propKey = String(target.length + index);
          }
          return Reflect.get(target, propKey, receiver);
        }
      };
    
      let target = [];
      target.push(...elements);
      return new Proxy(target, handler);
    }
    
    let arr = createArray('a', 'b', 'c');
    console.log(arr[-1]);
    // 输出
    // c
    
    1. 案例 4

      设置 Proxy 以后,达到了将函数名链式使用的效果

    var double = (n) => n * 2;
    var pow = (n) => n * n;
    var reverseInt = (n) =>
      n
        .toString()
        .split('')
        .reverse()
        .join('') | 0;
    var pipe = (function() {
      return function(value) {
        var funcStack = [];
        var oproxy = new Proxy(
          {},
          {
            get: function(pipeObject, fnName) {
              if (fnName === 'get') {
                return funcStack.reduce(function(val, fn) {
                  return fn(val);
                }, value);
              }
              funcStack.push(window[fnName]);
              return oproxy;
            }
          }
        );
        return oproxy;
      };
    })();
    console.log(pipe(3).double.pow.reverseInt.get);
    // 输出->3*2->6*6->63
    // 63
    
    1. 案例 5

      生成各种 DOM 节点的通用函数 dom

    const dom = new Proxy(
      {},
      {
        get(target, property) {
          return function(attrs = {}, ...children) {
            const el = document.createElement(property);
            for (let prop of Object.keys(attrs)) {
              el.setAttribute(prop, attrs[prop]);
            }
            for (let child of children) {
              if (typeof child === 'string') {
                child = document.createTextNode(child);
              }
              el.appendChild(child);
            }
            return el;
          };
        }
      }
    );
    const el = dom.div({}, 'Hello, my name is ', dom.a({}, 'name'), '.I love:', dom.ul({}, dom.li({}, 'study')));
    document.body.appendChild(el);
    // 输出
    // 往body中按照代码层级插入dom
    
    1. 案例 6

      get()拦截方法的第三个参数是 Proxy 实例

    const proxy5 = new Proxy(
      {},
      {
        get: function(target, property, receiver) {
          return receiver;
        }
      }
    );
    console.log(proxy5.getReceiver === proxy5);
    // 输出
    // true
    
    1. 案例 7

      d 对象本身没有 a 属性,读取 d.a 的时候,会去 d 的原型 proxy 去读取,receiver 指向 d,可以看出 receiver 代表原始读取操作所在的那个对象

    const proxy6 = new Proxy(
      {},
      {
        get: function(target, property, receiver) {
          return receiver;
        }
      }
    );
    
    const d = Object.create(proxy6);
    console.log(d.a === d);
    // 输出
    // true
    
    1. 案例 8

      如果对象的 configurable 为 false,则 Proxy 不能修改该属性

    const target7 = Object.defineProperties(
      {},
      {
        foo: {
          value: 123,
          writable: false,
          configurable: false
        }
      }
    );
    
    const handler7 = {
      get(target, propKey) {
        return 'abc';
      }
    };
    const proxy7 = new Proxy(target7, handler7);
    // console.log(proxy7.foo)
    // 输出
    // Proxy.js:277 Uncaught TypeError: 'get' on proxy: property 'foo' is a read-only and non-configurable data property on the proxy target
    
  • set()拦截方法

    1. 案例 1

      set 是拦截某个属性的赋值 操作,下例在赋值的时候拦截了大于 200 或者非数组的情况并报错

    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');
          }
        }
        // 对于满足条件的 age 属性以及其他属性,直接保存
        obj[prop] = value;
      }
    };
    let person1 = new Proxy({}, validator);
    person1.age = 100;
    console.log(person1.age);
    // person1.age = 'young'
    // person1.age = 300
    // 输出
    // 100
    // 报错
    // 报错
    
    1. 案例 2

      只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的

    const handler3 = {
      get(target, key) {
        invariant(key, 'get');
        return target[key];
      },
      set(target, key, value) {
        invariant(key, 'set');
        target[key] = value;
        return true;
      }
    };
    
    function invariant(key, action) {
      if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
      }
    }
    const target3 = {};
    const proxy = new Proxy(target3, handler3);
    proxy.prop = 1;
    console.log(proxy.prop);
    // proxy._prop = 1;
    // 输出
    // 1
    // 报错
    
    1. 案例 3

      receiver 指的是原始的操作行为所在的那个对象,一般情况下是 proxy 实例本身

    const handler4 = {
      set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
      }
    };
    const proxy8 = new Proxy({}, handler4);
    proxy8.foo = 'bar';
    console.log(proxy8.foo === proxy8);
    // 输出
    // true
    
    1. 案例 4

      设置 myObj 属性的值的时候,myObj 并没有 foo 属性,会到原型链上去找 foo 属性,myObj 的原型是 proxy9 proxy9 是一个 Proxy,设置 foo 触发 set 拦截方法,把所有属性都指向 receiver,receiver 就是原始原始赋值行为所在的对象 myObj

    const handler5 = {
      set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
      }
    };
    const proxy9 = new Proxy({}, handler5);
    const myObj = {};
    Object.setPrototypeOf(myObj, proxy9);
    
    myObj.foo = 'bar';
    console.log(myObj.foo === myObj);
    // 输出
    // true
    
    1. 案例 5

      目标对象自身的某个属性,不可写且不可配置,那么 set 方法将不起作用

    const obj4 = {};
    Object.defineProperty(obj4, 'foo', {
      value: 'bar',
      writable: false
    });
    
    const handler6 = {
      set: function(obj, prop, value, receiver) {
        obj[prop] = 'baz';
      }
    };
    
    const proxy10 = new Proxy(obj4, handler6);
    proxy10.foo = 'baz';
    console.log(proxy10.foo);
    // 输出
    // baz
    
    1. 案例 6

      严格模式下,set 代理如果没有返回 true,就会报错

    // 'use strict';
    const handler8 = {
      set: function(obj, prop, value, receiver) {
        obj[prop] = receiver;
        // 无论有没有下面这一行,都会报错
        return false;
      }
    };
    const proxy11 = new Proxy({}, handler8);
    proxy11.foo = 'bar123';
    // 输出
    // TypeError: 'set' on proxy: trap returned falsish for property 'foo'
    
  • apply()拦截方法

    1. 案例 1

      Proxy 的实例作为函数调用时,就会被 apply()方法拦截

    var target1 = function() {
      return 'I am the target';
    };
    var handler9 = {
      apply: function() {
        return 'I am the proxy';
      }
    };
    var p = new Proxy(target1, handler9);
    console.log(p());
    // 输出
    // I am the proxy
    
    1. 案例 2

      每当执行 proxy 函数(直接调用或 call 和 apply 调用),就会被 apply 方法拦截

    ar twice = {
    apply(target, ctx, args) {
      // 等同于Function.prototype.apply.call(...arguments) *2
      return Reflect.apply(...arguments) * 2;
    }
    };
    
    function sum(left, right) {
      return left + right;
    };
    var proxy12 = new Proxy(sum, twice);
    console.log(proxy12(1, 2))
    console.log(proxy12.call(null, 5, 6))
    console.log(proxy12.apply(null, [7, 8]))
    console.log(Reflect.apply(proxy12, null, [9, 10]))
    // 输出
    // (1+2)*2=6
    // (5+6)*2=22
    // (7+8)*2=30
    // (9+10)*2=38
    
  • has()拦截方法

    has 方法拦截的是 HasProperty 操作,而不是 HasOwnProperty 操作,即 has 方法不判断一个属性是对象自身的属性,还是继承的属性

    1. 案例 1

      原对象的属性名的第一个字符是下划线,proxy.has 就会返回 false,从而不会被 in 运算符发现

    var handler10 = {
      has(target, key) {
        if (key[0] === '_') {
          return false;
        }
        return key in target;
      }
    };
    var target2 = {
      _prop: 'foo',
      prop: 'foo'
    };
    var proxy13 = new Proxy(target2, handler10);
    console.log('_prop' in proxy13);
    // 输出
    // false
    
    1. 案例 2

      原对象不可配置或者禁止扩展,这时 has 拦截会报错

    var obj = {
      a: 10
    };
    Object.preventExtensions(obj);
    var p = new Proxy(obj, {
      has: function(target, prop) {
        return false;
      }
    });
    // console.log('a' in p)
    // 输出
    // 报错
    
    1. 案例 3

      has 拦截只对 in 运算符生效,虽然 for...in 循环也用到了 in 运算符,但是 has 拦截对 for...in 循环不生效

    let stu1 = {
      name: '张三',
      score: 59
    };
    let stu2 = {
      name: '李四',
      score: 99
    };
    
    let handler11 = {
      has(target, prop) {
        if (prop === 'score' && target[prop] < 60) {
          console.log(`${target.name} 不及格`);
          return false;
        }
        return prop in target;
      }
    };
    let oproxy1 = new Proxy(stu1, handler11);
    let oproxy2 = new Proxy(stu2, handler11);
    console.log('score' in oproxy1);
    console.log('score' in oproxy2);
    for (let a in oproxy1) {
      console.log(oproxy1[a]);
    }
    for (let b in oproxy2) {
      console.log(oproxy2[b]);
    }
    // 输出
    // 第一行输出
    // 张三 不及格
    // false
    // 第二行输出
    // true
    // 第三行输出
    // 张三
    // 59
    // 第四行输出
    // 李四
    // 99
    
  • construct()拦截方法

    1. 案例 1

      construct 方法用于拦截 new 命令

    var p = new Proxy(function() {}, {
      construct: function(target, args, newTarget) {
        console.log('called: ' + args.join(', '));
        return {
          value: args[0] * 10,
          target: newTarget
        };
      }
    });
    console.log(new p(1).value);
    console.log(new p(1).target);
    // 输出
    // called: 1
    // 10
    // ƒ () { [native code] }
    
    1. 案例 2

      construct 方法返回的必须是一个对象,否则会报错

    var p = new Proxy(function() {}, {
      construct: function(target, argumentsList) {
        return 1;
      }
    });
    // new p()
    // 输出
    // 报错
    
  • deleteProperty()拦截方法

    1. 案例 1

      deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除。

    var handler12 = {
      deleteProperty(target, key) {
        invariant(key, 'delete');
        delete target[key];
        return true;
      }
    };
    function invariant(key, action) {
      if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} private "${key}" property`);
      }
    }
    var target4 = {
      _prop: 'foo'
    };
    var proxy14 = new Proxy(target4, handler12);
    // console.log(delete proxy14._prop)
    // 输出
    // 报错
    
  • defineProperty()拦截方法

    defineProperty 方法拦截了 Object.defineProperty 操作。

    如果目标对象不可扩展(non-extensible),则 defineProperty 不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则 defineProperty 方法不得改变这两个设置。

    1. 案例 1

      defineProperty 方法返回 false,导致添加新属性总是无效

    var handler13 = {
      defineProperty(target, key, descriptor) {
        return true;
      }
    };
    var target5 = {};
    var proxy15 = new Proxy(target5, handler13);
    proxy15.foo = 'bar';
    console.log(proxy15.foo);
    // 输出
    // undefined
    
  • getOwnPropertyDescriptor()拦截方法

    1. 案例 1

      getOwnPropertyDescriptor 方法拦截 Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者 undefined。

    var handler14 = {
      getOwnPropertyDescriptor(target, key) {
        if (key[0] === '_') {
          return;
        }
        return Object.getOwnPropertyDescriptor(target, key);
      }
    };
    var target6 = {
      _foo: 'bar',
      baz: 'tar'
    };
    var proxy16 = new Proxy(target6, handler14);
    console.log(Object.getOwnPropertyDescriptor(proxy16, 'wat'));
    console.log(Object.getOwnPropertyDescriptor(proxy16, '_foo'));
    console.log(Object.getOwnPropertyDescriptor(proxy16, 'baz'));
    // 输出
    // undefined
    // undefined
    // {value: "tar", writable: true, enumerable: true, configurable: true}
    
  • getPrototypeOf()拦截方法

    getPrototypeOf 方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作

    • Object.prototype.proto
    • Object.prototype.isPrototypeOf()
    • Object.getPrototypeOf()
    • Reflect.getPrototypeOf()
    • instanceof

    注意,getPrototypeOf 方法的返回值必须是对象或者 null,否则报错。另外,如果目标对象不可扩展(non-extensible), getPrototypeOf 方法必须返回目标对象的原型对象。

    1. 案例 1
    var proto1 = {};
    var p = new Proxy(
      {},
      {
        getPrototypeOf(target) {
          return proto1;
        }
      }
    );
    console.log(Object.getPrototypeOf(p) === proto1);
    // 输出
    // true
    
  • isExtensible()拦截方法

    该方法只能返回布尔值,否则返回值会被自动转为布尔值

    1. 案例 1

      isExtensible 方法拦截 Object.isExtensible 操作

    var p = new Proxy(
      {},
      {
        isExtensible: function(target) {
          console.log('called');
          return true;
        }
      }
    );
    
    Object.isExtensible(p);
    console.log(Object.isExtensible(proxy) === Object.isExtensible(target));
    // 输出
    // called
    
    1. 案例 2
    var p = new Proxy(
      {},
      {
        isExtensible: function(target) {
          return false;
        }
      }
    );
    
    // Object.isExtensible(p)
    // 输出
    // 报错
    
  • ownKeys 拦截方法

    ownKeys 方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。

    • Object.getOwnPropertyNames()
    • Object.getOwnPropertySymbols()
    • Object.keys()
    • for...in 循环
    1. 案例 1
    let target8 = {
      a: 1,
      b: 2,
      c: 3
    };
    let handler15 = {
      ownKeys(target) {
        return ['a'];
      }
    };
    let proxy17 = new Proxy(target8, handler15);
    console.log(Object.keys(proxy17));
    // 输出
    // ["a"]
    
    1. 案例 2

      过滤下划线属性

    let target9 = {
      _bar: 'foo',
      _prop: 'bar',
      prop: 'baz'
    };
    let handler16 = {
      ownKeys(target) {
        return Reflect.ownKeys(target).filter((key) => key[0] !== '_');
      }
    };
    let proxy18 = new Proxy(target9, handler16);
    for (let key of Object.keys(proxy18)) {
      console.log(target9[key]);
    }
    // 输出
    // baz
    
    1. 案例 3

      使用 Object.keys 方法时,有三类属性会被 ownKeys 方法自动过滤,不会返回

    • 目标对象上不存在的属性
    • 属性名为 Symbol 值
    • 不可遍历(enumerable)的属性
    let target10 = {
      a: 1,
      b: 2,
      c: 3,
      [Symbol.for('secret')]: '4'
    };
    Object.defineProperty(target10, 'key', {
      enumerable: false,
      configurable: true,
      writable: true,
      value: 'static'
    });
    let handler17 = {
      ownKeys(target) {
        return ['a', 'd', Symbol.for('secret'), 'key'];
      }
    };
    let proxy19 = new Proxy(target10, handler17);
    console.log(Object.keys(proxy19));
    // 输出
    // [a]
    
    1. 案例 4

      ownKeys 方法还可以拦截 Object.getOwnPropertyNames()

    var p2 = new Proxy(
      {},
      {
        ownKeys: function(target) {
          return ['a', 'b', 'c'];
        }
      }
    );
    console.log(Object.getOwnPropertyNames(p2));
    // 输出
    // [ 'a', 'b', 'c' ]
    
    1. 案例 5

      for...in 循环也受到 ownKeys 方法的拦截

    const obj5 = {
      hello: 'world',
      name: 'koa'
    };
    const proxy20 = new Proxy(obj5, {
      ownKeys: function() {
        return ['a', 'name'];
      }
    });
    for (let key in proxy20) {
      console.log(key);
    }
    // 输出
    // name
    
    1. 案例 6

      ownKeys 方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。

    var obj = {};
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return [123, true, undefined, null, {}, []];
      }
    });
    // Object.getOwnPropertyNames(p)
    // 输出
    // 报错
    
    1. 案例 7

      如果目标对象自身包含不可配置的属性,则该属性必须被 ownKeys 方法返回,否则报错。

    var obj = {};
    Object.defineProperty(obj, 'a', {
      configurable: false,
      enumerable: true,
      value: 10
    });
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return ['b'];
      }
    });
    // for (let key in p) {
    // console.log(key);
    // }
    // 输出
    // 报错
    
    1. 案例 8

      如果目标对象是不可扩展的(non-extensible),这时 ownKeys 方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。

    var obj = {
      a: 1
    };
    Object.preventExtensions(obj);
    var p = new Proxy(obj, {
      ownKeys: function(target) {
        return ['a', 'vc'];
      }
    });
    // console.log(Object.getOwnPropertyNames(p))
    // 输出
    // 报错
    
  • preventExtensions()拦截方法

    preventExtensions 方法拦截 Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。

    1. 案例 1

      只有目标对象不可扩展,proxy.preventExtensions 才能返回 true

    var proxy21 = new Proxy(
      {},
      {
        preventExtensions: function(target) {
          return true;
        }
      }
    );
    
    // Object.preventExtensions(proxy21)
    // 输出
    // 报错
    
    1. 案例 2

      通常要在 proxy.preventExtensions 方法里面,调用一次 Object.preventExtensions

    var proxy22 = new Proxy(
      {},
      {
        preventExtensions: function(target) {
          console.log('called');
          Object.preventExtensions(target);
          return true;
        }
      }
    );
    console.log(Object.preventExtensions(proxy22));
    // 输出
    // called
    // Proxy {}
    
  • setPrototypeOf()拦截方法

    setPrototypeOf 方法主要用来拦截 Object.setPrototypeOf 方法。

    该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf 方法不得改变目标对象的原型。

    1. 案例 1

      只要修改原型对象就报错

    var handler18 = {
      setPrototypeOf(target, proto) {
        throw new Error('Changing the prototype is forbidden');
      }
    };
    var proto3 = {};
    var target11 = function() {};
    var proxy23 = new Proxy(target11, handler18);
    // Object.setPrototypeOf(proxy23, proto3);
    // 输出
    // 报错
    

其他

  • Proxy.revocable()

    Proxy.revocable()方法返回一个可取消的 Proxy

    proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy,执行 revoke 后,再访问 Proxy 实例就会报错

let target12 = {};
let handler19 = {};
let { proxy24, revoke } = Proxy.revocable(target12, handler19);
// proxy24.foo = 123;
// console.log(proxy24.foo)
// revoke();
// proxy24.foo
// 输出
// 123
// 报错
  • this 问题

    1. 案例 1

      在不做任何拦截的情况下,this 指向 Proxy 代理

    const target13 = {
      m: function() {
        console.log(this === proxy25);
      }
    };
    const handler20 = {};
    const proxy25 = new Proxy(target13, handler20);
    target13.m();
    proxy25.m();
    // 输出
    // false
    // true
    
    1. 案例 2

      没 Proxy 代理前,可以访问到 name 属性,代理 jane 后,并且没有任何拦截,导致 this 指向变化, 不指向构造函数,而是指向 Proxy,Proxy 没有 name 属性,就返回 undefined

    const _name = new WeakMap();
    class Person {
      constructor(name) {
        _name.set(this, name);
      }
      get name() {
        return _name.get(this);
      }
    }
    const jane = new Person('Jane');
    console.log(jane.name);
    const proxy26 = new Proxy(jane, {});
    console.log(proxy26.name);
    // 输出
    // jane
    // undefined
    
    1. 案例 3

      有些原生属性,Proxy 是没办法代理这些原生对象的属性

    const target14 = new Date();
    const handler21 = {};
    const proxy27 = new Proxy(target14, handler21);
    // proxy27.getDate();
    // 输出
    // 报错
    
    1. 案例 4

    为了解决上述没办法代理原生对象的属性问题,在获取属性的拦截方法中,重新改变 this 指向,就可以解决这个问题

    const target15 = new Date('2015-01-01');
    const handler22 = {
      get(target, prop) {
        if (prop === 'getDate') {
          return target.getDate.bind(target);
        }
        return Reflect.get(target, prop);
      }
    };
    const proxy28 = new Proxy(target15, handler22);
    console.log(proxy28.getDate());
    // 输出
    // 1
    
  • web 服务端问题

    Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端

暂无评论