Skip to content
On this page

Proxy代理器的使用

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

Proxy 相关词汇

  • handler:包含拦截器等占位符对象,可以理解为处理器对象;
  • tarps:提供属性访问的方法,这类似于操作系统中捕获器的概念;
  • target:被 Proxy 代理虚拟化的对象。

Proxy 语法

const p = new Proxy(target, handler)

参数

target :要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至是另外一个代理对象);

target:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为;

Proxy 小试牛刀

这里创建一个 person 对象,并初始化 2 个属性,分别 name张三age18,接着使用 Proxy 去代理它,返回一个 person 代理对象,最终使用赋值、取值来验证是否代理成功。

js
// 创建 person 对象,并初始化 name、age
let person = {};
person.name = '张三';
person.age = 18;

// 使用 new Proxy 创建 person 的代理对象
person = new Proxy(person, {
    get(target, key) {
        console.log(`getting ${key}!`);
        return target[key];
    },
    set(target, key, value) {
        console.log(`setting ${key}!`);
        target[key] = value;
    }
});

// 更改 person 的属性
person.name = '李四';
person.age = 20;
// 获取 person 的属性
console.log(person.name);
console.log(person.age);

new Proxy() 里面包含了 2 个参数,person 就是需要代理的对象,{...} 对象就是我们所说的 handler 对象。

handler 对象中有 2 个方法,分别是 getset,这两个方法就是所谓的 tarps(拦截器)

以上代码输出内容

setting name!
setting age!
getting name!
李四
getting age!
20

可以看到当我们使用 person.xxx=xxx 设置属性的时候会进入我们写的 set 拦截器中,同样当我们使用 person.xxx 获取属性的时候会进入 get 拦截器,这样我们就可以在 setget 代码中写入我们自己的逻辑,例如使用 console.log 拦截信息,也可以使其获取固定值;

当然 traps(拦截器) 方法不只只有 getset 这两个,接下来列出所有的拦截器;

Proxy 拦截器

handler 对象是一个包容一批特定属性的占位符对象。它包含有 Proxy 的各个拦截器(trap)。

所有的拦截器都是可选的,如果没有定义某个拦截器,那么就会保留源对象的默认行为。

handler.apply()

描述

handler.apply() 方法用于拦截函数的调用。

语法

js
var p = new Proxy(target, {
  apply: function(target, thisArg, argumentsList) {
  }
});

参数

以下是传递给 apply 方法的参数,this 上下文绑定在 handler 对象上。

target:目标对象(函数)。

thisArg :被调用时的上下文对象。

argumentsList:被调用时的参数数组。

返回值

apply 方法可以返回任何值。

作用于

该方法会拦截目标对象的以下操作:

  • proxy(...args)
  • Function.prototype.apply()Function.prototype.call()
  • Reflect.apply()

代码示例

js
// 创建一个sum函数,返回a+b的值
function sum(a, b) {
    return a + b;
}

// 把sum函数包装成一个代理函数,使得sum函数的参数总是为10和20
const sumProxy = new Proxy(sum, {
    apply: function (target, thisArg, argumentsList) {
        console.log('target', target);
        console.log('thisArg', thisArg);
        console.log('argumentsList', argumentsList);
        // 在这里可以对参数进行修改
        argumentsList[0] = 10;
        argumentsList[1] = 20;
        return target.apply(thisArg, argumentsList);
    }
});

// 未添加代理的sum函数
console.log(`未添加代理的结果: ${sum(1, 2)}`);
// 添加代理的sum函数
console.log(`添加代理的结果: ${sumProxy(1, 2)}`);

输出内容

未添加代理的结果: 3
target ƒ sum(a, b) {
    return a + b;
}
thisArg undefined
argumentsList (2) [1, 2]
添加代理的结果: 30

handler.construct()

描述

handler.construct() 方法用于拦截 new 操作符。为了使 new 操作符在生成的 Proxy 对象上生效,用于初始化代理的目标对象自身必须具有 [[Construct]] 内部方法(即 new target 必须是有效的)。

语法

js
var p = new Proxy(target, {
  construct: function(target, argumentsList, newTarget) {
  }
});

参数

下面的参数将会传递给 construct 方法,this 绑定在 handler 上。

target:目标对象(函数)。

argumentsList:被调用时的参数数组。

newTarget:最初被调用的构造函数,上面的例子而言是 p。

