数据劫持
在访问或者修改对象的某个属性时,不只对这个对象进行操作,还基于数据做了其他操作。例如:Angular、React、Vue这些,在改变state的时候对视图进行更新。
es5的Object.defineProperty()可以实现,但是有个问题就是数组的方法:push、pop、shift、unshift等方法,是无法监听的,只能遍历。
Proxy
在es6中新增了proxy和reflect,具体怎么用,先看个例子
const p = new Proxy ({a: 'a'}, {
get: (target, key, receiver) => {
console.log(`get ${key}`);
return target[key];
},
set: (target, key, value, receiver) => {
console.log(`set ${key} -> ${value}`);
target[key] = value;
return true;
}
});
console.log(p);
p.b = 1;
p.b += 2;
console.log(p);
跑一下
{ a: 'a' }
set b -> 1
get b
set b -> 3
{ a: 'a', b: 3 }
从上面的demo可以看出,new Proxy的时候实际上是对一个对象(即第一个参数)设置了拦截,当操作这个对象的时候,会触发handler(第二个参数),我们可以在handler里做一些额外的操作,比如更新视图。
当然也可以只在handler里设置get、set或者设置为{},当设置为{}的时候将没有拦截效果。
上面只用了get和set,proxy还有其他拦截,共13种,接下来看几个demo。
const target = {
a: 1,
b: 2
}
const p = new Proxy(target, {
has: (target, key) => {
console.log(`check ${key}`);
return key in target;
}
});
console.log('a' in p);
console.log('c' in p);
结果如下
check a
true
check c
false
这里的has是对key in obj做了拦截。
const target = {
a: 1,
b: 2
}
const handler = {
deleteProperty: (target, key) => {
console.log(`delete: ${key}`);
delete target[key];
return true;
}
}
const p = new Proxy(target, handler);
console.log(p);
delete p['a'];
console.log(target);
{ a: 1, b: 2 }
delete: a
{ b: 2 }
对delete拦截。
其他的方法就不说了可以看这里。
proxy还可以作为其他对象的原型。
const proxy = new Proxy({}, {
get: (target, key) => {
return 1;
}
});
const obj = Object.create(proxy);
console.log(obj.a);
看输出,运行之后会打印1。obj这个对象是没有a这个属性的,但是继承了proxy,proxy又对get做了拦截,所有属性值都返回1。
这里有个问题需要注意一下,就是this。使用了Proxy之后this就不再是target而是Proxy对象。
const target = {
a: 1,
testThis: function() {
console.log(this);
}
}
console.log(target.testThis());
const proxy = new Proxy(target, {});
console.log(proxy.testThis());
输出的结果
{a: 1, testThis: ƒ}
Proxy {a: 1, testThis: ƒ}
可以看见this的指向被改变了,但是有时候我们又需要用到this,可以用bind来绑定。
const target = {
a: 1,
testThis: function() {
console.log(this);
}
}
console.log(target.testThis());
const proxy = new Proxy(target, {
get: (target, key) => {
if (key === 'testThis') {
return target.testThis.bind(target);
}
return Reflect.get(target, key);
}
});
console.log(proxy.testThis());
{a: 1, testThis: ƒ}
{a: 1, testThis: ƒ}
Reflect
和Proxy相同也有13个方法,并且一一对应。
先把上边的例子改写一下
const p = new Proxy ({a: 'a'}, {
get: (target, key, receiver) => {
console.log(`get ${key}`);
return Reflect.get(target, key, receiver);
},
set: (target, key, value, receiver) => {
console.log(`set ${key} -> ${value}`);
target[key] = value;
return Reflect.set(target, key, value, receiver);
}
});
console.log(p);
p.b = 1;
p.b += 2;
console.log(p);
跑出来的结果和之前是一样的,同样has和delete也可以返回Reflect.has、Reflect.deleteProperty。
const target = {
a: 1,
b: 2
}
const handler = {
deleteProperty: (target, key) => {
console.log(`delete: ${key}`);
return Reflect.deleteProperty(target, key);
}
}
const p = new Proxy(target, handler);
console.log(p);
delete p['a'];
console.log(target);
Reflect不一定要和Proxy一起使用,是可以单独使用的。
const target = {
a: 1,
b: 2
}
console.log(Reflect.has(target, 'a'));
运行之后打印的是true,效果和'a' in target是一样的,但是写法上更js,是调用一个方法而不是执行一行命令。
并不是所有方法的效果都一样,有一些做了优化,比如Object.defineProperty在调用失败的时候会抛出一个异常,Reflect.defineProperty则会返回false这样更友好,不需要用try catch。
对比一下
// Object.defineProperty
try {
Object.defineProperty(target, property, attributes);
} catch (e) {
}
// Reflect
if (Reflect.defineProperty(target, property, attributes)) {
} else {
}
其他方法看这里。