月底冲业绩,分享一波代理和反射!

2,011 阅读4分钟

反射与代理

认识反射和代理

反射和代理是ECMAScript 6新增的语言特性,代理(Proxy)可以为开发者提供可拦截并且向基本操作嵌入额外的行为能力。 简单的说,在对一个代理对象访问属性、删除属性、添加属性等操作时进行拦截,并且加上自定义操作行为。反射(Reflect)是一个内置对象, 是给底层操作提供默认行为的方法的集合。它与代理的捕获器一一对应,主要功能之一为了方便Proxy捕获器调用。

代理和反射还很新,所以这边"蛮"啰嗦,如果你熟悉代理这一特性可跳过基础,应用可能对您有用。

下面看一段简单的代理:

    const target = {} 
    const handle = {
        get: function(target,key, receiver){ // 这里定义一个捕获器
            console.log(target, key, receiver)
        }
    }
    const proxy = new Proxy(target, handle);
    console.log(target.a)
    console.log(proxy.a)

值得注意的是,proxy对象是没有prototype的。然后可以利用反射来优化一下:

    const target = {
        id: 'foo'
    }
    const handle = {
        get(){
            return Reflect.get(...arguments)
        }
    }
    const proxy = new Proxy(target, handle)
    console.log(proxy.id)

前面也说到了代理的捕获器与反射的方法一一对应的,捕获器全部默认还可以这么写:

    const target = {
        id: 'foo'
    }
    const proxy = new Proxy(target, Reflect)
    console.log(Reflect)
    console.log(proxy.id)

普通代理对象通过new Proxy()去创建一个代理对象,这种创建方法不能取消代理。proxy提供revocable方法,通过该方法创建的代理, 可以主动取消代理对象和目标对象的代理关联。可撤销代理:

    const target = {
        id: 'foo'
    }
    const handle = {
        get(){
            return Reflect.get(...arguments)
        }
    }
    const {proxy, revoke} = Proxy.revocable(target, handle)
    console.log(proxy.id)
    revoke()
    console.log(proxy.id) // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

代理捕获器和反射API

代理一共有13个捕获器(有人叫它陷阱函数),对应也有13个反射方法。如下:

  • 基础捕获器
        const proxy = new Proxy(target, {
            /**
             * @param target 目标对象
             * @param p propertyKey 比如id
             * @param receiver 代理对象
             * @returns {任意返回值} 
             */
            get(target, p, receiver) {
                return Reflect.get(...arguments)
            },
            /**
             * 
             * @param target 目标对象
             * @param p propertyKey
             * @param value 
             * @param receiver 代理对象
             * @returns {boolean}
             */
            set(target, p, value, receiver) {
                return Reflect.set(...arguments)
            },
            deleteProperty(target, p) {
                console.log(...arguments)
                return Reflect.deleteProperty(...arguments)
            }
        });
        proxy.id
        proxy.id = 'yosun'
        delete proxy.id
  • 原型代理
     const target = {id: 'yosun'}
     const proxy = new Proxy(target, {
         /**
          * @param target
          * @returns {object}
          */
         getPrototypeOf(target) {
             console.log(...arguments)
             return Reflect.getPrototypeOf(...arguments)
         },
         /**
          * @param target
          * @param v
          * @returns {boolean}
          */
         setPrototypeOf(target, v) {
             return Reflect.setPrototypeOf(...arguments)
         }
     })
    Object.getPrototypeOf(proxy);
    Object.setPrototypeOf(proxy, {});
  • 对象
    const target = {}
    const proxy = new Proxy(target, {
        isExtensible(target){
            return Reflect.isExtensible(...arguments)
        },
        preventExtensions(target) {
            return Reflect.preventExtensions(...arguments)
        },
        ownKeys(target) {
            console.log(...arguments)
            return Reflect.ownKeys(...arguments)
        },
        /**
          * @param target
          * @param p propertyKey
          * @returns {boolean}
        */
        has(target, p) {
            return Reflect.has(...arguments)
        },
        defineProperty(target, p, attributes) {
            console.log(target, p, attributes)
            return Reflect.defineProperty(...arguments)
        },
        getOwnPropertyDescriptor(target, p) {
            console.log(...arguments)
            // return Reflect.getOwnPropertyDescriptor(...arguments) // 使用默认
            return { configurable: true, enumerable: true, value: 'sun' }
        }
    })
    Object.isExtensible(proxy)
    Object.preventExtensions(proxy)
    'yosun' in target
    Object.keys(proxy)
    Object.defineProperty(proxy, 'id', {
        value: 'yosun'
    })
    console.log(Object.getOwnPropertyDescriptor(proxy, 'id').value)
  • 函数
    const target = function () {}
    const proxy = new Proxy(target, {
        /**
         * @param target
         * @param thisArg
         * @param argArray
         * @returns {*}
         */
        apply(target, thisArg, argArray) {
            console.log(...arguments)
            return Reflect.apply(...arguments)
        },
        /**
         * @param target
         * @param argArray
         * @param newTarget
         * @returns {any}
         */
        construct(target, argArray, newTarget) {
            console.log(...arguments)
            return Reflect.construct(...arguments)
        }
    })
    proxy(111)
    new proxy(111)