返回值

construct 方法必须返回一个对象。

作用于

该拦截器可以拦截以下操作:

  • new Proxy(...args)
  • Reflect.construct()

代码示例

js
// 创建一个person函数,用于创建对象,需要接收两个参数,name和age
function person(name, age) {
    this.name = name;
    this.age = age;
}

// 创建一个personProxy代理,用于代理person函数,当使用new关键字调用personProxy时,会触发construct拦截
const personProxy = new Proxy(person, {
    construct(target, args) {
        console.log(`Creating a ${target.name}`);
        console.log(`Creating args ${args}`)
        return new target(...args);
    }
})

// 使用new关键字调用personProxy函数,会触发construct拦截
const me = new personProxy('John', 30);

输出内容

Creating a person
Creating args John,30

handler.defineProperty()

描述

handler.defineProperty() 用于拦截对象的 Object.defineProperty() 操作。

语法

js
var p = new Proxy(target, {
  defineProperty: function(target, property, descriptor) {
  }
});

参数

下列参数将会被传递给 defineProperty 方法。this 绑定在 handler 对象上。

target:目标对象(函数)。

property:待检索其描述的属性名。

descriptor:待定义或修改的属性的描述符。

返回值

defineProperty 方法必须以一个 Boolean 返回,表示定义该属性的操作成功与否。

作用于

该拦截器可以拦截以下操作:

  • Object.defineProperty()
  • Reflect.defineProperty()

代码示例

js
// 创建一个person对象
let person = {};

// 创建person对象的代理
personProxy = new Proxy(person, {
    defineProperty(target, property, attributes) {
        // 输出需要修改的属性和属性描述符
        console.log('called: ' + property + 'attributes: ' + JSON.stringify(attributes));
        // 调用Reflect.defineProperty()方法修改属性
        return Reflect.defineProperty(target, property, attributes)
    }
});

// 修改person的age属性
Object.defineProperty(personProxy, 'age', {
    configurable: true,
    enumerable: true,
    value: 18,
    type: 'number' // ERROR,此行代码不会生效
});

// 输出person的age属性描述符
console.log(Object.getOwnPropertyDescriptor(personProxy, 'age'));

输出内容

called: ageattributes: {"value":18,"enumerable":true,"configurable":true}
{value: 18, writable: false, enumerable: true, configurable: true}

上述代码中有一行代码 type: 'number' ,这行代码并不会生效,因为可以从输出内容可以看出并没有 type 字段,这是因为递给 defineProperty 的 descriptor 有一个限制 - 只有以下属性才有用,非标准的属性将会被无视: enumerableconfigurablewritablevaluegetset

handler.deleteProperty()

描述

handler.deleteProperty() 方法用于拦截对对象属性的 delete 操作。

语法

js
var p = new Proxy(target, {
  deleteProperty: function(target, property) {
  }
});

参数

deleteProperty 方法将会接受以下参数。this 被绑定在 handler 上。

target:目标对象(函数)。

property:待删除的属性名。

返回值

deleteProperty 必须返回一个 Boolean 类型的值,表示了该属性是否被成功删除。

作用于

该拦截器可以拦截以下操作:

  • 删除属性:delete proxy[foo] 和 delete proxy.foo
  • Reflect.deleteProperty()

代码示例

js
// 创建一个person对象,包含name、age属性
let person = {
    name: '张三',
    age: 18
}

// 创建一个代理对象
let personProxy = new Proxy(person, {
    deleteProperty(target, p) {
        console.log('delete', p);
        Reflect.deleteProperty(target, p);
    }
});

// 删除personProxy的name属性
delete personProxy.name;

输出内容

delete name

handler.get()

描述

handler.get() 方法用于拦截对象的读取属性操作。

语法

js
var p = new Proxy(target, {
  get: function(target, property, receiver) {
  }
});

参数

以下是传递给 get 方法的参数,this 上下文绑定在handler 对象上。

target:目标对象。

property:被获取的属性名。

recevier:Proxy 或者继承 Proxy 的对象。

返回值

get 方法可以返回任何值。

作用于

