proxy.md

proxy—(基础)

定义: proxy对js中的所有类型, 做一个代理, 所有操作都会先进入代理, 如果我们有设置对应操作的代理,那么优先使用我们自定义的操作(最常见是get/set).如果没有对应代理, 那么进入正常操作.

来看一下语法:

1
2
3
// target: 被代理的目标
// handler: 代理的行为函数
let p = new Proxy(target, handler);

来看一下mdn上面的示例, 我们能更好的理解proxy的行为

示例1: 代理object的get方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 初始化一个handler, 该handler的内容是设置get方法, 如果我们读取了不在该对象上的属性, 则返回默认值37
let handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};
// 初始化一个proxy对象 ,
// 参数1是一个空对象, 意味着我们没有对其他变量进行绑定
// 参数2是一个handler重写了get行为
let p = new Proxy({}, handler);

p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
// 此时,c不在p内, 所以返回默认值37
// 至此, 我们完成了对对象p, get方法的代理.
console.log('c' in p, p.c); // false, 37

代理转发, 数据绑定

简而言之, proxy构造函数的第一个参数,就是被绑定的元素。当我们修改proxy生成的实例时,修改会传递到被绑定的元素上。达到数据绑定的目的.

来看例子:

1
2
3
4
5
6
let target = {};
let p = new Proxy(target, {});

p.a = 37; // 操作转发到目标

console.log(target.a); // 37. 操作已经被正确地转发

验证对象赋值

我觉得代理有点像钩子的概念, 当我加入代理后, 那么我可以重写原有的默认行为(get/set), 也可以在原有的get/set增加逻辑, 就时钩子的概念啊.

这个例子重写了对象的set方法, 在set方法里验证对于属性’age’的赋值是否合法.并且保留默认的set行为.达到了验证的效果.

我们在vue/react都存在对应的props数据验证.在这个地方是否就是用proxy重写的?

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
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;
}
};

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

高阶应用之: 扩展构造函数

Reflect

这里要引入一个es2015的api.Reflect

Reflect是新增的一个内置对象, 这个对象不是构造函数, 无法使用new操作符,Reflect上所有的方法都是静态的.(类似Math.Refect对象)。

1
2
3
// 例如, get方法
var obj = {a: 1}
Reflect.get(obj, 'a') // ‘a’

所以,其实ReflectAPI实际上是, 把Reflect作为一个静态标识, 类似Math, 然后以第一个参数为this, 去调用this上的get方法.

注意了, 如果Reflect调用时, 对象的默认行为已经被修改, 那么此时, 会触发修改后的行为.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 类似这种效果
var likeReflect = {
get (obj, key) {
return obj['a']
}
}

var obj = {
a: 1
}
var prObj = new Proxy(obj, {
get: function(obj, key){
return obj[key] * 3;
}
})
likeReflect.get(prObj, 'a')

到这里, 关于Reflect的基本知识就足够了.

构造函数扩展

平常,我们都是通过以下方式来完成一个构造函数的扩展, 有了proxy我们就可以用一个新姿势来处理这种场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 手动实现构造函数OLO
function Person(name) {
this.name = name
}

function Man(name, sex) {
Person.call(this, name)
this.sex = sex
}
Man.prototype = Person.prototype
Man.prototype.constructor = Man

a = new Person('lili')
b = new Man('xiaoming', 11)

// 使用工具函数实现OLO
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}

扩展构造函数

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
function extend(sup,base) {
// 获取属性的属性(枚举/可读/可写/可配置)
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype,"constructor"
);
// 链接原型链, 子类->父类
base.prototype = Object.create(sup.prototype);
// proxy的处理函数,
// 重写construct: 使用上面的sup的新链, 并且调用原有逻辑(新建一个对象, 执行函数体, 返回obj)
// 重写apply: 保证初始化入参都被调用了 this.key = value
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);
}
};
// 以base为基础新建proxy
var proxy = new Proxy(base,handler);
// 重新设置constructor
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

使用proxy实现双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const input = document.getElementById('input');
const p = document.getElementById('p');
const obj = {};

const newObj = new Proxy(obj, {
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(target, key, value, receiver);
if (key === 'text') {
input.value = value;
p.innerHTML = value;
}
return Reflect.set(target, key, value, receiver);
}
});

input.addEventListener('keyup', function(e) {
newObj.text = e.target.value;
});