上一篇和大家聊了聊元编程的代码生成,这一次和各位聊聊反射吧。
看过我上一篇博文的朋友已经对元编程有个大概的了解了,“用代码编写代码”,并实践了怎么动态生成代码,那么反射又是怎么回事呢?
反射就是,可以在运行时检查或修改代码结构,从而改变代码的表现形式。
——前端装逼架构师 Yubble
注意了同学,之前是生成,现在是’查‘、’改‘。
大致了解了反射的概念,我们再来详细了解下反射的三个分支:
1、自省(Introspection):代码能够自我检查、访问内部属性,我们可以据此获得代码的底层信息。
2、自我修改(Self-Modification):顾名思义,代码可以修改自身。
3、调解(Intercession):在元编程中,调解的概念类似于包装(wrapping)、捕获(trapping)、拦截(intercepting)。
这几个专业术语看起来很高大上哈,但是我们在以前的开发过程中经常会用到,只是我们当时不知道罢了,下面上代码。
例子1:自省——自我检查,访问内部属性
在我们日常工作时编码访问一个对象内部信息,以及检查内容时,这种方法就称为自省,是不是感觉起了个比较中二的名字。
例子2:自我修改——代码可修改自身
这个也就没什么可说的了,简单的赋值实现
例子3:调解——包装、捕获、拦截
js中对调解最有力的表现方式就是defineProperty了,它重新定义了对一个对象属性的使用,搭配着get、set方法又充分的诠释了捕获与拦截的特性。
相信写到这里,各位客官都有些疑惑了吧,“既然之前的语法都支持反射,为什么还会出现一个Reflect API?”,原因我归纳了以下几点:
1、将所有的反射操作都集中在一个命名空间中。我们如果百度的话,就会发现Reflect支持13种反射操作,其中大多都已经在Object、Function对象中实现过,但是Reflect对它们做了个整合,且未来会只支持Reflect API这种方式。
2、让返回结果更加规范合理。比方说之前在Object.defineProperty()中给对象做调解时,如果被控制的object是已经被Object.freeze()冻结过的,在Object.defineProperty()里操作set方法则会报错,此时如果不想影响到整条进程则需要使用try {} catch() {}来包裹。而Reflect.defineProperty()则是可以用if() else {}来做报错判断,巴适滴很。
3、让函数代替操作符。函数依旧是我们在js世界中的一等公民,我们之前用于操作对象内部的操作符可以用它完全替换掉。例如用 Reflect.has() 代替 in,用 Reflect.deleteProperty() 代替 delete,用 Reflect.constructor() 代替 new。这些命令符虽说使用起来确实效果不差,但是如果想要复用这些能力的话,还是需要将其封装为一个函数,所以Reflect API为我们提供了这个函数。
4、帮助代理实现反射(Proxy)。这一块是目前Reflect使用最频繁的场景,提到Proxy我们也要捎带啰嗦两句,介于Vue2.0与3.0的实现原理,所以我们经常会把Object.defineProperty与Proxy相提并论,但是在我看来他们的定义差别还是很大的,从字面翻译上,一个是给对象中某一个属性做属性定义,一个是给某个对象做代理。虽然他们都存在set、get这些拦截方法,且符合反射中调解这个定义,但是Object.defineProperty更注重对象中某一个属性的可操作性(可读、可写、可便利),Proxy则是对整个对象的拦截并完成反射。
为什么说Reflect可以协助Proxy完成反射呢?因为在Proxy第二个参数handle中包含的拦截方法,Reflect API都有对应的实现。请看代码实现:
其实这第三个参数reciver才是使用Reflect.*方法的最关键因素,它的意义是告诉Reflect.get此时调用这个属性的对象或者上下文。我们在用proxy实例时,不免会将这个实例作为其他对象的继承对象,此时的上下文环境应该是调用这个属性的对象,而不是直接写死为trapTarget,用下面这个简单的小例子来说明:
理论上访问temp.foo时,this应该去temp上去查找,但是由于我们在代理中写死了target[property],那么它只会返回obj对象上的属性,所以这个target也就固定指向了obj对象,这样也就造成了不合理的this指向。具体知识大家可以参考这一篇博文《推荐使用Reflect.get而不是target[key]的原因》
以上Reflect这个API在元编程——反射中的实现与特点就都交代完了,总结起来的话,就是围绕着对象的访问,检查,拦截,修改表现形式的一种做法。
提到Proxy就不得不想到Vue3.0的双向绑定,其实它也是遵循着元编程的思路去实现,而它的元数据则是被代理的virtual Dom,通过对virtual Dom的代理进行反射操作,之后如果有时间,我会整理下Vue3.0中具体使用到元编程——反射的案例。这里给大家推荐一本书《Vue.js设计与实现》,在下有幸与它的作者霍春阳做过同事,这本书内容丰富,对我们技术视野的提升也是很有帮助的。
终于把元编程全部梳理完了,也算是对自己有个交代。前段时间整个人有点颓,也找了不少小伙伴喝酒聊天,其中一个朋友说的一句话给我触动很深:“我们本来就处于这种信息高速时代,每天都把自己折磨的很累,难得能有几天能自由支配的时间,如果把这么宝贵的时间都留给焦虑了,那就太遗憾了。”
听完她这句话,我重新定义了一下这段空窗期的安排,上午看自己以前想看的书或电影,下午整理以前没时间整理的知识点,写博文,晚上去拳馆打拳。把节奏慢下来仔细看看生活,发现很多美妙的东西都被自己忽略了。