最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 关于proxy一篇就够了

    正文概述 掘金(武康)   2021-01-12   586

    前言

    关注 vue2.0 的同学都知道, vue2.0 实现响应式的方式的是通过 Object.defineProperty,而 vue3.0的响应式实现是用的 ES6 的新语法 proxy 代理实现的。

    在 vue3.0 生态圈不断完善的今天,我们不可忽视的需要了解 vue3.0 的语法和实现方式,3.0 中最核心的 API 不过就是 Proxy了,你真的了解了 Proxy 了吗?

    什么是Proxy

    ts 中对 Proxy 接口的定义

    interface ProxyHandler<T extends object> {
        getPrototypeOf? (target: T): object | null;
        setPrototypeOf? (target: T, v: any): boolean;
        isExtensible? (target: T): boolean;
        preventExtensions? (target: T): boolean;
        getOwnPropertyDescriptor? (target: T, p: PropertyKey): PropertyDescriptor | undefined;
        has? (target: T, p: PropertyKey): boolean;
        get? (target: T, p: PropertyKey, receiver: any): any;
        set? (target: T, p: PropertyKey, value: any, receiver: any): boolean;
        deleteProperty? (target: T, p: PropertyKey): boolean;
        defineProperty? (target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean;
        ownKeys? (target: T): PropertyKey[];
        apply? (target: T, thisArg: any, argArray?: any): any;
        construct? (target: T, argArray: any, newTarget?: any): object;
    }
    
    interface ProxyConstructor {
        revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
        new <T extends object>(target: T, handler: ProxyHandler<T>): T;
    }
    declare var Proxy: ProxyConstructor;
    

    Proxy 的语法

    通过ts 的接口的定义可以发现 Proxy 有两种使用方式,可以通过 new 关键词调用,或者使用 Proxy.revocable 的静态方法。

    通过 new 关键字调用

    可以接收两个参数

    参数名称参数描述
    target需要 Proxy 包装的对象(可以是任意类型的对象,包括数组、函数,甚至是另一个 Proxy 的实例)handle一个以函数作为属性的对象,属性定义了在执行代理操作时的行为
    const target = {};
    
    const proxy = new Proxy(target, {
        get (target, key, receiver) {
            return 123;
        }
    })
    
    console.log(proxy.getData) // 123 通过 proxy 获取任何的属性的值,这里都会输出123
    

    静态方法 Proxy.revocable

    接收和通过 new 调用一样的参数,返回值时一个对象,包含 proxyrevoke 两个属性

    属性说明
    proxyProxy 对象的实例,和通过 new 关键字调用返回一样的代理对象revoke函数,执行会销毁 proxy 属性的代理行为,在执行了 revoke 方法后,在使用 proxy 对象,会报错
    const target = {};
    
    const {proxy, revoke} = Proxy.revocable(target, {
        get (target, key, receiver) {
            return 123;
        }
    })
    
    console.log(proxy.getData); // 123
    revoke(); // 销毁 proxy 代理
    console.log(proxy.getData); // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
    // 被销毁的代理对象不能在执行代理方法
    

    Proxy 可以代理哪些行为

    Proxy 可以代理所有 ProxyHandler 中定义的行为,下面会详细的解释一下。

    Proxy 可以代理对象的13种行为:

    get 代理对象属性的读取

    接收三个参数

    • target:源对象
    • key:获取的对象的属性名称
    • receiver:proxy 对象实例本身

    三种可以触发 get 的方法

    访问属性: proxy[foo]和 proxy.bar
    访问原型链上的属性: Object.create(proxy)[foo]
    Reflect.get()
    

    示例代码:

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

    set 代理对象属性的设置

    接收四个参数

    • target:源对象
    • key:要设置的对象的属性
    • value:要设置的对象属性的值
    • receiver:proxy 实例本身

    三种可以触发 set 的方法

    指定属性值:proxy[foo] = bar 和 proxy.foo = bar
    指定继承者的属性值:Object.create(proxy)[foo] = bar
    Reflect.set()
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        set (target, key, value, receiver) {
            target[key] = value;
            return true;
        }
    })
    
    proxy.getData = 123
    console.log(target.getData) // 123
    console.log(proxy.getData) // 123
    

    has 代理对象 in 操作符

    接收两个参数

    • target:源对象
    • key: 要判断的key

    返回 Boolean 类型的值

    四种可以触发 has的方法

    属性查询: foo in proxy
    继承属性查询: foo in Object.create(proxy)
    with 检查: with(proxy) { (foo); }
    Reflect.has()
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        has (target, key) {
            return key === 'getData'
        }
    })
    
    console.log("getData" in proxy)
    console.log("a" in proxy)
    
    • getPrototypeOf 代理 Object.getPrototypeOf 获取原型对象的行为

      接收一个参数

      • target:源对象

        返回一个对象或者 null

    五种可以触发 getPrototypeOf 的方式

    Object.getPrototypeOf(p) // Object.getPrototypeOf
    Reflect.getPrototypeOf(p) // 反射方法 Reflect.getPrototypeOf
    p.__proto__ // __proto__ 属性
    Array.prototype.isPrototypeOf(p) // isPrototypeOf 方法
    p instanceof Array    // instanceof 操作符
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        getPrototypeOf (target) {
            return {}
        }
    })
    console.log(Object.getPrototypeOf(proxy))
    console.log(Object.getPrototypeOf(target))
    

    输出结果:

    关于proxy一篇就够了

    setPrototypeOf 代理 Object.setPrototypeOf 设置原型对象的行为

    接收两个参数

    • target:源对象
    • v:要设置成原型的对象可以是 对象或者 null

    返回一个 Boolean 类型

    两种可以触发 setPrototypeOf 的方法

    Object.setPrototypeOf()
    Reflect.setPrototypeOf() // 反射方法
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        setPrototypeOf (target, v) {
            return Reflect.setPrototypeOf(target, v) // 反射也是ES6中提供的新的操作对象的方法
        }
    })
    Object.setPrototypeOf(proxy, null)
    console.log(Object.getPrototypeOf(proxy)) // null
    console.log(Object.getPrototypeOf(target)) // null
    Object.setPrototypeOf(proxy, {a: 1})
    console.log(Object.getPrototypeOf(proxy)) // {a: 1}
    console.log(Object.getPrototypeOf(target)) // {a: 1}
    

    isExtensible 代理 Object.isExtensible() 判断对象是否可扩展的行为

    接收一个参数:

    • target:源对象

    返回一个 Boolean

    两种可以触发 isExtensible 方法的操作:

    Object.isExtensible()
    Reflect.isExtensible()
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        isExtensible (target) {
            return true
        }
    })
    
    console.log(Object.isExtensible(proxy)) // true
    console.log(Object.isExtensible(target)) // true
    

    错误示例

    const target = {};
    
    const proxy = new Proxy(target, {
        isExtensible (target) {
            return false
        }
    })
    
    console.log(Object.isExtensible(proxy))
    

    关于proxy一篇就够了

    preventExtensions 代理 Object.preventExtensions 让对象变成不可扩展的行为

    接收一个参数:

    • target:源对象

    返回一个 Boolean

    两种可以触发 preventExtensions 的方式:

    Object.preventExtensions()
    Reflect.preventExtensions()
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        preventExtensions (target) {
            return Reflect.preventExtensions(target)
        }
    })
    
    Object.preventExtensions(proxy)
    console.log(Object.isExtensible(proxy)) // false
    console.log(Object.isExtensible(target)) // false
    

    getOwnPropertyDescriptor 代理 Object.getOwnPropertyDescriptor 获取对象上的一个自有属性的描述符的行为

    接收两个参数:

    • target:源对象
    • key:要获取属性描述符的对象的key

    返回 PropertyDescriptorundefined, 获取的 key 不存在的时候返回 undefined

    两种可以触发 getOwnPropertyDescriptor 的方式

    Object.getOwnPropertyDescriptor()
    Reflect.getOwnPropertyDescriptor()
    

    示例代码:

    const target = {
        a: 123
    };
    
    const proxy = new Proxy(target, {
        getOwnPropertyDescriptor (target, key) {
            return Reflect.getOwnPropertyDescriptor(target, key)
        }
    })
    console.log(Object.getOwnPropertyDescriptor(proxy, 'a')) // {value: 123, writable: true, enumerable: true, configurable: true}
    

    defineProperty 代理 Object.defineProperty 在对象上添加一个新的属性或者修改对象上现有属性的行为

    接口三个参数

    • target: 源对象
    • key:要添加或者修改的属性
    • attributes: 要添加或修改的属性的描述符

    返回 Boolean

    三种可以触发 defineProperty 的方法

    Object.defineProperty()
    Reflect.defineProperty()
    proxy.property='value'
    

    示例代码:

    const target = {};
    
    const proxy = new Proxy(target, {
        defineProperty(target, key, attr) {
            return Reflect.defineProperty(target, key, attr)
        }
    })
    
    Object.defineProperty(proxy, 'a', {
        configurable: false,
        writable: false,
        value: 123
    })
    

    deleteProperty 代理 delete 操作符删除对象的属性的操作

    接收两个参数

    • target:源对象
    • key:要删除的属性名

    返回 boolean 类型的值

    两种可以触发 deleteProperty 的方法

    删除属性: delete proxy[foo] 和 delete proxy.foo
    Reflect.deleteProperty()
    

    示例代码:

    const target = {};
    
    Object.defineProperty(target, 'a', {
        configurable: false,
        writable: false,
        value: 123
    })
    
    const proxy = new Proxy(target, {
        deleteProperty(target, key) {
            return Reflect.deleteProperty(target, key)
        }
    })
    
    delete proxy.a
    

    ownKeys 方法拦截 使用Object.getOwnPropertyNames()方法返回一个由指定对象所有自身属性的属性名(包括不可枚举,但是不包括 Symbol值作为名称的属性)组成的数组

    接收一个参数

    • target:源对象

    返回一个数组

    四种触发 ownKeys 的方法

    Object.getOwnPropertyNames()
    Object.getOwnPropertySymbols()
    Object.keys()
    Reflect.ownKeys()
    

    示例代码:

    const target = {
        a: '123',
        [Symbol()]: 123
    };
    
    const proxy = new Proxy(target, {
        ownKeys(target) {
            return Reflect.ownKeys(target)
        }
    })
    console.log(Object.keys(proxy)) // ['a']
    

    apply 方法拦截函数的调用

    接收三个参数:

    • target:源方法
    • thisArg: 被调用时的上下文
    • argumentsList:被调用时的上下文

    返回任意值

    三种触发 apply 的方法

    proxy(...args)
    Function.prototype.apply() 和 Function.prototype.call()
    Reflect.apply()
    

    示例代码:

    const target = {
        a(b) {
            console.log(b);
        }
    };
    
    const proxy = new Proxy(target, {
        apply(target, thisArg, argumentsList) {
            return Reflect.apply(target, thisArg, argumentsList)
        }
    })
    proxy.a.call(proxy, 123) // 123
    

    construct 方法拦截用于 new 操作

    接收三个参数:

    • target:源对象
    • argumentsList: constructor 的参数列表
    • newTarget:最初被调用的构造函数

    两种触发 construct 的方法

    new proxy(...args)
    Reflect.construct()
    

    示例代码:

    class A {
        constructor(a, b) {
            console.log(a, b) // 1, 2
            this.a = a;
            this.b = b;
        }
    }
    
    const proxy = new Proxy(A, {
        construct(target, argArray, newTarget) {
            console.log(target) // A
            console.log(argArray) // [1, 2]
            console.log(newTarget) // proxy
            console.log(newTarget === proxy) // true
            return Reflect.construct(target, argArray, newTarget)
        }
    })
    
    console.log(new proxy(1, 2)); // A {a: 1, b: 2}
    

    到这里 Proxy 能做的代理就结束了。

    proxy 和 Object.defineProperty 都可以对 源对象的getset 方法做拦截,为什么 vue3 弃用 Object.defineProperty 选用 proxy 了呢?

    Proxy 对比 Object.defineProperty

    • Object.defineProperty 不能一次监听所有的属性,只能递归遍历,才能代理到所有的属性,对于新添加的属性做不到监听
    • Object.defineProperty 不能监听 Array 类型的数据的操作
    • Proxy 的拦截方式更多,但是 Proxy 的兼容性没有 Object.defineProperty 好。

    参考文章

    【你不知道的 Proxy】:用 ES6 Proxy 能做哪些有意思的事情?

    MND Proxy 文档


    起源地下载网 » 关于proxy一篇就够了

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元