proxy&&defineProperty

242 阅读2分钟

Object.defineProperty() 和 ES2015 中新增的 Proxy对象,会经常用来做数据劫持.

数据劫持:在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果.数据劫持最典型的应用------双向的数据绑定

defineProperty

VUE实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。

用法:

Object.defineproperty(obj, key, descriptor)

缺陷

  1. 只能劫持对象已有的属性,且仅能劫持getter和setter

  2. 无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应

     let obj = {
         age: 11
     }
     
     let val = 1  // 初始值
     
     Object.defineProperty(obj, 'name', {
         get() {
             console.log('get')
             return val
         },
         set(newVal) {
             console.log('set')
             val = newVal
         }
     })
     console.log(obj.name)   // get 1
     obj.name = 2            // set
     console.log(obj.name);  // get 2
    

Proxy

用法

// target: 用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组、函数,甚至另一个代理)
// handler:一个对象,其属性是当执行一个操作时定义代理的行为的函数
let p = new Proxy(target, handler)

例子

    let obj = {
        name: 'Eason',
        age: 30
    }
    let handler = {
        get (target, key, receiver) {
            console.log('get', key)
            return Reflect.get(target, key, receiver)
        },
        set (target, key, value, receiver) {
            console.log('set', key, value)
            return Reflect.set(target, key, value, receiver)
        }
    }
    let proxy = new Proxy(obj, handler)
    proxy.name = 'Zoe' // set name Zoe
    proxy.age = 18 // set age 18

Reflect是一个内置的对象, 它提供拦截JavaScript操作的方法。这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。Reflect的所有属性和方法都是静态的(如Math对象)。

  • Reflect.get():获取对象身上某个属性的值,类似于 target[name]。
  • Reflect.set():将值分配给属性的函数,返回一个Boolean,如果更新成功,则返回true

proxy返回的是一个新对象, 可以通过操作返回的新的对象打到目的

  1. 劫持一个未来的(新增的)属性
  2. 元编程(对编程语言的再次编程),可劫持
    • 对象(delete getOwnPropertyNames...等)
    • 数组
    • 方法
  3. Proxy是es6提供的新特性,兼容性不好,最主要的是这个属性无法用polyfill来兼容

例子

    function asyncQue(cb) {
        setTimeout(() => {
            cb()
        }, 1000)
    }
    const cb = () => {
        console.log(123)
    }
    // 一秒后同时执行
    asyncQue(cb);
    asyncQue(cb);
    asyncQue(cb);

    // 劫持函数
    // Reflect 反射
    // promise 代表上一个任务
    let asyncQueProxy = new Proxy(asyncQue, {
        // 调用asyncQue函数
        apply(target, ctx, args) {
            // target: asyncQue
            // args: 参数数组; ctx: this
            promise =  Promise.resolve(promise).then(() => {
                return new Promise((resolve, reject) => {
                    // 恢复函数的默认行为
                    // args: cb
                    Reflect.apply(target, ctx, [() => {
                        args[0]()
                        resolve()
                    }])
                })
            })
        }
    })

    asyncQueProxy(cb); // 1秒后执行
    asyncQueProxy(cb); // 2秒后执行
    asyncQueProxy(cb); // 3秒后执行

详情请看数据劫持 OR 数据代理