[javascript核心-01]彻底梳理清楚Proxy 代理与Reflect反射

news/2024/10/9 11:40:53/

1. Proxy 代理

本文github地址:JavaScript_Interview_Everything大前端知识体系与面试宝典,从前端到后端,全栈工程师,六边形战士

1.1. 快速了解

Proxy是ES6新增的, 用来创建代理对象,通过代理对象完成对原对象的监听操作,不直接监听原对象,不改变原对象的属性描述符。

Proxy 提供了更多的监听操作,可以通过重写 Proxy 的捕获器来对代理对象进行操作和监听。

1.2. get/set捕获器

//04.js
const obj = {name:'flten',age:20,
}const proxy = new Proxy(obj, {get: function(target, key){console.log(`${key} 属性被获取`)return target[key]},set: function(target, key, newValue){console.log(`${key} 属性被设置`)target[key] = newValue},
})proxy.name='fltenwall'
proxy.age=18console.log(proxy.name)
console.log(proxy.age)
console.log('---------')
console.log(obj.name)
console.log(obj.age)/*
name 属性被设置
age 属性被设置
name 属性被获取
fltenwall
age 属性被获取
18
---------
fltenwall
18
*/

1.3. in 捕获器

对象属性的存在性检查操作监听

//05.js
const obj = {name:'flten',age:20,
}const proxy = new Proxy(obj, {has: function(target, key){console.log(`对象的${key}属性存在性检查操作`)return key in target},
})console.log('name' in proxy)/*
对象的name属性存在性检查操作
true
*/

1.4. deleteProperty 监听属性删除操作

//06.js
const obj = {name:'flten',age:20,
}const proxy = new Proxy(obj, {deleteProperty: function(target,key){console.log(`监听到删除了属性${key}`)delete target[key]}
})delete proxy.age/*
监听到删除了属性age
*/

1.5. apply监听函数对象的apply操作

//07.js
function fn(){}const proxy = new Proxy(fn, {apply: function(target, thisArg, argArray){console.log(`${target.name}对象进行了apply操作`)target.apply(thisArg, argArg)}
})proxy.apply({}, ['age'])/*
对fn对象进行了apply操作
*/

1.6. construct 监听函数对象的 new 调用操作

//08.js
function fn(){}const proxy = new Proxy(fn, {construct: function(target, argArray, newTarget){console.log(`${target.name}对象进行了new操作`)return new target(...argArray)}
})new proxy('age')/*
对fn对象进行了new操作
*/

2. Reflect反射

2.1. 快速了解

Reflect是 ES6 新增的一个对象,它提供了很多操作对象的方法,类似Object 操作对象的方法。

例如:

Reflect.defineProperty()

问:为什么要新增一个 Reflect 对象呢?

1.Object 作为一个构造函数,承担了太多额外的操作,因此需要一个专门的对象承担这些操作

2.类似 indelete操作符非常奇怪,这些操作不应该单独作为操作符

3.因此有需求将对象操作的方法全部放到一个专门的Reflect对象上,将其规范化

4.Reflect上的 13 个方法与 Proxy类上的捕获器是一一对应的

5.在使用 Proxy 进行代理时,可以用Reflect对目标对象进行操作,而不直接对原对象进行操作,让代理操作更加真实,实现真正的代理操作

问:通过Reflect操作对象与直接操作对象有什么的区别?

Reflect在方法上与对象方法有不同,比如设置值时,Reflect.set()会返回一个布尔结果,从而知道设置值操作是否成功

问:Reflect有哪些应用场景呢?

1.与 Proxy 配合进行代理操作,用Reflect操作对象

2.替代 Object 的对象操作

//09.js
const obj = {name:'flten',age:16,
}const proxy = new Proxy(obj, {get: function(target, key, receiver){console.log(`监听到了${target.name}对象${key}的get操作`)return Reflect.get(target, key)},set: function(target, key, newValue, receiver){console.log(`监听到了${target.name}对象${key}的set操作`)Reflect.set(target, key, newValue)},
})proxy.age = 18
console.log(proxy.age)/*
监听到了flten对象age的set操作
监听到了flten对象age的get操作
18
*/

