前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES6之Reflect和Proxy

ES6之Reflect和Proxy

作者头像
19组清风
发布2021-11-15 15:31:12
3520
发布2021-11-15 15:31:12
举报
文章被收录于专栏:Web Front EndWeb Front End

Proxy与Reflect

调用new Proxy()可以创建代替其他目标(target)对象的代理,它虚拟化了目标,所以两者看起来功能一致。

代理可以拦截JavaScript引擎内部目标的底层对象操作,这些底层操作被拦截后会触发相应特定操作的陷阱函数。(钩子函数Hook)

反射API以Reflect对象的形式出现,对象中方法的默认特性与相同的底层操作一致,而代理可以覆写这些操作,每个代理陷阱对应一个命名和参数都相同的Reflect方法。

代理和反射.jpeg
代理和反射.jpeg

关于Proxy和Object.defineProperty()对于数组的劫持

以及对于Vue为什么不可以实现数组的数据响应式劫持解释

无论是Object.defineProperty()还是new Proxy()对于数组的代理,数字的原本方法比如push(), pop(), shift()等这些方法是可以不被拦截的,所以这也就Vue底层对于数组的监听在原型上重写了这些方法(变异方法,做到数据响应式)。(虽然Vue不支持对于数组下标形式的修改,但是这两种方式显然是支持数组下标的拦截的。)

为什么不用数组的拦截并不是不支持而是->参见github上尤大的回答。

数组劫持.png
数组劫持.png
代码语言:javascript
复制
- 常见拦截器:(详细见Demo)



- get(target, key, receiver) 拦截 -> 默认Reflect.get(target, key, receiver)

- set(target, key, value, receiver)拦截 -> 默认 Reflect.set(target, key, value, receiver)



- has(target,key) 拦截 -> 默认 Reflect.has(target,key) (对于in拦截)

- ownKeys(target) 拦截 -> Refelect.ownKeys(target)

> ownKeys陷阱唯一接收的参数事操作的目标,返回值必须是一个数组或者类数组对象,否则就会抛出错误。当调用for of,Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertySymbols()或者Object.assign()以及for in方法的时候,可以用ownKeys陷阱过滤不想使用的属性键。

- deleteProperty(target, key)陷阱拦截删除 -> Reflect.deleteProperty(target, key)

> ES5中删除属性delete属性操作符,成功返回true,不成功返回false,严格模式下会报错。配置Object.defineProperty(obj,'xx',{ configurable:false })设置属性不可以配置也就不可以删除了。

> ES6中可以使用代理Proxy代理deleteProperty()来改变这个行为。(不可删除,被拦截的需要返回false)
复制代码

深入浅出ES6对于函数的解释

函数有两个内部方法[call]和[construct], apply陷阱和constructor陷阱可以复写这些内部方法,若使用new操作符调用函数,则执行construct方法。则会执行construct陷阱。 若不用则调用call方法。执行会执行apply陷阱(以及apply,call调用函数都会进入apply陷阱)。

小Tips:注意apply和construct陷阱的写法是x:()=> {} 函数写法,其他属性陷阱是a() {} 写法。

代码语言:javascript
复制
- 函数的拦截

- apply(target,thisArg,argumentsList)陷阱对于函数的拦截。 -> Reflect.apply(target,this.Arg,argumentsList)

> 非new调用的函数陷阱拦截。thisAra:函数被调用时this的指向,argumentsList:传递给函数的参数数组。

- constructor(target,argumentList,newTarget)陷阱 -> Reflect.constructor(target,argumentList,newTarget)

> new 调用constructor时触发constructor陷阱。target被代理的class类,argumentList:参数列表,array。newTarget:new 调用时命令指向的构造函数(new实例时候的构造函数)。
复制代码
代码语言:javascript
复制
// ES5中拦截对象的属性
let obj = {}
let newVal = ""
Object.defineProperty(obj, 'name', {
    get() {
        return newVal
    },
    set(val) {
        newVal = val
    }
})

// ES6 Proxy

// get 拦截
let obj = {}
const p = new Proxy(obj, {

})
p.name = 'wanghaoyu'
console.log(obj.name) // wanghaoyu
// 上面代码没有进行任何配置代理拦截 所以对于代理p的才会原封不动的到obj上


// 增加一些拦截的钩子
// 注意这个例子是用in检查receiver(代理)而非target,
// 是因为防止receiver是否含有has陷阱,
// 如果有has陷阱就可以对in进行一层拦截。
// 而源对象target可能会忽略in操作符的has陷阱从而引发错误。
const array = [7, 8, 9]
const p = new Proxy(arr, {
    get(target, props, receiver) {
        // target 表示源对象(被代理的对象)
        // props 表示读取的属性key值
        // receiver表示操作发生的对象(通常是代理)
        if (!(props in receiver)) {
            throw new TypeError("key值不存在")
        }
        // Reflect反射API以对象的形式出现,对象中方法的默认特性与相同的底层操作一致。
        // 也就是return Refelct API就是默认的get值。
        return Reflect.get(target, props, receiver)
    }
})


