《一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础.docx》由会员分享,可在线阅读,更多相关《一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础.docx(13页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、一文读懂Decorator装饰器理解VSCode源码的基础以上就是装饰器的详细实现方法其核心思路是Step1备份原来类构造器(Class.prototype)的属性描绘符(Descriptor)利用Object.getOwnPropertyDescriptor获取Step2编写装饰器函数业务逻辑代码利用执行原函数前后钩子添加耗时统计逻辑Step3用装饰器函数覆盖原来属性描绘符的value利用Object.defineProperty代理Step4手动执行装饰器函数装饰Class(类)指定属性进而实如今不修改原代码的前提下执行额外逻辑代码5.Decorator装饰器语法糖但上一步4.2.2手写的
2、装饰器函数存在两个可优化的点是否可以让装饰器函数更关注业务逻辑Step1,Step2是通用逻辑的每个装饰器都需要实现简单来讲就是可复用的。是否可以让装饰器写法更简单纯函数实现的装饰器每装饰一个属性都要手动执行装饰器函数详见Step4步骤。针对上述优化点装饰器草案中有一颗十分甜的语法糖也就是Decorator它可以帮你省去很多繁琐的步骤来用上装饰器。只需要在想使用的装饰器前加上符号装饰器就会被应用到目的上。5.1Decorator语法糖的便捷性下面我们用Decorator的写法来实现同样的功能看看代码量可以精简多少/Step2编写装饰器函数业务逻辑代码functionlogTime(target
3、,key,descriptor)constoldMetheddescriptor.valueconstlogTimefunction(.arg)letstartnewDate()tryreturnoldMethed.apply(this,arg)/调用之前的函数finallyletendnewDate()console.log(耗时:$end-startms)descriptor.valuelogTimereturndescriptorclassGuanYu/Step4利用语法糖装饰指定属性logTimeattack()console.log(挥了一次大刀)/Step4利用语法糖装饰指定属性l
4、ogTimerun()console.log(跑了一段间隔)constguanYunewGuanYu()guanYu.attack()/LOG:挥了一次大刀/LOG:耗时:3msguanYu.run()/LOG:跑了一段间隔/LOG:耗时:3ms为了让更直观解析上述代码是否可以编译后正常执行我们可以从直接看到编译后的代码和运行结果注意为了方便理解记得关闭配置emitDecoratorMetadata制止输出元数据元数据是另一个比拟复杂的知识点我们本篇文章先跳过关闭后编译的代码会更简单我们翻开点击Run运行按钮即可看到其正常运行以及输出结果比照纯手写的装饰器用Decorator语法糖可以省去2个
5、重复的步骤Step1备份原来类构造器(Class.prototype)的属性描绘符(Descriptor)constoldDescriptorObject.getOwnPropertyDescriptor(targetPrototype,key)Step3用装饰器函数覆盖原来属性描绘符的valueObject.defineProperty(targetPrototype,key,.oldDescriptor,value:logTime开发者仅需两步即可实现装饰器的功能可以更专注于装饰器本身的业务逻辑Step2编写装饰器函数业务逻辑代码functionlogTime(target,key,des
6、criptor)constoldMetheddescriptor.valueconstlogTimefunction(.arg)letstartnewDate()tryreturnoldMethed.apply(this,arg)/调用之前的函数finallyletendnewDate()console.log(耗时:$end-startms)descriptor.valuelogTimereturndescriptorStep4利用语法糖装饰指定属性logTimeattack()console.log(挥了一次大刀)5.2【重点】分析Decorator语法糖编译后的代码Decorator语法
7、糖很甜但却不能直接食用。因为装饰器目前仅仅是ECMAScript的语言提案还处于阶段无论是最新版的Chrome阅读器还是Node.js都不能直接运行带有Decorator语法糖的代码。我们需要借助TypeScript或Babel的才能将源码编译后才能正常运行。而在上我们可以直接看到编译后代码。为了更明晰容易理解我们把编译后的业务代码先注释掉只看装饰器实现的相关代码usestrict/Part1装饰器工具函数(_decorate)的定义var_decorate(thisthis._decorate)|function(decorators,target,key,desc)varcargument
8、s.length,rc3?target:descnull?descObject.getOwnPropertyDescriptor(target,key):desc,d;if(typeofReflectobjecttypeofReflect.decoratefunction)rReflect.decorate(decorators,target,key,desc);elsefor(varidecorators.length-1;ii-)if(ddecoratorsi)r(c3?d(r):c3?d(target,key,r):d(target,key)|r;returnc3rObject.defi
9、neProperty(target,key,r),r;functionlogTime(target,key,descriptor)/.classGuanYu/./Part2装饰器工具函数(_decorate)的执行_decorate(logTime,GuanYu.prototype,attack,null);_decorate(logTime,GuanYu.prototype,run,null);/.上述代码核心点是两个局部一个是定义一个是执行。定义局部较为复杂我们先从执行入手Part2装饰器工具函数(_decorate)的执行会传入以下4个参数装饰器业务逻辑函数类的构造器类的构造器属性名属性
10、描绘符(可以为null)为了方便理解Part1装饰器工具函数_decorate的定义我们需要精简_decorate的函数代码让它变成最简单的样子而精简代码的前提是采集条件条件1(thisthis._decorate)可删除这里的this是指window对象这一步的含义是防止重复定义_decorate函数属于辅助代码可删掉。条件2c3falsePart1的carguments.length代表参数的个数由Part2我们知道工具函数会传入4个参数因此在本次案例中c3参数个数小于3的情况不存在即c3false条件3c3true本次案例中c3参数大于3的情况存在即c3true。条件4descnull同
11、时在Part1我们知道第四个参数desc传入的值就是null即descnull条件5typeofReflect!objectReflect反射是ES6的语法本文为了更容易理解暂不引入新的ES6特性以及语法让环境默认为ES5即不存在Reflect对象即typeofReflect!object有了上述条件后我们可以进一步精简_decorate的方法代码片段1rc3?target:descnull?descObject.getOwnPropertyDescriptor(target,key):desc/根据c3false,descnull条件/精简后rdescObject.getOwnPropert
12、yDescriptor(target,key)/r以及desc此时代表的是属性的描绘符Descriptor代码片段2if(ddecoratorsi)r(c3?d(r):c3?d(target,key,r):d(target,key)|r;/根据c3false,c3true条件/精简后if(ddecoratorsi)rd(target,key,r)|r;代码片段3if(typeofReflectobjecttypeofReflect.decoratefunction)rReflect.decorate(decorators,target,key,desc);/为了方便理解本案例暂认为Reflec
13、t不存在/精简后代码片段4returnc3rObject.defineProperty(target,key,r),r;/根据c3true,r是属性描绘符必定存在/精简后Object.defineProperty(target,key,r)returnr;精简后整体代码var_decoratefunction(decorators,target,key,desc)varcarguments.length;/Step1备份原来类构造器(Class.prototype)的属性描绘符(Descriptor)varrdescObject.getOwnPropertyDescriptor(target,
14、key),vard;for(varidecorators.length-1;ii-)/d为装饰器业务逻辑函数if(ddecoratorsi)/执行d并传入target类构造器key属性名r属性描绘符rd(target,key,r)|r;/Step3用装饰器函数覆盖原来属性描绘符Object.defineProperty(target,key,r)returnr;代码经过精简之后核心原理还是以及我们4.2.2手写一个装饰器函数的原理是一样的。Step1备份原来类构造器(Class.prototype)的属性描绘符(Descriptor)利用Object.getOwnPropertyDescrip
15、tor获取*Step3用装饰器函数覆盖原来属性描绘符的value*利用Object.defineProperty代理TypeScript对装饰器编译后的代码只不过是把装饰器可复用的逻辑抽离成一个工具函数方便复用而已。分析到这里是不是对Decorator装饰器最根本的实现有了更深化的解析从上面的例子我们也进一步验证了DecoratorPattern装饰器形式的设计理念在不修改原有代码情况下对功能进展扩展Decorator装饰器的详细实现本质是函数参数有target,key,descriptorDecoretor是装饰器的一种语法糖只是一种便捷写法编译后本质还是一个函数6.带参数的装饰器装饰器工厂
16、函数在上面的记录函数耗时例子中假如我们祈望在日志前面加个可变的标签怎样实现答案是使用带参数的装饰器重点logTime是个高阶函数可以理解成装饰器工厂函数其接收参数执行后返回一个装饰器函数functionlogTime(tag)/这是一个装饰器工厂函数returnfunction(target,key,descriptor)/这是装饰器constoldMetheddescriptor.valueconstlogTimefunction(.arg)letstartnewDate()tryreturnoldMethed.apply(this,arg)finallyletendnewDate()con
17、sole.log(【$tag】耗时:$end-startms)descriptor.valuelogTimereturndescriptorclassGuanYulogTime(攻击)attack()console.log(挥了一次大刀)logTime(奔跑)run()console.log(跑了一段间隔)/.编译后/._decorate(logTime(攻击),GuanYu.prototype,attack,null);_decorate(logTime(奔跑),GuanYu.prototype,run,null);/.看了编译后的代码我们就很容易知道带参数装饰器的详细实现原理无非是直接先执
18、行装饰器工厂函数此时传入对应参数然后返回一个新的装饰器业务逻辑的函数。7.五种装饰器类、属性、方法、参数、访问器我们上面学了那么多装饰器的内容其实只学了一种装饰器方法装饰器而装饰器一共有5种类型可被我们使用类装饰器属性装饰器方法装饰器访问器装饰器参数装饰器先来个全家福然后我们逐一攻破/类装饰器classDecoratorclassGuanYu/属性装饰器propertyDecoratorname:string;/方法装饰器methodDecoratorattack(/参数装饰器parameterDecoratormeters:number)/访问器装饰器accessorDecoratorget
19、horse()7.1类装饰器类型声明/类装饰器functionclassDecorator(target:any)return/.参数只承受一个参数target:类的构造器返回假如类装饰器返回了一个值她将会被用来代替原有的类构造器的声明因此类装饰器合适用于继承一个现有类并添加一些属性以及方法。例如我们可以添加一个addToJsonString方法给所有的类来新增一个toString方法functionaddToJSONString(target)returnclassextendstargettoJSONString()returnJSON.stringify(this);addToJSONS
20、tringclassCpublicfoofoopublicnum24;console.log(newC().toJSONString()/LOG:foo:foo,num:247.2方法装饰器已经在上面章节介绍过利用方法装饰器来实现记录函数耗时功能如今我们重新复习下类型声明/方法装饰器functionmethodDecorator(target:any,propertyKey:string,descriptor:PropertyDescriptor)return/.参数target:对于静态成员来讲是类的构造器对于实例成员来讲是类的原型链propertyKey:属性的名称descriptor:属
21、性的返回假如返回了值它会被用于替代属性的描绘器。利用方法装饰器我们可以实现更多的详细场景比方打印Request的恳求参数以及结果功能functionloggerParamsResult(target,propertyKey,descriptor)constoriginaldescriptor.value;descriptor.valueasyncfunction(.args)letresultleterrortryresultawaitoriginal.call(this,.args);catch(e)errornewError(e)if(error)console.error(恳求失败)co
22、nsole.error(恳求参数:,.args)console.error(失败原因:,error)elseconsole.log(恳求成功)console.log(恳求参数,.args)console.log(恳求结果:,result)returnresult;classApploggerParamsResultrequest(data)returnnewPromise(resolve,reject)constrandomMath.random()0.5if(random)resolve(random)elsereject(random)constappnewApp();app.request(url:s:/tencent/LOG:恳求成功/LOG:恳求参数,/url:s:/tencent/LOG:恳求结果:,true