es7你都懂了吗?今天带你了解es7的神器decorator
es7带来了很多更强大的方法,比如async/await,decorator等,相信大家对于async/await已经用的很熟练了,下面我们来讲一下decorator。
何为decorator?
官方说法,修饰器(Decorator)函数,用来修改类的行为。这样讲对于初学者来说不是很好理解,通俗点讲就是我们可以用修饰器来修改类的属性和方法,比如我们可以在函数执行之前改变它的行为。因为decorator是在编译时执行的,使得让我们能够在设计时对类、属性等进行标注和修改成为了可能。decorator不仅仅可以在类上面使用,还可以在对象上面使用,但是decorator不能修饰函数,因为函数存在变量提升。decorator相当于给对象内的函数包装一层行为。decorator本身就是一个函数,他有三个参数target(所要修饰的目标类), name(所要修饰的属性名), descriptor(该属性的描述对象)。后面我们会让大家体会到decorator的强大魅力。
大型框架都在使用decorator?
Angular2中的TypeScript Annotate就是标注装潢器的另一类实现。
React中redux2也开始利用ES7的Decorators进行了大量重构。
Vue如果你在使用typescript,你会发现vue组件也开始用Decorator了,就连vuex也全部用Decorators重构。
接下来让我们举一个简单的readonly的例子:
这是一个Dog类
class Dog {bark () {return '汪汪汪!!'}}
让我们给他加上@readonly修饰器后
import { readOnly } from "./decorators";class Dog {@readonlybark () {return '汪汪汪!!'}}let dog = new Dog()dog.bark = 'wangwang!!';// Cannot assign to read only property 'bark' of [object Object]// 这里readonly修饰器把Dog类的bark方法修改为只读状态
让我们看下readonly是怎么实现的,代码很简单
/*** @param target 目标类Dog* @param name 所要修饰的属性名 bark* @param descriptor 该属性的描述对象 bark方法*/function readonly(target, name, descriptor) {// descriptor对象原来的值如下// {// value: specifiedFunction,// enumerable: false,// configurable: true,// writable: true// };descriptor.writable = false;return descriptor}
readonly有三个参数,第一个target是目标类Dog,第二个是所要修饰的属性名bark,是一个字符串,第三个是该属性的描述对象,bark方法。这里我们用readonly方法将bark方法修饰为只读。所以当你修改bark方法的时候就是报错了。
decorator 实用的decorator库 core-decorators.js
npm install core-decorators --save
// 将某个属性或方法标记为不可写。@readonly// 标记一个属性或方法,以便它不能被删除; 也阻止了它通过Object.defineProperty被重新配置@nonconfigurable// 立即将提供的函数和参数应用于该方法,允许您使用lodash提供的任意助手来包装方法。 第一个参数是要应用的函数,所有其他参数将传递给该装饰函数。@decorate// 如果你没有像Babel 6那样的装饰器语言支持,或者甚至没有编译器的vanilla ES5代码,那么可以使用applyDecorators()助手。@extendDescriptor// 将属性标记为不可枚举。@nonenumerable// 防止属性初始值设定项运行,直到实际查找修饰的属性。@lazyInitialize// 强制调用此函数始终将此引用到类实例,即使该函数被传递或将失去其上下文。@autobind// 使用弃用消息调用console.warn()。 提供自定义消息以覆盖默认消息。@deprecate// 在调用装饰函数时禁止任何JavaScript console.warn()调用。@suppressWarnings// 将属性标记为可枚举。@enumerable// 检查标记的方法是否确实覆盖了原型链上相同签名的函数。@override// 使用console.time和console.timeEnd为函数计时提供唯一标签,其默认前缀为ClassName.method。@time// 使用console.profile和console.profileEnd提供函数分析,并使用默认前缀为ClassName.method的唯一标签。@profile
还有很多这里就不过多介绍,了解更多 https://github.com/jayphelps/core-decorators
下面给大家介绍一些我们团队写的一些很实用的decorator方法库
作者:吴鹏和 罗学
noConcurrent 避免并发调用,在上一次操作结果返回之前,不响应重复操作
import {noConcurrent} from './decorators';methods: {@noConcurrent //避免并发,点击提交后,在接口返回之前无视后续点击async onSubmit(){let submitRes = await this.$http({...});//...return;}}
makeMutex 多函数互斥,具有相同互斥标识的函数不会并发执行
import {makeMutex} from './decorators';let globalStore = {};class Navigator {@makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳转相关函数并发执行static async navigateTo(route){...}@makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳转相关函数并发执行static async redirectTo(route){...}}
withErrToast 捕获async函数中的异常,并进行错误提示
methods: {@withErrToast({defaultMsg: '网络错误', duration: 2000})async pullData(){let submitRes = await this.$http({...});//...return '其他原因'; // toast提示 其他原因// return 'ok'; // 正常无提示}}
mixinList 用于分页加载,上拉加载时返回拼接数据及是否还有数据提示
methods: {@mixinList({needToast: false})async loadGoods(params = {}){let goodsRes = await this.$http(params);return goodsRes.respData.infos;},async hasMore() {let result = await this.loadgoods(params);if(result.state === 'nomore') this.tipText = '没有更多了';this.goods = result.list;}}// 上拉加载调用hasMore函数,goods数组就会得到所有拼接数据// loadGoods可传三个参数 params函数需要参数 ,startNum开始的页码,clearlist清空数组// mixinList可传一个参数 needToast 没有数据是否需要toast提示
typeCheck 检测函数参数类型
methods: {@typeCheck('number')btnClick(index){ ... },}// btnClick函数的参数index不为number类型 则报错
Buried 埋点处理方案,统计页面展现量和所有methods方法点击量,如果某方法不想设置埋点 可以 return 'noBuried' 即可
@Buriedmethods: {btn1Click() {// 埋点为 btn1Click},btn2Click() {return 'noBuried'; // 无埋点},},created() {// 埋点为 view}// 统计页面展现量和所有methods方法点击量
decorators.js
/*** 避免并发调用,在上一次操作结果返回之前,不响应重复操作* 如:用户连续多次点击同一个提交按钮,希望只响应一次,而不是同时提交多份表单* 说明:* 同步函数由于js的单线程特性没有并发问题,无需使用此decorator* 异步时序,为便于区分操作结束时机,此decorator只支持修饰async函数*/export const noConcurrent = _noConcurrentTplt.bind(null,{mutexStore:'_noConCurrentLocks'});/*** 避免并发调用修饰器模板* @param {Object} namespace 互斥函数间共享的一个全局变量,用于存储并发信息,多函数互斥时需提供;单函数自身免并发无需提供,以本地私有变量实现* @param {string} mutexStore 在namespace中占据一个变量名用于状态存储* @param {string} mutexId 互斥标识,具有相同标识的函数不会并发执行,缺省值:函数名* @param target* @param funcName* @param descriptor* @private*/function _noConcurrentTplt({namespace={}, mutexStore='_noConCurrentLocks', mutexId}, target, funcName, descriptor) {namespace[mutexStore] = namespace[mutexStore] || {};mutexId = mutexId || funcName;let oriFunc = descriptor.value;descriptor.value = function () {if (namespace[mutexStore][mutexId]) //上一次操作尚未结束,则无视本次调用return;namespace[mutexStore][mutexId] = true; //操作开始let res = oriFunc.apply(this, arguments);if (res instanceof Promise)res.then(()=> {namespace[mutexStore][mutexId] = false;}).catch((e)=> {namespace[mutexStore][mutexId] = false;console.error(funcName, e);}); //操作结束else {console.error('noConcurrent decorator shall be used with async function, yet got sync usage:', funcName);namespace[mutexStore][mutexId] = false;}return res;}}/*** 多函数互斥,具有相同互斥标识的函数不会并发执行* @param namespace 互斥函数间共享的一个全局变量,用于存储并发信息* @param mutexId 互斥标识,具有相同标识的函数不会并发执行* @return {*}*/export function makeMutex({namespace, mutexId}) {if (typeof namespace !== "object") {console.error('[makeNoConcurrent] bad parameters, namespace shall be a global object shared by all mutex funcs, got:', namespace);return function () {}}return _noConcurrentTplt.bind(null, {namespace, mutexStore:'_noConCurrentLocksNS', mutexId})}/*** 捕获async函数中的异常,并进行错误提示* 函数正常结束时应 return 'ok',return其它文案时将toast指定文案,无返回值或产生异常时将toast默认文案* @param {string} defaultMsg 默认文案* @param {number, optional} duration 可选,toast持续时长*/export function withErrToast({defaultMsg, duration=2000}) {return function (target, funcName, descriptor) {let oriFunc = descriptor.value;descriptor.value = async function () {let errMsg = '';let res = '';try {res = await oriFunc.apply(this, arguments);if (res != 'ok')errMsg = typeof res === 'string' ? res : defaultMsg;} catch (e) {errMsg = defaultMsg;console.error('caught err with func:',funcName, e);}if (errMsg) {this.$toast({title: errMsg,type: 'fail',duration: duration,});}return res;}}}/*** 分页加载* @param {[Boolean]} [是否加载为空显示toast]* @return {[Function]} [decrotor]*/export function mixinList ({needToast = false}) {let oldList = [],pageNum = 1,/*** state [string]* hasmore [还有更多]* nomore [没有更多了]*/state = 'hasmore',current = [];return function (target,name,descriptor) {const oldFunc = descriptor.value,symbol = Symbol('freeze');target[symbol] = false;/*** [description]* @param {[Object]} params={} [请求参数]* @param {[Number]} startNum=null [手动重置加载页数]* @param {[Boolean]} clearlist=false [是否清空数组]* @return {[Object]} [{所有加载页数组集合,加载完成状态}]*/descriptor.value = async function(params={},startNum=null,clearlist=false) {try {if (target[symbol]) return;// 函数执行前赋值操作target[symbol] = true;params.data.pageNum = pageNum;if (startNum !== null && typeof startNum === 'number') {params.data.pageNum = startNum;pageNum = startNum;}if (clearlist) oldList = [];// 释放函数,取回listlet before = current;current = await oldFunc.call(this,params);// 函数执行结束赋值操作(state === 'hasmore' || clearlist) && oldList.push(...current);if ((current.length === 0) || (params.data.pageSize > current.length)) {needToast && this.$toast({title: '没有更多了',type: 'fail'});state = 'nomore';} else {state = 'hasmore';pageNum++;}target[symbol] = false;this.$apply();return { list : oldList,state };} catch(e) {console.error('fail code at: ' + e)}}}}/*** 检测工具*/const _toString = Object.prototype.toString;// 检测是否为纯粹的对象const _isPlainObject = function (obj) {return _toString.call(obj) === '[object Object]'}// 检测是否为正则const _isRegExp = function (v) {return _toString.call(v) === '[object RegExp]'}/*** @description 检测函数* 用于检测类型action* @param {Array} checked 被检测数组* @param {Array} checker 检测数组* @return {Boolean} 是否通过检测*/const _check = function (checked,checker) {check:for(let i = 0; i < checked.length; i++) {if(/(any)/ig.test(checker[i]))continue check;if(_isPlainObject(checked[i]) && /(object)/ig.test(checker[i]))continue check;if(_isRegExp(checked[i]) && /(regexp)/ig.test(checker[i]))continue check;if(Array.isArray(checked[i]) && /(array)/ig.test(checker[i]))continue check;let type = typeof checked[i];let checkReg = new RegExp(type,'ig')if(!checkReg.test(checker[i])) {console.error(checked[i] + 'is not a ' + checker[i]);return false;}}return true;}/*** @description 检测类型* 1.用于校检函数参数的类型,如果类型错误,会打印错误并不再执行该函数;* 2.类型检测忽略大小写,如string和String都可以识别为字符串类型;* 3.增加any类型,表示任何类型均可检测通过;* 4.可检测多个类型,如 "number array",两者均可检测通过。正则检测忽略连接符 ;*/export function typeCheck() {const checker = Array.prototype.slice.apply(arguments);return function (target, funcName, descriptor) {let oriFunc = descriptor.value;descriptor.value = function () {let checked = Array.prototype.slice.apply(arguments);let result = undefined;if(_check(checked,checker) ){result = oriFunc.call(this,...arguments);}return result;}}};const errorLog = (text) => {console.error(text);return true;}/*** @description 全埋点* 1.在所有methods方法中埋点为函数名* 2.在钩子函数中'beforeCreate','created','beforeMount','mounted','beforeUpdate','activated','deactivated'依次寻找这些钩子* 如果存在就会增加埋点 VIEW** 用法:* @Buried* 在单文件导出对象一级子对象下;* 如果某方法不想设置埋点 可以 return 'noBuried' 即可*/export function Buried(target, funcName, descriptor) {let oriMethods = Object.assign({},target.methods),oriTarget = Object.assign({},target);// methods方法中if(target.methods) {for(let name in target.methods) {target.methods[name] = function () {let result = oriMethods[name].call(this,...arguments);// 如果方法中返回 noBuried 则不添加埋点if(typeof result === 'string' && result.includes('noBuried')) {console.log(name + '方法设置不添加埋点');} else if(result instanceof Promise) {result.then(res => {if(typeof res === 'string' && res.includes('noBuried')) { console.log(name + '方法设置不添加埋点'); return; };console.log('添加埋点在methods方法中:' , name.toUpperCase ());this.$log(name);});}else{console.log('添加埋点在methods方法中:' , name.toUpperCase ());this.$log(name);};return result;}}}// 钩子函数中const hookFun = (hookName) => {target[hookName] = function() {let result = oriTarget[hookName].call(this,...arguments);console.log('添加埋点,在钩子函数' + hookName + '中:', 'VIEW');this.$log('VIEW');return result;}}const LIFECYCLE_HOOKS = ['beforeCreate','created','beforeMount','mounted','beforeUpdate','activated','deactivated',];for(let item of LIFECYCLE_HOOKS) {if (target[item]) return hookFun(item);}}
es7你都懂了吗?今天带你了解es7的神器decorator的更多相关文章
- 人人都懂区块链--pdf电子版学习资料下载
人人都懂区块链 21天从区块链“小白”到资深玩家电子版pdf下载 链接:https://pan.baidu.com/s/1TWxYv4TLa2UtTgU-HqLECQ 提取码:6gy0 好的学习资料需 ...
- [转帖]看完这篇文章,我奶奶都懂了https的原理
看完这篇文章,我奶奶都懂了https的原理 http://www.17coding.info/article/22 非对称算法 以及 CA证书 公钥 核心是 大的质数不一分解 还有 就是 椭圆曲线算法 ...
- 《Machine Learning in Action》—— 懂的都懂,不懂的也能懂。非线性支持向量机
说在前面:前几天,公众号不是给大家推送了第二篇关于决策树的文章嘛.阅读过的读者应该会发现,在最后排版已经有点乱套了.真的很抱歉,也不知道咋回事,到了后期Markdown格式文件的内容就解析出现问题了, ...
- Java六大问题你都懂了吗?
这些问题对于认真学习java的人都要必知的,当然如果你只是初学者就没必要那么严格了,那如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列. 一.到底要怎么样初始化! 本问题讨论 ...
- javascript的那些事儿你都懂了吗
javascript从开始的验证表单的脚本语言发展到现在能运行在服务器上,其影响力不断的提升.自己作为一个做前端的,编写js是必不可少,从自己学习js的历程来看其实也是比较吃力.要 学好它,还是的花费 ...
- 看完这篇文章,我奶奶都懂了https的原理
本文在个人技术博客同步发布,详情可猛戳 亦可扫描屏幕右方二维码关注个人公众号 Http存在的问题 上过网的朋友都知道,网络是非常不安全的.尤其是公共场所很多免费的wifi,或许只是攻击者的一个诱饵 ...
- 【人人都懂密码学】一篇最易懂的Java密码学入门教程
密码与我们的生活息息相关,远到国家机密,近到个人账户,我们每天都在跟密码打交道: 那么,密码从何而来?生活中常见的加密是怎么实现的?怎么保证个人信息安全?本文将从这几方面进行浅谈,如有纰漏,敬请各位大 ...
- 1分钟了解协同过滤,pm都懂了
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/79565720 projec ...
- Selenium自动化:有代码测试与无代码测试。这些你都懂了吗?
大多数测试人员认为 Selenium是满足其测试自动化需求的自动化框架.作为全球测试人员使用的开放源框架, Selenium 无疑是测试人员适应日趋敏捷的公司的一种好方法.实际上, Selenium仍 ...
随机推荐
- vue项目中编写一个图片预览的公用组件
今天产品提出了一个查看影像的功能需求. 在查看单据的列表中,有一列是影像字段,一开始根据单据号调用接口查看是否有图片附件,如果有则弹出一个全屏的弹出层,如果没有给出提示.而且,从列表进入详情之后,附件 ...
- nginx做yum源
我这边环境是原先有个nginx只是做了代理转发,现在需要在通过nginx做yum源方便后期安装源 1.原先的配置代理转发,为不影响原先配置及端口,在http中最末尾加“include include ...
- oo第12次作业
一.规格化设计发展历史 在上世纪60年代,由于程序猿们难以忍受超级难懂的机器语言和稍微好懂一点点的汇编语言,便发明了结构化的程序语言,使得程序猿们能愉快地编写复杂程度适中的程序.但是需求是在不断增长的 ...
- C#将结构体和指针互转的方法
. 功能及位置 将数据从托管对象封送到非托管内存块,属于.NET Framework 类库 命名空间:System.Runtime.InteropServices 程序集:mscorlib(在 msc ...
- 第45章:MongoDB-集群--Sharding(分片)--分片的管理
①列出所有的Shard db.runCommand({“listshards”:1}); ②查看分片信息 db.printShardingStatus(); ③判断是否分片 db.runCommand ...
- windows 子系统 linux wsl 开启ssh 服务
原因 windows自带的终端操作丑陋,习惯xshell的操作 步骤 1.关闭windows自带的ssh服务,这个占用了22端口 2.wsl 安装ssh服务(使用的是ubuntu) sudo apt ...
- 20155205 郝博雅 Exp6 信息搜集与漏洞扫描
20155205 郝博雅 Exp6 信息搜集与漏洞扫描 一.实践内容 (1)各种搜索技巧的应用 (2)DNS IP注册信息的查询 (3)基本的扫描技术:主机发现.端口扫描.OS及服务版本探测.具体服务 ...
- [swarthmore cs75] Lab 0 Warmup & Basic OCaml
课程回顾 Swarthmore学院16年开的编译系统课,总共10次大作业.本随笔记录了相关的课堂笔记以及第1次大作业. 什么是编译 编译就是执行Program->Program'转换的过程,如下 ...
- max (Largest elements in array)
句法: M = max(A) M = max(A,[],dim) [M,I] = max(___) C = max(A,B) ___ = max(___,nanflag) 描述: M=max(A) ...
- FPGA计算中定标与位扩展的实现
我不知道名字取对没有,在FPGA计算中有时往往需要在不溢出的情况下将数扩大,从而获得更好的计算精度. 比如.在一个8位宽的系统中,将x=0000_0010,算术左移m=5位之后得到xt=0100_00 ...