// set 拦截
// set(target,key,value,receiver)拦截 -> 默认 Reflect.set(target,key,value,receiver)
// target源对象(用于接收属性的对象),key要写入的属性键,value被写入属性的值,receiver操作发生的对象(代理对象)。
let arr = new Proxy(arr, {
    set(target, key, value, receiver) {
        if (typeof key !== 'number') {
            throw new TypeError("属性必须是数字")
        }
        return Reflect.set(target, key, value, receiver)
    }
})

// has陷阱
// has(target,key) 拦截 -> 默认 Refelect.has(target,key) (对于in拦截)
// target(源对象),key(in操作符前的属性key值)
let range = {
    name: "target",
    value: 42
}
let proxy = new Proxy(range, {
    has(target, key) {
        if (key === 'value') {
            return false // 表示碰到key为value的返回false 不可被for in
        } else {
            return Reflect.has(target, key)
        }
    }
})


// ownKeys拦截
// ownKeys(target) 拦截 -> Refelect.ownKeys(target)
// target源对象。(ownKeys对于Object.keys(),Object.getOwnPropertyNames(),Object,getOwnPropertySymbols(),for in进行拦截)

// ownKeys陷阱唯一接收的参数事操作的目标,返回值必须是一个数组或者类数组对象,否则就会抛出错误。当调用Object.keys(),Object.getOwnPropertyNames(),Object.getOwnPropertySymbols()或者Object.assign()以及for in方法的时候,可以用ownKeys陷阱过滤不想使用的属性键。

let userInfo = {
    username: "wanghaoyu",
    age: 23,
    _password: "***" // 下划线开头的***我并不想被Object.keys(),forin等取到
}

// 这个例子使用了一个ownKeys陷阱,它首先调用Reflect.ownKeys()获取目标的默认key列表
// 接下来用filter()方法过滤掉以下划线自负开始的字符串。然后将过滤后的数组元素添加到proxy的方法返回上
user = new Proxy(userInfo, {
    ownKeys(target) {
        // 返回不以_开头的属性数组
        return Reflect.ownKeys(target).filter(i => !i.startsWith("_"))
    }
})
Object.keys(userInof) // [username,age]

// deleteProperty拦截
// deleteProperty(target,key)陷阱拦截删除 -> Reflect.deleteProperty(target,key)
// target源对象,key需要删除的key值。

// ES5中删除属性delete属性操作符,成功返回true,不成功返回false,严格模式下会报错。配置Object.defineProperty(obj,'xx',{ configurable:false })设置属性不可以配置也就不可以删除了。

// ES6中可以使用代理Proxy代理deleteProperty()来改变这个行为。

let user = {
    name: "wanghaoyu",
    age: 18,
    _password: "***"
}
// 我希望下划线开头的_password属性不可以删除
user = new Proxy(user, {
    deleteProperty(target, key) {
        if (key.startsWith("_")) {
            return false
        } else {
            return Reflect.deleteProperty(target, key)
        }
    }
})


// apply拦截
// apply(target,thisArg,argumentsList)陷阱对于函数的拦截。 -> Reflect.apply(target,this.Arg,argumentsList)
// target被执行的元素(被代理的函数),thisArg函数被调用时内部this的值,argumentsList传递给函数的参数数组。

// 注意形参上的...args并不是扩展,是rest参数
// 将传入的参数合成成为一个数组Array
let sum = (...args) => {
    let num = 0
    args.forEach(i => {
        num += i
    })
    return num
}

// proxy apply
let proxy = new Proxy(sum, {
    // 注意和属性拦截写法的区别哦
    apply: (target, this, args) => {
        return Reflect.apply(target, this, args) * 2
    }
})

console.log(sum(10, 16)) // 26
console.log(proxy(10, 12)) // 52


// constructor陷阱
// constructor(target,argumentList,newTarget)陷阱 -> Reflect.constructor(target,argumentList,newTarget)
// target被代理的class类,argumentList参数列表,newTarget指的是创造实例的时候new命令作用的那个构造函数。

let user = class User {

}

let proxy = new Proxy(user, {
    construct: (target, arguments, newTarget) => {
        return Reflect.constructd(target, arguments, newTarget)
    }
})

class Test {
    constructor() {}
}

let proxy = new Proxy(Test, {
    construct: (target, argList, newTarget) => {
        console.log(target === Test, 'target === Test') // true
        console.log(newTarget === proxy, 'newTarget') // true
    }
})

const test = new proxy()
复制代码
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年09月09日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Proxy与Reflect
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档