沐光

记录在前端之路的点点滴滴

浅谈 Proxy

前言

最近阅读的 《你所不知道的 JavaScript 下篇》 基本上讲的都是 ES6 的一些知识,先前跟着阮一峰老师的 《ES6 入门》 一书,将大多数工作中常用的知识都过了一遍,但是少数稍微复杂的却都是“点到为止”,Proxy 代理正属于这一类。现在正好又学到了这一块,为了加深印象,还是写一篇博文以作记录。

此篇仅记录一些基础内容,方便理解,更为深层次的内容之后有时间进行补充。

简介

Proxy 是什么,其有什么作用?简单来说,其主要是拦截对目标对象进行的一些操作,方便定义一些基本的操作以及配置自定义的用户行为。

Proxy 的语法很简单,如下:

1
const proxyObject = new Proxy(target, handler);

其中 target 为需要进行“代理”的对象,handler 则是对该对象进行拦截的一些配置了。

引: Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

handler 方法

handler 总共有 13 种可代理的方法,每一种方法都有与之对应的 Reflect 函数,具体的我就不全部列举出来了,这里今介绍一些可能会常用的:

getOwnPropertyDescriptordefineProperty

此两操作可以算是一种配套的操作,一个读取对象的属性描述符,另一个配置对象,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var target = {};

var p = new Proxy(target,{
getOwnPropertyDescriptor(target, prop) {
console.log('called' + prop);
return Reflect.getOwnPropertyDescriptor(target, prop);
},
defineProperty(target, prop, descriptor) {
console.log('defined', prop);
return Reflect.defineProperty(target, prop, descriptor);
}
});

p.age = 123;

// called age
// defined age

注意: 当 handler 中有设置 get/set 方法时,此两 trap 会被覆盖掉。

getset

此两方法一眼便知是拦截对象的读写操作,与上面的不同的是,此仅仅涉及到读与写的配置,更为详细的配置还是得依靠 defineProperty 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var target = {}

var p = new Proxy(target,{
get(target, prop){
console.log('get', prop)
return Reflect.get(target, prop) || '-';
// 直接 target[prop] 结果也一致
},
set(target, prop, value, receiver) {
console.log('set', prop, value);
return Reflect.set(target, prop, value, receiver);
}
});


p.a = 123;

console.log(p.a);

// set a 123
// get a
// 123
applyconstruct

此两方法主要用于扩展构造函数,例如 MDN 上的 extend 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function extend(sup, base) {
// 获取 base 的 constructor 描述(不是原型链描述,而是构造函数描述)
// base === base.prototype.constructor
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype, "constructor"
);

// base 的 prototype 继承 sup 的 prototype
base.prototype = Object.create(sup.prototype);

// 添加 base 构造函数的代理
var handler = {
// new 对象时为目标对象绑定 __proto__ 链(就是手动实现 new 的过程)
construct: function(target, args) {
// 创建目标函数
var obj = Object.create(base.prototype);
// 注:this 为 handler 对象,触发 apply 方法,绑定示例属性
this.apply(target, obj, args);
// 返回目标函数
return obj;
},
// 调用目标函数时,将 sup 的 base 的作用域绑定至实例对象
// 也就是原来构造函数的那种 apply/call 写法
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
};

// 注意 name 需写在 age 前面,与父类保持一致
// 内部不在需要 apply/call 方法,已经通过代理处理了
var Boy = extend(Person, function(name, age) {
this.age = age;
});

Boy.prototype.sex = "M";

// 此才会出发 handler
var Peter = new Boy("Peter", 13);

console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13

参考文章