ES6 Proxy 与 Reflect

1,055 阅读3分钟

ES6 Proxy 与 Reflect

概述

  • Proxy 与 Reflect 是 ES6 为了操作对象引入的 API。
  • Proxy 可以对目标对象的读取,函数调用等操作进行拦截,然后进行处理。它不直接操作对象,而是像代理模式,通过【对象】的【代理对象】进行操作,在进行这些操作时,可以添加一些额外的操作。
  • Reflect 可以用于获取【目标对象】的行为,它与 Object 类似,但更容易读。为了操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。

Proxy 基本用法

const target = {
    name: 'Tom',
    age: 8
}
const handler = {
    get: function(target, key) {
        console.log('get', key, target[key]); // get name Tom
        return target[key];
    },
    set: function(target, key, value) {
        console.log('set', key, value); // set age 9
        target[key] = value;
    }
}

let proxy = new Proxy(target, handler)
proxy.name // 实际执行 handler.get
proxy.age = 9 // 实际执行 handler.set

console.log(proxy) // Proxy {name: "Tom", age: 9, male: "man"}
console.log(target) //      {name: "Tom", age: 9, male: "man"}
// 通过 new Proxy() 新建实例时,其实是对【目标对象】进行了浅拷贝,因此【目标对象】与【代理对象】会相互影响

Proxy 与 Reflect 基本用法

const handler = {
    get(target, key, receiver) {
        const ownKeys = Reflect.ownKeys(target)
        //  只处理本身(非原型) 属性---原型的属性例如:push
        if (ownKeys.includes(key)) {
            console.log('get', key)
        }

        const result = Reflect.get(target, key, receiver)
        return result // 返回结果
    },
    set(target, key, val, receiver) {
        // 重复的数据或值没有改变不处理 
        // 场景: 
        // 1. 数组push()的时候,length不用重设; 
        // 2. proxyTom.age 已经是 8 再执行 proxyTom.age = 8
        const oldVal = target[key]
        if (val === oldVal) {
            return true
        }

        const result = Reflect.set(target, key, val, receiver)
        console.log('set', key, val)
        console.log('result---', result)
        return result // 是否设置成功 true:成功,false:失败
    },
    deleteProperty(target, key, receiver) {
        const result = Reflect.deleteProperty(target, key)
        console.log('deleteProperty', key)
        console.log('result---', result)
        return result // 是否删除成功 true:成功,false:失败
    }
}

const Tom = {
    name: 'Tom',
    age: 8,
    male: 'boy'
}

const proxyTom = new Proxy(Tom, handler)
proxyTom.age
proxyTom.age = 9
delete proxyTom.male

console.log('%c------------------------', 'color:blue')

const list = [1, 2, 3]
const proxyList = new Proxy(list, handler)
proxyList.push(4)
console.log('%c------------------------', 'color:blue')

Proxy 中的 receiver

情况一:

const Tom = {
    name: 'Tom'
}

const proxy = new Proxy(Tom, {
    get(target, key, receiver) {
        console.log(receiver === proxy) // true
        return target[key]
    }
});

proxy.name

情况二:

const Tom = {
    name: 'Tom'
}

const proxy = new Proxy(Tom, {
    get(target, key, receiver) {
        console.log(receiver === proxy) // false
        console.log(receiver === Jack) // true
        return target[key]
    },
});

const Jack = {}

// 将Jack的原型指向Tom的代理对象proxy
Object.setPrototypeOf(Jack, proxy);

Jack.name

总结:

  1. 谁读写属性,就是谁接收(即:谁读写属性,谁就是receiver),例如:上面情况一proxy.nameproxy读取name属性,所以proxy就是receiver; 情况二Jack.name 同理,Jackreceiver

什么情况 Proxy 要配合 Reflect 使用?

const Cat = {
    name: 'miaomiao',
    get value() {
        return this.name
    }
}

const handler = {
    get(target, key, receiver) {
        return Reflect.get(target, key) // 相当于 return target[key]
    }
}

const proxy = new Proxy(Cat, handler)
const Tom = {
    name: 'tomtom'
}
Object.setPrototypeOf(Tom, proxy)

console.log(Tom.value) // miaomiao

粗略步骤:

  1. Tom.value时,由于Tom本身不存在value属性,此时根据原型链向上读取它原型对象proxyvalue
  2. 触发proxy上的get value()
  3. 触发handlerget陷阱
  4. 进入get陷阱时,target为源对象Catkeyvalue
  5. get陷阱中返回Reflect.get(target, key)相当于target[key]
  6. Tom.value原本的调用方Tom,在get陷阱中被修改成了对应的target也就是Cat
  7. 导致最后打印出了Cat[value]也就是 miaomiao

我们希望的是,Tom.value 返回的是 tomtom,所以我们要把代码改成:

const Cat = {
    name: 'miaomiao',
    get value() {
        return this.name
    }
}

const handler = {
    get(target, key, receiver) {
        -
        return Reflect.get(target, key) // 相当于 return target[key]
            +
            return Reflect.get(target, key, receiver) // 可以理解为 target[key].call(receiver)
    }
}

const proxy = new Proxy(Cat, handler)
const Tom = {
    name: 'tomtom'
}
Object.setPrototypeOf(Tom, proxy)

console.log(Tom.value) // tomtom

相关

参考