◆

JavaScript Proxy 代理拦截对象操作详解

2023-02-02 01:370

proxy

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。其中,new Proxy() 表示生成一个 Proxy 实例,target 参数表示所要拦截的目标对象,handler 参数也是一个对象,用来定制拦截行为。

示例

let xiaohong = {
  name: '小红',
  age: 15,
};
xiaohong = new Proxy(xiaohong, {
  get(target, key) {
    let result = target[key];
    // 如果是获取 年龄 属性,则添加 岁字
    if (key === 'age') result += '岁';
    return result;
  },
  set(target, key, value) {
    if (key === 'age' && typeof value !== 'number') {
      throw Error('age字段必须为Number类型');
    }
    return Reflect.set(target, key, value);
  },
});
console.log(`我叫${xiaohong.name}  我今年${xiaohong.age}了`);
// xiaohong.age = "aa";
// 上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为

执行结果如下

上面代码中,作为构造函数,Proxy 接受两个参数。第一个参数是所要代理的目标对象; 第二个参数是一个配置对象, 对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。

  • 如果 handler 没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a; // "b"

上面代码中,handler 是一个空对象,没有任何拦截效果,访问 proxy 就等同于访问 target。

Proxy 实例方法

get

get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身 (严格地说,是操作行为所针对的对象),其中最后一个参数可选。

var person = {
  name: '张三',
};

var proxy = new Proxy(person, {
  get: function (target, propKey) {
    if (propKey in target) {
      return target[propKey];
    } else {
      throw new ReferenceError('Prop name "' + propKey + '" does not exist.');
    }
  },
});

proxy.name; // "张三"
proxy.age; // 抛出一个错误

上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回 undefined。

set()

set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

示例

在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合 get 和 set 方法,就可以做到防止这些内部属性被外部读写。

const handler = {
  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 target = {};
const proxy = new Proxy(target, handler);
proxy._prop;
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c';

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

apply()

  • apply 方法拦截函数的调用、call 和 apply 操作。
  • apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

示例

var target = function () {
  return 'I am the target';
};
var handler = {
  apply: function () {
    return 'I am the proxy';
  },
};

var p = new Proxy(target, handler);

p();
// "I am the proxy"

上面代码中,变量 p 是 Proxy 的实例,当它作为函数调用时(p()),就会被 apply 方法拦截,返回一个字符串。

var twice = {
  apply(target, ctx, args) {
    return Reflect.apply(...arguments) * 2;
  },
};

function sum(left, right) {
  return left + right;
}
var proxy = new Proxy(sum, twice);
proxy(1, 2); // 6
proxy.call(null, 5, 6); // 22
proxy.apply(null, [7, 8]); // 30
Reflect.apply(proxy, null, [9, 10]); // 38

上面代码中,每当执行 proxy 函数(直接调用或 call 和 apply 调用),就会被 apply 方法拦截。 另外,直接调用 Reflect.apply 方法,也会被拦截。

has()

  • has() 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。
  • has() 方法可以接受两个参数,分别是目标对象、需查询的属性名。
  • 如果原对象不可配置或者禁止扩展,这时 has() 拦截会报错。例:obj 使用 Object.preventExtensions(obj);
  • has() 方法拦截的是 HasProperty 操作,而不是 HasOwnProperty 操作,即 has() 方法不判断一个属性是对象自身的属性,还是继承的属性。
  • for...in 循环也用到了 in 运算符,但是 has() 拦截对 for...in 循环不生效
var handler = {
  has(target, key) {
    if (key[0] === '_') {
      return false;
    }
    return key in target;
  },
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy; // false

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has()就会返回 false,从而不会被 in 运算符发现。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVEJavaScript Proxy 代理拦截对象操作详解加载中