Proxy & Reflect

264 阅读1分钟

数据劫持

在访问或者修改对象的某个属性时,不只对这个对象进行操作,还基于数据做了其他操作。例如:AngularReactVue这些,在改变state的时候对视图进行更新。
es5的Object.defineProperty()可以实现,但是有个问题就是数组的方法:pushpopshiftunshift等方法,是无法监听的,只能遍历。

Proxy

在es6中新增了proxyreflect,具体怎么用,先看个例子

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里设置getset或者设置为{},当设置为{}的时候将没有拦截效果。
上面只用了getsetproxy还有其他拦截,共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这个属性的,但是继承了proxyproxy又对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);

跑出来的结果和之前是一样的,同样hasdelete也可以返回Reflect.hasReflect.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 {

}

其他方法看这里