临时起的兴趣,想写一篇关于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

理解一下执行的步骤:

1. decorators = [show],target = Shoow;

2. c = 2,r = target{Shoow},d = undefined;

3. 不存在 Reflect,走循环,只循环一次;

4. d = show,r = show(target{Shoow}),r 没返回结果,所以 r 还是 r , r = target{Shoow};

5. return 结果: c = 2, 所以返回 false;

6. 执行后的无返回值,但是在执行 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

再来看看执行步骤:

1. decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,这里是为null,不是为undefined;

2. c = 4,r = target{Shoow},d = undefined;

3. 不存在 Reflect,走循环,只循环一次;

4. 第一次循环取 d = logger1,r = logger1(target, key, r),因为 return 存在值,r = logger1 的返回函数,这时候 descriptor.value 被第一次重写;

5. 第二次循环取 d = logger2,r = logger2(target, key, r),又因为 return 存在值,r = logger2 的返回函数,这时候 descriptor.value 被第二次重写;

6. return 结果: 因为 c > 3,r 存在值,执行 Object.defineProperty(target, key, r)来重写对象属性并且返回 r (r为重写的结果);

7. 经过 2 次重写 showMe 属性值,执行结果符合预期。

欢乐

装饰器给你带来什么欢乐?

简单列几个最明显的优点,其他在运用中各自提取每日份的快乐去吧...

1. 业务和功能之间的解耦(比如日志)

  // 日常
dosomething(){
consol.log('start')
// some thing
console.log('end')
} // 使用装饰器
@logger(logConfig?)
dosomething();

2. 代码结构清晰(特别针对多层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;

最后

注意编译

 tsc --target ES5 --experimentalDecorators

或者 tsconfig.json 里加入

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}

顺便,AOP了解一下...

浅入浅出Typescript Decorators的更多相关文章

  1. 浅入浅出EmguCv(三)EmguCv打开指定视频

    打开视频的思路跟打开图片的思路是一样的,只不过视频是由一帧帧图片组成,因此,打开视频的处理程序有一个连续的获取图片并逐帧显示的处理过程.GUI同<浅入浅出EmguCv(二)EmguCv打开指定图 ...

  2. 浅入浅出EmguCv(一)OpenCv与EmguCv

    最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...

  3. 浅入深出之Java集合框架(上)

    Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  4. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  5. 浅入深出之Java集合框架(下)

    Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...

  6. 浅入深出Vue:环境搭建

    浅入深出Vue:环境搭建 工欲善其事必先利其器,该搭建我们的环境了. 安装NPM 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版本的NodeJS Windows安装程序 下载下来后,直 ...

  7. 浅入深出Vue:工具准备之PostMan安装配置及Mock服务配置

    浅入深出Vue之工具准备(二):PostMan安装配置 由于家中有事,文章没顾得上.在此说声抱歉,这是工具准备的最后一章. 接下来就是开始环境搭建了~尽情期待 工欲善其事必先利其器,让我们先做好准备工 ...

  8. 浅入深出Vue:工具准备之WebStorm安装配置

    浅入深出Vue之工具准备(一):WebStorm安装配置 工欲善其事必先利其器,让我们先做好准备工作吧 导航篇 WebStorm安装配置 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版 ...

  9. 浅入深出Vue系列

    浅入深出Vue导航 导航帖,直接点击标题即可. 文中所有涉及到的资源链接均在最下方列举出来了. 前言 基础篇 浅入深出Vue:工具准备之WebStorm搭建及配置 浅入深出Vue之工具准备(二):Po ...

  10. 浅入深出Vue:前言

    浅入深出Vue系列文章 之前大部分是在做后端,后来出于某些原因开始接触Vue.深感前端变化之大,各种工具.框架令人眼花缭乱.不过正是这些变化,让前端开发更灵活. 博主在刚开始时,参考官网的各个步骤以及 ...

随机推荐

  1. 剑指offer PART 2

    剑指offer PART 2 书点击自取 提取码: njku 标签(空格分隔): 笔记 C++知识点: 1.面向对象的特性 2.构造函数 3.析构函数 4.动态绑定 5.常用的设计模式 6.UML图 ...

  2. C#实现简单的RPC

    demo地址:https://pan.baidu.com/s/1PeTdV2V9DF87jZTHdz4CyA 提取码:n2qm 参考地址:https://github.com/neuecc/Magic ...

  3. WPF一组Radio与enum绑定

    工作中用到了一组RadioButton对应一组数据的情况,这个时候最好能将一组RadioButton绑定Enum. 步骤 1.MyControl库中Type.cs中定义Enum namespace M ...

  4. linux nfs远程挂载和卸载

    一.nfs远程挂载 1.首先确定服务端(实体挂载节点)的IP 2.通过cat  /etc/hosts 查看服务端的server name 3.mount -t nfs servername:/挂载文件 ...

  5. centos7安装配置nfs

    操作系统版本:3.10.0-123.el7.x86_64 192.168.137.11  nfs服务端 192.168.137.10  nfs客户端 一.安装nfs服务端(在192.168.137.1 ...

  6. java注解的实质,何为注解

    注解就是贴标签 (1)注解的作用 1,生成文档.如常用的@param 2,跟踪代码依赖性,实现替代文件的功能.在spring中,主要是减少配置. 3,编译时进行格式检查.如常用的@override ( ...

  7. Unity自动切割动画

    最近在开发项目时,需要处理大量的动画,于是就网上查找资料,然后写了这么编辑器工具: 就是在模型导入时,根据配置文件自动切割动画. 首先我们需要封装两个类:一个模型类和一个动画类 public clas ...

  8. python_redis简介与安装和使用

    一.简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted ...

  9. Java File类与文件IO流总结

    1.File类 File类被定义为“文件和目录路径名的抽象表示形式”,这是因为File类既可以表示“文件”也可以表示“目录”,他们都通过对应的路径来描述.通过构造函数创建一个File类对象,则该对象就 ...

  10. mysql中加入海量数据

    delimiter // create procedure m() begin declare num int; set num=1; while num < 100000 insert int ...