美文网首页
proxy&reflect

proxy&reflect

作者: Super曲江龙Kimi | 来源:发表于2020-02-04 10:49 被阅读0次

    Proxy

    proxy能够代理整个对象, 在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

    和defineProperties的对比

    1 :defineProperties是针对属性的劫持。而proxy是针对于对象的,不需要递归进行劫持。性能更好。
    2: proxy可以代理数组,而不需要重写数组的方法(hack)。length变化也可以监控到。而defineProperties不行
    3: proxy代理支持13种代理拦截方法,而defineProperties没有那么多。
    4: proxy返回的是一个新对象,而defineProperties只能遍历操作源对象
    5: proxy兼容性不好,需要polyfill。

    基本操作

    get

    const arr = [111,222,44,33];
    const proxyArr = new Proxy(arr, {
        get(target, key, _proxy) {
            console.log(target === arr);   // true
            console.log(target === proxyArr); // false  返回新对象
            console.log(_proxy === proxyArr); // true
            console.log(_proxy === arr); // false 
            let index = key;
            if (key < 0) {
                index = target.length + Number(key);
            }
            return target[index];
        }
    })
    console.log(proxyArr[-4])  //  111
    

    如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错

    set

    const good = {
        name : 'car',
        price: 100
    }
    const proxyGood = new Proxy(good, {
        set(target, key, value, _proxy) {
            if (Number(value) > target.price) {
                Reflect.set(target, key, value, _proxy);
            } else {
                throw new RangeError('price is invalid');
            }
        }
    })
    proxyGood.price = 500; // { name:'car', price:500 }
    proxyGood.price = 300; // RangeError('price is invalid')
    

    注意,如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。

    apply

    var twice = {
        apply (target, ctx, args) {
            console.log(arguments) // [target, ctx, args]
            console.log(args) // target函数执行时参数列表
            console.log(ctx) // 上下文this
            console.log(target === sum) // true
            return Reflect.apply(...arguments) * 2;
        }
    };
    function sum (left, right) {
        return left + right;
    };
    var proxy = new Proxy(sum, twice);
    console.log(proxy(1, 2)); // 6
    console.log(proxy.call(null, 5, 6)) // 22
    console.log(proxy.apply(null, [7, 8])); // 30
    console.log(proxy.bind({}, 5, 5)()); // 20
    console.log(Reflect.apply(proxy, null, [6, 6])); // 24
    

    has

    has 只对 in操作符生效, 但不隐藏属性,所以for in 、keys等操作仍然可以遍历到属性

    const stu1 = {name: 'kimi', score: 80};
    const stu2 = {name: 'boom', score: 59};
    
    const handle = {
        has(target, key) {
            if (key === 'score' && target[key] < 60) return false;
            return true;
        }
    }
    
    const proxy1 = new Proxy(stu1, handle);
    const proxy2 = new Proxy(stu2, handle);
    
    console.log('score' in proxy1) // true
    console.log('score' in proxy2) // false
    for (let k in proxy2) {
        console.log(k) // name score 
        console.log(proxy2[k]) // boom 59
    }
    console.log(Object.keys(proxy2)); // [ 'name', 'score' ]
    

    如果原对象不可配置或者禁止扩展,这时has拦截会报错。如果某个属性不可配置(或者目标对象不可扩展),则has方法就不得“隐藏”目标对象的该属性。

    construct

    const p = new Proxy(Array, {
        construct(target, args, _proxy) {
            console.log(_proxy === p); // true
            return { val : new target(...args) } // 必须返回一个对象,如果不是会报错
        }
    })
    
    const a = new p(222,33);
    console.log(Object.getPrototypeOf(a));
    

    deleteProperty

    const a = {age: 27}
    const b = {age: 35}
    
    function checkAge(age) {
        if (Number(age) > 30) throw new RangeError('age is too old!')
    }
    
    const handle = {
        deleteProperty(target, key) {
            checkAge(target[key]);
            Reflect.deleteProperty(target, key);
            return true
        }
    }
    
    const p1 = new Proxy(a, handle)
    const p2 = new Proxy(b, handle)
    
    console.log(delete p1.age); // true
    console.log(delete p2.age); // new RangeError('age is too old!')
    

    注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

    ownKeys

    const p = new Proxy({'name':'kimi'}, {
        ownKeys(target) {
            // ownKeys方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。
            return ['a', 'name']
        }
    })
    
    for(let k in p) {
        // 拦截后只返回['a', 'name'],但是原来的对象中只有name属性所以只返回name没有a
        console.log(k);  // name  
    }
    console.log(Object.keys(p)); // ['name']  与for in 同理
    console.log(Reflect.ownKeys(p)); // ['a', 'name']
    console.log(Object.getOwnPropertyNames(p)); // ['a', 'name']
    

    注意,使用Object.keys方法和for in时,有三类属性会被ownKeys方法自动过滤,不会返回。

    1. 目标对象上不存在的属性
    2. 属性名为 Symbol 值
    3. 不可遍历(enumerable)的属性
    let target = {
      a: 1,
      b: 2,
      c: 3,
      [Symbol.for('secret')]: '4',
    };
    
    Object.defineProperty(target, 'key', {
      enumerable: false,
      configurable: true,
      writable: true,
      value: 'static'
    });
    
    let handler = {
      ownKeys(target) {
        return ['a', 'd', Symbol.for('secret'), 'key'];
      }
    };
    
    let proxy = new Proxy(target, handler);
    
    Object.keys(proxy) // ['a']
    for(let k in proxy) {
          console.log(k); // a
     }
    

    proxy中还可以拦截 defineProperty、getOwnPropertyDescriptor、getPrototypeOf
    、isExtensible、preventExtensions、setPrototypeOf等方法。

    Reflect

    Reflect的作用是将Object对象的一些明显属于语言内部的方法,放到Reflect对象上。比如Object.defineProperty。这种属于整个语言的方法,并且修改某些Object方法的返回结果,让其变得更合理。方法和proxy一一对应。

    Reflect.ownKeys方法用于返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和

    相关文章

      网友评论

          本文标题:proxy&reflect

          本文链接:https://www.haomeiwen.com/subject/ucskxhtx.html