2.2. Proxy的 get 与Reflect.get 的第三个参数(代理对象):receiver

看下面的代码有什么问题?

//10.js
const obj = {_name:'flten',get name(){return this._name},set name(newValue){this._name = newValue}
}const proxy = new Proxy(obj, {get: function(target, key){console.log(`get方法访问监听了${key}属性`, target)return Reflect.get(target, key)},set: function(target, key, newValue){Reflect.set(target, key, newValue)}
})console.log(proxy.name)/*
get方法访问监听了name属性 {_name: 'flten'}
flten
*/

我们来分析一下上面的代码:

1.console.log(proxy.name)触发代理对象proxyget方法,因此输出get方法访问监听了name属性 {_name: 'flten'};

2.但是Reflect.get(target, key)进行操作时,是访问name属性,而name属性是通过 getter 方法get name(){return this._name},进行访问的,而这个 getter 里是去访问 this._name

3.于是存在两个问题:(1)访问了_name属性,为什么没有监听到?打印的结果显示只监听到了name属性;(2)this指向的是哪个对象?实际上我们可以从第一行的打印结果get方法访问监听了name属性 {_name: 'flten'}中看到,其实this指向的是目标对象obj,而不是代理对象 proxy,因此访问_name属性时,其实是直接访问了目标对象obj

4.因此,在这种情况下,我们对_name属性的监听失败,而且直接访问了目标对象obj,绕开了代理

因此我们需要在 proxyget 中直接获取到代理对象,并且改变 this 的指向,解决的方案是在代理对象 proxyget 方法和 Reflectget 方法中增加一个代理对象的参数 receiver,并且能够将 this指向改为代理 对象