应用

一般想禁止访问对象的一些属性,我们可以这么做:

    const hiddenPropertyKey = ['sex', 'age'];
    const person = {
        name: 'yosun',
        age: '26',
        sex: 'man'
    }
    const findHumanInfo = new Proxy(person, {
        get(target, p, receiver) {
            if (!hiddenPropertyKey.includes[p]){
                console.log('您还没有还没有权限访问~')
                return undefined
            }
            return Reflect.get(...arguments)
        },
        has(target, p) {
            if (!hiddenPropertyKey.includes[p]){
                console.log('您还没有还没有权限访问~')
                return false
            }
            return Reflect.has(...arguments)
        }
    })
    findHumanInfo.age // 您还没有还没有权限访问
    findHumanInfo.name // yosun
    'age' in findHumanInfo // 您还没有还没有权限访问~ return false

我们常见的web表单验证,是个比较麻烦的问题,一般代码不容易整合。下面是set捕获器一种常见用法:

    const form = new Map();
    const validator = {
        set(target, property, value, receiver) {
            if(form.has(property)){
                return form.get(property)(value)
            }
            return Reflect.set(...arguments)
        }
    }
    form.set('age', validateAge)
    function validateAge(value){
        if(typeof value !=="number" || Number.isNaN(value)){
            throw new TypeError('年龄必须是数字类型')
        }
        if(value<=0){
            throw new TypeError('年龄必须大于零')
        }
        return true
    }

    const formValidator = {
    }
    const proxy = new Proxy(formValidator, validator)
    proxy.age = '十八'

construct捕获器应用:

    class SelfUser{
        constructor(id) {
            this.is = id
        }
    }
    const User = new Proxy(SelfUser, {
        construct(target, argArray, newTarget) {
            if (!argArray[0]){
                throw new Error("用户id不能为空")
            }else {
                console.log('创建用户成功')
                return Reflect.construct(...arguments)
            }
        }
    })
    new User(1) // 创建用户成功
    new User() // Uncaught Error: 用户id不能为空

TODO 时间原因还有很多未整理-----(自己还没理解的就先不分享)

不过还是要分析一个我觉得比较有趣的代码

    const watchJsRun = (expectObject) => {
        if (typeof expectObject === 'object' || typeof expectObject === 'function'){
            return new Proxy(expectObject,Reflect.ownKeys(Reflect).reduce((handles, key)=> {
                handles[key] = (...args)=> {
                    console.log(key, ...args)
                    return Reflect[key](...args)
                }
                return handles
            }, {}))
        }
        throw new Error('Cannot create proxy with a non-object as target or handler')
    }
    const arr = watchJsRun([])
    // console.log(arr)
    arr.push(1)
    console.log('------------分割线--------------')
    let obj = watchJsRun({})
    obj.id
    console.log('------------分割线--------------')
    let fn = watchJsRun(function Person() {

    })
    new fn()
    fn()
    fn.prototype.doEveryThing = function () {
        console.log('doEveryThing')
    }

以push为例:

get Array(1)0: 1length: 1__proto__: Array(0) push Proxy
get Array(1)0: 1length: 1__proto__: Array(0) length Proxy
set Array(1) 0 1 Proxy
getOwnPropertyDescriptor Array(1) 0
defineProperty Array(1) 0 Object
set Array(1) length 1 Proxy
getOwnPropertyDescriptor Array(1) length
defineProperty Array(1) length Object

一共执行了以上步骤。大家可以自己试试~ 上面的demo很关键,意义深远。既然看到这里了,我相信也会去尝试一下。在写到这里时候,已经是6点07分,过去下班已经7分钟 ;)。我想就算草草结束也要说明一下。这位朋友,你的点赞是我学习的动力! 哈哈哈。。。。。

常见的业务代码几乎看不到代理和反射的存在,proxy对代理的对象进行了衍生,符合低耦合,高内聚的设计理念。当然不要盲目的使用代理, 只有当对象功能变得异常复杂,我们要对访问进行限制时,再考虑使用代理。

还有很多没学会,欢迎指正~