es6 中的 Proxy 和 Reflect

Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

语法:

var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。
其中:

  1. target:表示要代理的对象
  2. handler:也是一个对象,用来定义要代理的对象的行为
var obj = {}
// 代理 obj 对象中属性的 get 和 set 行为
var proxyObj = new Proxy(obj, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});
// 赋值语句触发 setter,代理 obj 对象的 set 行为,控制台打印 setting count!
proxyObj.count = 1
// 控制台打印:getting count!	setting count!
++proxyObj.count
console.log(obj)	// { count: 2 } 
// 没有经过 proxyObj 的代理,控制台直接打印:2 ===> counthahahah
console.log(obj.count, "===> counthahahah")	

obj.count = 0

console.log(proxyObj)	// { count: 0 }
console.log(proxyObj.count)	// getting count!

Proxy 实例也可以作为其他对象的原型对象。

var proxy = new Proxy({}, {
  get: function(target, propKey, receiver) {
    return 35;
  }
});
let obj = Object.create(proxy);
obj.time // 35

上面代码中,proxy 对象是 obj 对象的原型,obj 对象本身并没有 time 属性,所以根据原型链,会在 proxy 对象上读取该属性,导致被拦截。

proxy 代理的 this 指向问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()  // true

上面代码中,一旦 proxy 代理 target.m ,后者内部的 this 就是指向 proxy ,而不是 target

这时只要改变 this 指向就可以解决这个问题

const target = {
  m: function () {
    console.log(this === proxy);
  }
};
const handler = {
  get(target, propName) {
    if(propName === 'm') {
      // 绑定 this 指针
      return target.m.bind(target)
    }
    return Reflect.get(target, propName)
  }
};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m()  // false

proxy 支持的代理操作

阮一峰 ES6 教程 ---- bookstack.cn/read/es6-3rd/spilt.2.docs-proxy.md

get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、 for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。

Reflect

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect 对象的设计目的有这样几个:

  1. Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty ),放到 Reflect 对象上。
  2. 修改某些 Object 方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而 Reflect.defineProperty(obj, name, desc) 则会返回false。
  3. Object 操作都变成函数行为。某些 Object 操作是命令式,比如 name in objdelete obj[name] ,而 Reflect.has(obj, name)Reflect.deleteProperty(obj, name) 让它们变成了函数行为。
  4. Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。
Proxy(target, {
  set: function(target, name, value, receiver) {
  	// eg:Reflect.set 方法设置 target 对象的 name 属性,值为 value。
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
});

上面代码中,Proxy 方法拦截 target 对象的属性赋值行为。它采用 Reflect.set 方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能

下面是另一个例子。

var loggedObj = new Proxy(obj, {
  get(target, name) {
    console.log('get', target, name);
    return Reflect.get(target, name);
  },
  deleteProperty(target, name) {
    console.log('delete' + name);
    return Reflect.deleteProperty(target, name);
  },
  has(target, name) {
    console.log('has' + name);
    return Reflect.has(target, name);
  }
});

上面代码中,每一个 Proxy 对象的拦截操作(get、delete、has),内部都调用对应的 Reflect 方法,保证原生行为能够正常执行。

使用 proxy 实现观察者模式

观察者模式 ---- https://blog.csdn.net/weixin_43842373/article/details/121975015

class Subject {
  observers = new Set()   // 存放观察者列表
  constructor(obj) {
    this.obj = new Proxy(obj, {   // 可观察对象
      set: (target, key, value, receiver) => {
        const result = Reflect.set(target, key, value, receiver)
        // 多播,执行观察者函数
        this.observers.forEach(observer => {
          observer(this.obj)  
        });
        return result
      }
    })  
  }

  /**
   * 观察者观察目标对象,传入一个观察者函数
   * @param {Function} fn 观察者函数
   */
  observe(fn) {
    this.observers.add(fn)
  }
}

const subject = new Subject({
  name: 'test',
  age: 18,
  sex: '男'
})
// 观察
subject.observe((people) => {
  console.log('你好,对象改变了的话告诉我')
})
subject.observe((people) => {
  console.log('哈哈哈哈哈', people)
})

subject.obj.name = '星星'

你可能感兴趣的:(javascript,javascript)