// 11.js
const obj = {_name:'flten',get name(){return this._name},set name(newValue){this._name = newValue}
}const proxy = new Proxy(obj, {get: function(target, key, receiver){console.log(`get方法访问监听了${key}属性`, target, receiver)console.log(Object.is(target, receiver))console.log(Object.is(proxy, receiver))// receiver 可以将 this 指向改为代理对象 proxyreturn Reflect.get(target, key, receiver)},set: function(target, key, newValue){Reflect.set(target, key, newValue)}
})console.log(proxy.name)/*get方法访问监听了name属性 {_name: 'flten'} Proxy(Object) {_name: 'flten'}
false
true
get方法访问监听_name属性 {_name: 'flten'} Proxy(Object) {_name: 'flten'}
false
true
flten
*/

从上面的打印结果可以看到,name_name都被监听到了,且receiver就是代理对象

因此receiver的作用就是传入代理对象,并且改变this指向

为了解决同样的问题,在 set 中也需要传入receiver

2.3. Proxy的 set 与Reflect.set 的第三个参数(代理对象):receiver

先来看下面的代码:

//12.js
const obj = {_name:'flten',get name(){return this._name},set name(newValue){this._name = newValue}
}const proxy = new Proxy(obj, {set: function(target, key, newValue){console.log(`${key}属性进行了set操作`)Reflect.set(target, key, newValue)}
})proxy.name = 'fltenwall'/*
对name属性进行了set操作
*/

同样的,对name进行设置值时,会设置_name属性的值,但是只监听到了name属性的set操作,因此也需要传入代理对象参数-receiver

//13.js
const obj = {_name:'flten',get name(){return this._name},set name(newValue){this._name = newValue}
}const proxy = new Proxy(obj, {set: function(target, key, newValue, receiver){console.log(`${key}属性进行了set操作`)Reflect.set(target, key, newValue, receiver)}
})proxy.name = 'fltenwall'/*
对name属性进行了set操作
对_name属性进行了set操作
*/

这样就可以监听到_name属性,且改变了this指向,从而不会绕过代理对象的监听和避免对目标对象的直接操作

2.4. Reflect.construct()

构建函数的实例对象的隐式原型__proto__是指向构造函数的原型对象prototype

//14.js
function Person(name, age){this.name = namethis.age = age
}const p = new Person('flten', 16)
console.log(Object.is(p.__proto__, Person.prototype)) // true

但如果想改变原型指向,我们需要修改__proto__的指向

// 15.js
function Person(name, age){this.name = namethis.age = age
}function Dog(){}const p = new Person('flten', 16)
p.__proto__ = Dog.prototype
console.log(Object.is(p.__proto__, Person.prototype)) // false
console.log(Object.is(p.__proto__, Dog.prototype)) // true

不过 Reflect 为我们提供了更好的方法:Reflect.construct()

//16.js
function Person(name, age){this.name = namethis.age = age
}function Dog(number){this.foot = number
}Dog.prototype.tail = trueconst dog = new Dog()
console.log(dog) // Dog { foot: undefined }// 以Person为构造函数,构造出原型为Dog的函数
const p = Reflect.construct(Person, ['flten', 16], Dog)console.log(p) // Dog { name: 'flten', age: 16 }
// 可以访问到Dog原型上的属性
console.log(p.tail)  // true
console.log(Object.is(p.__proto__, Person.prototype)) // false
console.log(Object.is(p.__proto__, Dog.prototype)) // true

本文github地址:JavaScript_Interview_Everything,大前端知识体系与面试宝典,从前端到后端,全栈工程师,六边形战士


http://www.ppmy.cn/news/237614.html

相关文章

cpu飚高的排查思路

cpu的衡量指标 使用率util:代表的是单位时间内CPU繁忙情况的统计。操作系统对cpu的管理就是利用周期的tick时钟中断,将cpu的使用划分时间片。每个时间片内去执行不同进程/线程里的代码。所以cpu的使用率统计其实也是以tick为单位的:统计周期…

redis整合通过QQ邮箱发送验证码

目录 1.QQ开启服务 2.java中配置 2.1.导入依赖 2.2.yml配置 2.2.1 mail配置 2.2.2 redis配置(与mail同级,在spring下一级) 2.3.conroller层 2.3.1 在controller类上加上 2.3.2 生成验证码方法 2.3.3发送邮件接口 2.3.4 用户登录接口&…

机械键盘linux,罗技发布第一款办公室机械键盘 K840 Mechanical

罗技旗下的机械键盘已经有不少,但到目前为止全部都划分在 G 系列的游戏产品线底下,在外观上虽然近来有收敛一点了,但有时候放在办公室环境还是太显眼了一些。上个月发布的 G413 是最接近「传统」办公室键盘外观的产品,果不期然地罗…

机械键盘浅谈

键盘是跟电脑频繁打交道的人的最重要的设备之一了,程序员自然也不例外,对我们来说一个好的键盘的重要性自然不言而喻,提到好键盘,自然会想到机械键盘,我最近入手了一个机械键盘,简单谈谈使用的感受吧。 我…

小米机械键盘测试软件,网友吐槽小米悦米机械键盘“有文化”:“独创”ATL按键...

IT之家讯 10月18日消息,今天小米众筹上线了一款悦米机械键盘,该机械键盘采用87键位设计,机身为铝合金材质,采用TTC红轴,官方称可经受5000万次可靠性测试。主控为意法半导体主控芯片,32位ARM架构、1000Hz刷新…

适配linux的机械键盘,完美适配macOS的机械键盘又多一款 买买买

原标题:完美适配macOS的机械键盘又多一款 买买买 ikbc 是由发烧友创立的国产品牌,是“THE KEYBOARD TO CHEER YOU UP”的缩写。ikbc 推出的机械键盘外观比较大方,设计不玩花样,万年公模、皮实耐操,算得上是日常所用优秀…

森松尼N-J60双模机械键盘按键操作说明

疫情头上买的尼森松N60双模机械键盘,价格美丽,不过熬了整整快1个月,京东总算是给我送到了。外包装有点花里胡哨,看着有点少女心,包装拆完直接扔了,哇咔咔。 话不多说直接上参数吧,咱也不是做评…

适合学计算机用的机械键盘,一款好用的机械键盘应该怎么选?看完这篇就明白了...

一款好用的机械键盘应该怎么选?看完这篇就明白了 2019-07-30 15:53:13 4点赞 14收藏 3评论 今天给大家带来好物推荐第1期——机械键盘。作为在办公室办公的从业者们,平时工作中与电脑的接触时间最多。而人与电脑的交互主要靠键盘和鼠标,其中使用最多的一般是键盘。要想真正大…