浅入浅出Typescript Decorators
临时起的兴趣,想写一篇关于ts decorator的文章,就花小半天整理了一下...
这东西,在ES2017里好像也有... 文档的话看这里。
因为临时,也没想写太多文字介绍,带少许文字说明直接开撸代码吧。
本文通过ts编译后的decorator代码解释一番装饰器是什么?能做什么?有什么好处?
实现代码
编译后代码是这样的,带注释:
var __decorate = (this && this.__decorate) || function(decorators, target, key, desc) { // c 参数长度 // r ? c < 3 则是target,否则先判断desc为null的话则将desc取target的key属性的描述,再否则便是desc了 // d 预留使用 var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; // 下面文字解释,这仅是个甩锅的行为 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); // 循环 decorators 并每次赋值给 d,并且判断值 else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) // c < 3 ,用 r 作为 decorators[i] 的入参执行; // c > 3 ,target, key, r 作为 decorators[i] 的入参执行; // c === 3,target, key 作为 decorators[i] 的入参执行。 // 如果执行无返回结果, r = r;。 r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; // 如果 c > 3 && r , 修改 target ,返回 r return c > 3 && r && Object.defineProperty(target, key, r), r; };
从代码里可以看出,最终结果要么是用decorator执行target,从而改变一些什么东西;要么就是使用Object.defineProperty来对target来做操作,代码就几行,用处确不小...具体的执行过程结合下面的两个例子会更容易理解。
值得一提的是,关于代码里的Reflect原本以为是这个 sec-reflect-object 里的方法,但可惜不是;
然后猜测是Typescript的实现,翻了Typescript/tsc.js的代码(如果打不开链接就从 node_modules 下看吧),发现也不是,再去查 stackoverflow 的解释,是这样的 what-is-reflect-decorate-in-js-code-transpiled-from-ts
大致说是ts希望把这个锅甩给ES来补,到时候ts的else里的代码便是polyfill了
案例
以下面的 decorator 和 class 作为例子解释
// ts 代码 function show(target: any) { console.log(target); target.prototype.showMe = (name: string) => { console.log("show me :", name); }; } interface IShow { showMe?(name: string): any; } @show class Show implements IShow { showMe(name: string) {} } const shoow = new Show(); shoow.showMe("ys"); // 编译后的js // decorator ,简单的打印,并且修改方法 function show(target) { console.log(target); target.prototype.showMe = function(name) { console.log("show me :", name); }; } // class Shoow var Shoow = (function() { function Shoow() {} Shoow.prototype.showMe = function(name) {}; // decorators 为[show],target 为 Shoow Shoow = __decorate([show], Shoow); return Shoow; })(); var shooow = new Shoow(); shooow.showMe("ys"); // output : show me : ys
理解一下执行步骤:
- decorators = [show],target = Shoow,
- c = 2,r = target{Shoow},d = undefined
- 不存在 Reflect,走循环,只循环一次
- d = show,r = show(target{Shoow}),r 没返回结果,所以 r 还是 r , r = target{Shoow}
- return 结果: c = 2, 所以返回 false
- 执行后无返回值,但是在执行show(target{Shoow})的时候将showMe方法改掉了,于是执行结果符合预期
一个不够?再来一个?这次在里面返回一个函数试试?
// ts代码 function logger1(config?) { return function(target, key: string, descriptor: PropertyDescriptor) { const _value = descriptor.value; if (typeof _value === "function") { descriptor.value = (...args) => { console.log(`logger1-begin : ${config.level}`); const res = _value.apply(target, args); console.log(`logger1-end`); return res; }; } return descriptor; }; } function logger2(config?) { return function(target, key: string, descriptor: PropertyDescriptor) { const _value = descriptor.value; if (typeof _value === "function") { descriptor.value = (...args) => { console.log(`logger2-begin : ${config.level}`); const res = _value.apply(target, args); console.log(`logger2-end`); return res; }; } return descriptor; }; } interface IShow { showMe?(name: string): any; } class Show implements IShow { @logger1({ level: "info" }) @logger2({ level: "error" }) showMe(name: string) { console.log("show me :", name); } } const shoow = new Show(); shoow.showMe("ys"); // output 这里手动加个缩进,这时候showMe方法已经经过多次包裹 // logger1-begin : info // logger2-begin : error // show me : ys // logger2-end // logger1-end
再来看看执行步骤:
- decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,这里是为null,不是为undefined
- c = 4,r = target{Shoow},d = undefined
- 不存在 Reflect,走循环,只循环一次
- 第一次循环取 d = logger1,r = logger1(target, key, r),因为 return 存在值,r = logger1 的返回函数,这时候 descriptor.value 被第一次重写
- 第二次循环取 d = logger2,r = logger2(target, key, r),又因为 return 存在值,r = logger2 的返回函数,这时候 descriptor.value 被第二次重写
- return 结果: 因为 c > 3,r 存在值,执行 Object.defineProperty(target, key, r)来重写对象属性并且返回 r (r为重写的结果)
- 经过 2 次重写 showMe 属性值,执行结果符合预期
欢乐
装饰器给你带来什么欢乐?简单列几个最明显的优点,其他在运用中各自提取每日份的快乐去吧...
- 业务和功能之间的解耦(比如日志)
// 日常 dosomething(){ consol.log('start') // some thing console.log('end') } // 使用装饰器 @logger(logConfig?) dosomething();
- 代码结构清晰(特别针对多层HOC后的React组件)
// 日常多层HOC class MyComponent extends Component{ // .. } connect(mapFn)( MyHoc(someMapFn)( Form.create(fieldsMapFn)(MyComponent) ) ) // 使用装饰器 @connect(mapFn) @MyHoc(someMapFn) @FormFields(fieldsMapFn) class MyComponent extends Component{ // .. } export default MyComponent;
最后
AOP,了解一下
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【直播预告】阿里技术专家天樵:基于JVM的脚本语言开发、运用实践
主讲人:天樵(阿里集团-业务平台事业部-技术专家)本名:包行杰 来自阿里巴巴的技术专家 — 天樵,在阿里巴巴长期担任规则引擎开发工作,通过规则引擎、规则管理平台等技术输出,来支持阿里巴巴复杂多变的上层业务变更需求,对脚本工具、规则引擎、流程引擎等有比较深入的研究。 目前负责的阿里开源项目:https://github.com/alibaba/QLExpress 点击关注天樵的云栖社区个人主页 内容概要:复杂的业务系统往往会使用表达式(EL)或者脚本语言(script)来实现部分业务逻辑的实时动态发布,比如在数学公式计算,规则引擎、脚本引擎中的运用。 本人在阿里巴巴长期担任规则引擎开发工作,通过规则引擎、规则管理平台等技术输出,来支持阿里巴巴复杂多变的上层业务变更需求。 本次分享聚焦基于jvm的脚本语言,通过对这个领域常见的脚本语言原理、基本使
- 下一篇
StringMVC,jsp如何让转换科学技术法格式,并且限制输入为数字?
引入 : <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <body><td><tr> <fmt:formatNumber type="number" value="${entity.属性}" pattern="#.######" var="formattedNumber"/> <form:input path="" htmlEscape="false" class="form-control" number="true" value="${formattedNumber}"/> </tr></td></body>
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- 设置Eclipse缩进为4个空格,增强代码规范
- Mario游戏-低调大师作品
- MySQL8.0.19开启GTID主从同步CentOS8