该方法会拦截目标对象的以下操作:

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
  • Reflect.get()`

代码示例

js
let person = {
    name: '张三',
    age: 18,
};

let personProxy = new Proxy(person, {
    get(target, property, receiver) {
        console.log('get', property);
        return Reflect.get(target, property, receiver);
    }
});

personProxy.name;
personProxy.age;

输出内容

get name
get age

handler.getOwnPropertyDescriptor()

描述

handler.getOwnPropertyDescriptor() 方法是 Object.getOwnPropertyDescriptor() 的钩子。

语法

js
var p = new Proxy(target, {
  getOwnPropertyDescriptor: function(target, prop) {
  }
});

参数

下列参数会被传入 getOwnPropertyDescriptor 方法中。这是绑定到 handler 上。

target:目标对象。

prop:返回属性名称的描述。

返回值

getOwnPropertyDescriptor 方法必须返回一个 object 或 undefined

作用于

这个陷阱可以拦截这些操作:

代码示例

js
let person = {
    name: '张三',
    age: 18,
};

let personProxy = new Proxy(person, {
    getOwnPropertyDescriptor(target, prop) {
        console.log(`called: ${prop}`);
        return Reflect.getOwnPropertyDescriptor(target, prop);
    }
});

console.log(Object.getOwnPropertyDescriptor(personProxy, 'name'));

输出内容

called: name
{value: '张三', writable: true, enumerable: true, configurable: true}

handler.getPrototypeOf()

描述

handler.getPrototypeOf() 是一个代理(Proxy)方法,当读取代理对象的原型时,该方法就会被调用。

语法

js
const p = new Proxy(obj, {
  getPrototypeOf(target) {
  ...
  }
});

参数

当 getPrototypeOf 方法被调用时,this 指向的是它所属的处理器对象。

target:目标对象。

返回值

getPrototypeOf 方法的返回值必须是一个对象或者 null

作用于

在 JavaScript 中,下面这五种操作(方法/属性/运算符)可以触发 JS 引擎读取一个对象的原型,也就是可以触发 getPrototypeOf() 代理方法的运行:

代码示例

js
let person = {};

personProxy = new Proxy(person, {
    getPrototypeOf(target) {
        console.log('getPrototypeOf');
        return Reflect.getPrototypeOf(target);
    }
});

// 以下是 5 种 触发 getPrototypeOf 代理方法的方式
console.log(Object.getPrototypeOf(personProxy) === person.__proto__);
console.log(Reflect.getPrototypeOf(personProxy) === person.__proto__);
console.log(personProxy.__proto__ === person.__proto__);
console.log(Object.prototype.isPrototypeOf(personProxy));
console.log(personProxy instanceof Object);

输出内容

getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true
getPrototypeOf
true

handler.has()

描述

handler.has() 方法是针对 in 操作符的代理方法。

语法

js
var p = new Proxy(target, {
  has: function(target, prop) {
  }
});

参数

下面是传递给 has 方法的参数。this is bound to the handler.

target:目标对象。

prop:需要检查是否存在的属性。

返回值

has 方法返回一个 boolean 属性的值。

作用于

这个钩子可以拦截下面这些操作:

  • 属性查询:foo in proxy
  • 继承属性查询:foo in Object.create(proxy)
  • with 检查: with(proxy) { (foo); }
  • Reflect.has()

代码示例

js
let person = {
    name: '张三',
    age: 18,
};

let personProxy = new Proxy(person, {
    has: function (target, prop) {
        console.log('has', prop);
        return Reflect.has(target, prop);
    }
});

console.log('name' in personProxy);
console.log('age' in personProxy);
console.log('height' in personProxy);

输出内容

has name
true
has age
true
has height
false

handler.isExtensible()

描述

handler.isExtensible() 方法用于拦截对对象的 Object.isExtensible()。

Object.isExtensible() 静态方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。

语法

js
var p = new Proxy(target, {
  isExtensible: function(target) {
  }
});

参数

下列参数将会被传递给 isExtensible方法。this 绑定在 handler 对象上。

target:目标对象。

返回值

isExtensible方法必须返回一个 Boolean 值或可转换成 Boolean 的值。

作用于

该方法会拦截目标对象的以下操作:

代码示例

js
let person = {
    name: '张三',
    age: 18,
};

let personProxy = new Proxy(person, {
    isExtensible: function (target) {
        console.log('isExtensible');
        return Reflect.isExtensible(target);
    },
});


console.log(Object.isExtensible(personProxy));


// 关于 Object.isExtensible 一些知识点

// 新对象是可拓展的。
const empty = {};
Object.isExtensible(empty); // true

// 它们可以变为不可拓展的
Object.preventExtensions(empty);
Object.isExtensible(empty); // false

// 根据定义,密封对象是不可拓展的。
const sealed = Object.seal({});
Object.isExtensible(sealed); // false

// 根据定义,冻结对象同样也是不可拓展的。
const frozen = Object.freeze({});
Object.isExtensible(frozen); // false

输出内容

isExtensible
true

handler.ownKeys()

描述

handler.ownKeys() 方法用于拦截 Reflect.ownKeys().

语法

js
var p = new Proxy(target, {
  ownKeys: function(target) {
  }
});

参数

下面的参数被传递给 ownKeys。this 被绑定在 handler 上。

target:目标对象。

ownKeys:方法必须返回一个可枚举对象。

返回值

isExtensible方法必须返回一个 Boolean 值或可转换成 Boolean 的值。

作用于

该拦截器可以拦截以下操作::

代码示例

js
let person = {
    name: '张三',
    age: 18,
};

let personProxy = new Proxy(person, {
    ownKeys: function (target) {
        return Reflect.ownKeys(target);
    }
});

console.log(Object.getOwnPropertyNames(personProxy));

输出内容

['name', 'age']

handler.preventExtensions()

描述

handler.preventExtensions() 方法用于设置对Object.preventExtensions()的拦截

语法

js
var p = new Proxy(target, {
  preventExtensions: function(target) {
  }
});

参数

以下参数传递给 preventExtensions 方法。它会绑定到这个 handler.

target:目标对象。

返回值

preventExtensions 方法返回一个布尔值。

作用于

这个 trap 可以拦截这些操作:

代码示例

js
let person = {
};

let personProxy = new Proxy(person, {
    preventExtensions: function (target) {
        console.log('preventExtensions');
        return Object.preventExtensions(target);

    },
});

console.log(Object.preventExtensions(personProxy));

输出内容

preventExtensions
{}

handler.set()

描述

handler.set() 方法是设置属性值操作的捕获器。

语法

js
const p = new Proxy(target, {
  set: function(target, property, value, receiver) {
  }
});

参数

以下是传递给 get 方法的参数,this 上下文绑定在handler 对象上。

target:目标对象。

property:将被设置的属性名或 Symbol

value:新属性值。

recevier:最初被调用的对象。通常是 proxy 本身,但 handler 的 set 方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是 proxy 本身)。

假设有一段代码执行 obj.name = "jen", obj 不是一个 proxy,且自身不含 name 属性,但是它的原型链上有一个 proxy,那么,那个 proxy 的 set() 处理器会被调用,而此时,obj 会作为 receiver 参数传进来。

返回值

set() 方法应当返回一个布尔值。

作用于

该方法会拦截目标对象的以下操作:

  • 指定属性值:proxy[foo] = bar 和 proxy.foo = bar
  • 指定继承者的属性值:Object.create(proxy)[foo] = bar
  • Reflect.set()

代码示例

js
let person = {
};

let personProxy = new Proxy(person, {
    set: function (target, prop, value, receiver) {
        console.log(`property set ${prop} = ${value}`);
        return Reflect.set(target, prop, value, receiver);
    },
});


personProxy.name = '张三';
personProxy.age = 18;

输出内容

property set name = 张三
property set age = 18

handler.setPrototypeOf()

描述

handler.setPrototypeOf() 方法主要用来拦截 Object.setPrototypeOf().

语法

js
var p = new Proxy(target, {
  setPrototypeOf: function(target, prototype) {
  }
});

参数

以下参数传递给 setPrototypeOf 方法。

target:目标对象。

prototype:对象新原型或 null

返回值

如果成功修改了[[Prototype]]setPrototypeOf 方法返回 true,否则返回 false.

作用于

这个方法可以拦截以下操作:

代码示例

js
let person = {
};

let personProxy = new Proxy(person, {
    setPrototypeOf(target, newProto) {
        console.log('setPrototypeOf');
        return Reflect.setPrototypeOf(target, newProto);
    }
});

Object.setPrototypeOf(personProxy, Array.prototype);

输出内容

setPrototypeOf

总结

  1. Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等);
  2. target 需要被代理的对象,handler 包含了很多 traps(拦截器)称为处理对象,traps 有 getsethas 等;
  3. 代理对象可以用于输出一些关键信息,例如代码中调用了哪些对象的属性、函数等;

Released under the MIT License.