decorator(修饰器)的业务应用
decrator(修饰器)的业务应用
ES6问世的时间也不短了,而且很多时候对ES6所谓的“熟练应用”基本还停留在下面的几种api应用:
- const/let
- 箭头函数
- Promise
- async await
- 解构、扩展运算符
- Object.assign
- class static
- 数组遍历api
(当然也可能是我用的比较简单)
最近也是看了很多大神写的代码,确实学到了很多东西,这也让我下定决心要更深层次的应用ES6
本次我们介绍decrator(修饰器)在业务中的应用
decrator 基础
首先我们先看下decrator的用法:
1.类修饰器(只有一个参数):
target -> 指向类,如果是类型是function,则指向MyFunction.prototype
// 类修饰器
const animalDecorator = (target) => {
target.isAnimal = true
target.prototype.nickname = 'nimo'
};
@animalDecorator
class Cat {
...
}
console.log(Cat.isAnimal); // true
console.log((new Cat()).nickname); // 'nimo'
2.方法修饰器(有三个参数)
target -> 方法所在的类
key -> 方法名称
descriptor -> 描述对象
// 方法修饰器
const log = (target, key, descriptor) => {
const oriFunc = descriptor.value
descriptor.value = (...args) => {
console.log(`${key}:', args)
oriFunc.apply(this, args)
}
return descriptor
};
class Util {
@log
static setParam (param) {
...
}
}
Util.setParam({name: 'xxx'}) // 'setParam: {name: "xxx"}'
上面的用法没有传参数,如果需要传参数的话,内部需要return一个方法,以方法修饰器为例
// 方法修饰器
const log = (name) => {
return (target, key, descriptor) => {
const oriFunc = descriptor.value
descriptor.value = (...args) => {
console.log(`${key} ${name}:`, args)
oriFunc.apply(this, args)
}
return descriptor
}
};
class Util {
@log('forTest')
static setParam (param) {
...
}
}
Util.setParam({name: 'xxx'}) // 'setParam forTest: {name: "xxx"}'
decrator 实际应用
上面说的大家从网络上各种文章基本都能看到。
应用的话打日志也算是一种,但是感觉应用场景有限,一般对关键业务操作才会用到。常规的业务感觉应用并不多。
下面介绍几个常见的场景:
- 某个场景下需要同时请求多个接口,但这些接口都需要做登录验证
- 发送行为埋点,发送前需要获取token(如果cookie中有就从本地获取,否则从接口获取。注:这个token和登录没关系,是用来计算pv和uv的唯一标识)
我们以发送行为统计前需要获取token为例:
场景: 页面加载完成后,需要同时发送多个行为埋点统计(如:pv、某些模块曝光点)
特点: 每次发送埋点都要检查token是否存在,在本地cookie中没有token的时候,就会从接口获取,并种到本地。
看着逻辑好像没问题。
实际: 这些行为埋点方法调用的时机,基本上是同时发生。如果cookie中没用token,这几次api调用都会触发获取token接口的调用,这就导致多次不必要的请求。
目标: 我们希望,就请求一次接口就可以了。
那么,我们就需要处理发送埋点的方法,一般有两种方式:
- 传统方式:修改统计方法,建立callback缓存数组,只有第一次调用接口,修改标志位,把后面调用的callback通通缓存在数组里,等请求结束,在统一调用数组里的callbakc
- 通过修饰器处理(但实现原理也是如此)
统计方法:
...
/**
* 上报埋点
* @param {string} actiontype
* @param {string, optional} pagetype
* @param {Object, optional} backup
*/
static report (actiontype, pagetype, backup = {}) {
try {
// 处理actiontype字段
if (!actiontype) return
actiontype = actiontype.toUpperCase() // 转为大写
// 处理pagetype字段
if (!pagetype) {
// 获取当前页面的页面名称
pagetype = Util.getPageName()
}
pagetype = pagetype.toUpperCase()
// 处理backup字段
if (backup && typeof backup !== 'object') {
console.error('[埋点失败] backup字段应为对象类型, actionType:', actiontype, 'pageType:', pagetype, 'backup:', backup)
return
}
let commonParams = LeStatic._options.commonBackup.call(this)
for (let param in backup) {
if (param in commonParams) {
console.warn(`[埋点冲突] 参数名称: ${param} 与统一埋点参数名称冲突,请注意检查`, `actionType:`, actiontype, 'pageType:', pagetype, 'backup:', backup)
}
}
backup = Object.assign(commonParams, backup)
backup = JSON.stringify(backup)
// 保证token的存在
ZZLogin.ensuringExistingToken().then(() => {
// 获取cookieid字段
let cookieid = Cookies.get('tk')
// 发送埋点请求
wx.request({
url: LeStatic._options.LOG_URL,
data: {
cookieid,
actiontype,
pagetype,
appid: 'ZHUANZHUAN',
_t: Date.now(),
backup
},
success: (res) => {
if (res.data === false) {
console.warn('[埋点上报失败] 接口返回false, actionType:', actiontype, 'pageType:', pagetype)
}
},
fail: (res) => {
console.warn('[埋点上报失败] 网络异常, res:', res)
}
})
})
} catch (e) {
console.warn('[埋点上报失败] 捕获代码异常:', e)
}
}
这块看着好像没做缓存处理,别着急
关键点在:ZZLogin.ensuringExistingToken()的调用,我们来看下ZZLogin中的ensuringExistingToken方法
lib/ZZLogin.js
import { mergeStep } from '@/lib/decorators'
class ZZLogin {
...
/**
* token机制,请求发起前,先确保本地有token,如果没有,调用接口生成一个临时token,登录后
* @return {Promise}
*/
@mergeStep
static ensuringExistingToken () {
return new Promise((resolve, reject) => {
const tk = cookie.get('tk') || ''
// token已存在
if (/^wt-/.test(tk)) {
resolve()
return
}
// 获取用户token
ZZLogin.getToken().then(res => {
resolve()
})
})
}
}
我们在调用ensuringExistingToken 时加了修饰器,目的就是,即使同时刻多次调用,异步请求也是被合并成了一次,其他次的调用也是在第一次异步请求完成后,再进行统一调用。
来看看修饰器是怎么写的(mergeStep)
lib/decorators.js
...
// 缓存对象
const mergeCache = {}
export function mergeStep (target, funcName, descriptor) {
const oriFunc = descriptor.value
descriptor.value = (...args) => {
// 如果第一次调用
if (!mergeCache[funcName]) {
mergeCache[funcName] = {
state: 'doing', // 表示处理中
fnList: []
}
return new Promise((resolve, reject) => {
// 进行第一次异步处理
oriFunc.apply(null, args).then(rst => {
// 处理完成后,将状态置为done
mergeCache[funcName].state = 'done'
resolve(rst)
// 将缓存中的回调逐一触发
mergeCache[funcName].fnList.forEach(fnItem => {
fnItem()
})
// 触发后将数组置空
mergeCache[funcName].fnList.length = 0
})
})
// 同时刻多次调用
} else {
// 后面重复的调用的回调直接缓存到数组
if (mergeCache[funcName].state === 'doing') {
return new Promise((resolve, reject) => {
mergeCache[funcName].fnList.push(() => {
resolve(oriFunc.apply(null, args))
})
})
// 如果之前异步状态已经完成,则直接调用
} else {
return oriFunc.apply(null, args)
}
}
}
return descriptor
}
原理:
- 如果是第一次调用:创建缓存,建立promise对象,直接进行异步请求,并将状态改为doing
- 后面重复调用时,发现是doing状态,就将每个调用包装成一个promise,将callback,放到缓存数组中
- 第一次异步请求完成后,将状态改为done,并将缓存数组中的callback统一调用
- 后面再重复调用,发现状态已经是done了,就直接触发回调
其实修饰器大家知道么,基本上都了解,可业务里就是从来不用。包括es6中其它api也一样,会用了才是自己的。
最近也是全组一起重新深入学习es6的应用,并且是结合实际业务。
后面也是打算对现有项目的公共库进行算法优化升级。如果有机会再进行分享。

decorator(修饰器)的业务应用的更多相关文章
- ES6(Decorator(修饰器))
Decorator(修饰器) 1.基本概念 函数用来修改 类 的行为 1.Decorator 是一个函数 2.通过Decorator(修饰器)能修改 类 的行为(扩展 类 的功能)3.Decorato ...
- 19.Decorator修饰器
Decorator 修饰器 类的修饰 许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为.目前,有一个提案将这项功能,引入了 ECMAScript. @testable clas ...
- mobx中使用class语法或decorator修饰器时报错
之前课程中老师用的babel的版本比较低,我在学习时安装的babel版本较高,因此每当使用class语法或decorator修饰器时都会出现一些报错的情况! ❌ ERROR in ./src/inde ...
- js基石之---es7的decorator修饰器
es7的decorator修饰器 装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法. decorator就是给类添加或修改类的变量与方法的. 装饰器是一种函数, ...
- es6 Decorator修饰器
1.类的修饰: 修饰器(Decorator)函数,用来修改类的行为.修饰器是一个对类进行处理的函数.修饰器函数的第一个参数,就是所要修饰的目标类. @testable class MyTestable ...
- python decorator 修饰器
decorator 就是给函数加一层皮,好用! 1 from time import ctime 2 3 def deco(func): 4 def wrappedFunc(*args, **kwar ...
- ES6 Decorator 修饰器
目的: 修改类的一种方法,修饰器是一个函数 编译: 安装 babel-plugin-transform-decortators-legacy .babelrd plugins: [&quo ...
- 21.Decorator修饰器
1.类的修饰 2.方法的修饰 3.为什么修饰器不能用于函数? 4.core-decorators.js 5.使用修饰器实现自动发布事件 6.Mixin 7.Trait 8.Babel转码器的支持
- mobx学习笔记03——mobx基础语法(decorator修饰器)
在声明阶段实现类与类成员注解的一种语法. function log(target){ const desc = Object.getOwnPropertyDescriotors(target.prot ...
随机推荐
- Linux上配置使用iSCSI详细说明
本文详细介绍iSCSI相关的内容,以及在Linux上如何实现iSCSI. 第1章 iSCSI简介 1.1 scsi和iscsi 传统的SCSI技术是存储设备最基本的标准协议,但通常需要设备互相靠近并用 ...
- 如何把Python脚本导出为exe程序
一.pyinstaller简介 pyinstaller将Python脚本打包成可执行程序,使在没有Python环境的机器上运行 最新版是pyinstaller 3.1.1.支持python2.7和py ...
- Windows 使用 Visual Studio 编译 caffe
说明:最近看 caffe 发现在 github 上下载的源码没有windows版本的,需要自己生成项目文件才能用 Visual Studio 编译,这里记录一下生成Windows项目文件的方法以及编译 ...
- BZOJ_3252_攻略_线段树+dfs序
BZOJ_3252_攻略_线段树+dfs序 Description 题目简述:树版[k取方格数] 众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏.今天他得到了一款新游戏< ...
- BZOJ_4756_[Usaco2017 Jan]Promotion Counting_树状数组
BZOJ_4756_[Usaco2017 Jan]Promotion Counting_树状数组 Description n只奶牛构成了一个树形的公司,每个奶牛有一个能力值pi,1号奶牛为树根. 问对 ...
- Django之META与前后端交互
Django之META与前后端交互 1 提交表单之GET 前端提交数据与发送 1)提交表单数据 2)提交JSON数据 后端的数据接收与响应 1)接收GET请求数据 2)接收POST请求数据 3)响应请 ...
- linux系统版本查看
Linux下如何查看版本信息, 包括位数.版本信息以及CPU内核信息.CPU具体型号等等,整个CPU信息一目了然. 1.# uname -a (Linux查看版本当前操作系统内核信息) Lin ...
- JDBC知识详解
一.相关概念 1.什么是JDBC JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它 ...
- 【SAP业务模式】之STO(一):业务背景和前台操作
所谓STO即两个关联公司之间的库存转储交易,一家公司发出采购订单向另一家公司做采购,然后在做发货.如此之后,两家公司有相应应收应付的票据,以及开票和发票校验等动作. STO分为一步法与两步法,因为一步 ...
- ES 14 - (底层原理) Elasticsearch内部如何处理不同type的数据
目录 1 type的作用 2 type的底层数据结构 3 探究type的存储结构 3.1 创建索引并配置映射 3.2 添加数据 3.3 查看存储结构 4 关于type的最佳实践 1 type的作用 在 ...