es7你都懂了吗?今天带你了解es7的神器decoratorv

时间:2019-05-11 09:46来源:计算机教程
es7带来了很多更强大的方法,比如async/await,decorator等,相信大家对于async/await已经用的很熟练了,下面我们来讲一下decorator。 在 ES6 中增加了对类对象的相关定义和操作(比如 class 和

es7带来了很多更强大的方法,比如async/await,decorator等,相信大家对于async/await已经用的很熟练了,下面我们来讲一下decorator。

在 ES6 中增加了对类对象的相关定义和操作(比如 class 和 extends ),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并不是那么优雅。这个时候,我们就需要一种更优雅的方法来帮助我们完成这些事情。

何为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重构。

Python 的装饰器

接下来让我们举一个简单的readonly的例子:

这是一个Dog类

  1. class Dog {

  2. bark () {

  3. return '汪汪汪!!'

  4. }

  5. }

让我们给他加上@readonly修饰器后

  1. import { readOnly } from "./decorators";

  2.  

  3. class Dog {

  4. @readonly

  5. bark () {

  6. return '汪汪汪!!'

  7. }

  8. }

  9.  

  10. let dog = new Dog()

  11. dog.bark = 'wangwang!!';

  12. // Cannot assign to read only property 'bark' of [object Object]

  13. // 这里readonly修饰器把Dog类的bark方法修改为只读状态

让我们看下readonly是怎么实现的,代码很简单

  1. /**

  2. * @param target 目标类Dog

  3. * @param name 所要修饰的属性名 bark

  4. * @param descriptor 该属性的描述对象 bark方法

  5. */

  6. function readonly(target, name, descriptor) {

  7. // descriptor对象原来的值如下

  8. // {

  9. //   value: specifiedFunction,

  10. //   enumerable: false,

  11. //   configurable: true,

  12. //   writable: true

  13. // };

  14. descriptor.writable = false;

  15. return descriptor

  16. }

readonly有三个参数,第一个target是目标类Dog,第二个是所要修饰的属性名bark,是一个字符串,第三个是该属性的描述对象,bark方法。这里我们用readonly方法将bark方法修饰为只读。所以当你修改bark方法的时候就是报错了。

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。

decorator 实用的decorator库 core-decorators.js

npm install core-decorators --save

  1. // 将某个属性或方法标记为不可写。

  2. @readonly

  3. // 标记一个属性或方法,以便它不能被删除; 也阻止了它通过Object.defineProperty被重新配置

  4. @nonconfigurable

  5. // 立即将提供的函数和参数应用于该方法,允许您使用lodash提供的任意助手来包装方法。 第一个参数是要应用的函数,所有其他参数将传递给该装饰函数。

  6. @decorate

  7. // 如果你没有像Babel 6那样的装饰器语言支持,或者甚至没有编译器的vanilla ES5代码,那么可以使用applyDecorators()助手。

  8. @extendDescriptor

  9. // 将属性标记为不可枚举。

  10. @nonenumerable

  11. // 防止属性初始值设定项运行,直到实际查找修饰的属性。

  12. @lazyInitialize

  13. // 强制调用此函数始终将此引用到类实例,即使该函数被传递或将失去其上下文。

  14. @autobind

  15. // 使用弃用消息调用console.warn()。 提供自定义消息以覆盖默认消息。

  16. @deprecate

  17. // 在调用装饰函数时禁止任何JavaScript console.warn()调用。

  18. @suppressWarnings

  19. // 将属性标记为可枚举。

  20. @enumerable

  21. // 检查标记的方法是否确实覆盖了原型链上相同签名的函数。

  22. @override

  23. // 使用console.time和console.timeEnd为函数计时提供唯一标签,其默认前缀为ClassName.method。

  24. @time

  25. // 使用console.profile和console.profileEnd提供函数分析,并使用默认前缀为ClassName.method的唯一标签。

  26. @profile

还有很多这里就不过多介绍,了解更多 https://github.com/jayphelps/core-decorators

如果你熟悉 python 的话,对它一定不会陌生。那么我们先来看一下 python 里的装饰器是什么样子的吧:

下面给大家介绍一些我们团队写的一些很实用的decorator方法库

作者:吴鹏和 罗学

  • noConcurrent 避免并发调用,在上一次操作结果返回之前,不响应重复操作
  1. import {noConcurrent} from './decorators';

  2. methods: {

  3. @noConcurrent     //避免并发,点击提交后,在接口返回之前无视后续点击

  4. async onSubmit(){

  5. let submitRes = await this.$http({...});

  6. //...

  7. return;

  8. }

  9. }

  • makeMutex 多函数互斥,具有相同互斥标识的函数不会并发执行
  1. import {makeMutex} from './decorators';

  2. let globalStore = {};

  3. class Navigator {

  4. @makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳转相关函数并发执行

  5. static async navigateTo(route){...}

  6.  

  7. @makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳转相关函数并发执行

  8. static async redirectTo(route){...}

  9. }

  • withErrToast 捕获async函数中的异常,并进行错误提示
  1. methods: {

  2. @withErrToast({defaultMsg: '网络错误', duration: 2000})

  3. async pullData(){

  4. let submitRes = await this.$http({...});

  5. //...

  6. return '其他原因'; // toast提示 其他原因

  7. // return 'ok';   // 正常无提示

  8. }

  9. }

  • mixinList 用于分页加载,上拉加载时返回拼接数据及是否还有数据提示
  1. methods: {

  2. @mixinList({needToast: false})

  3. async loadGoods(params = {}){

  4. let goodsRes = await this.$http(params);

  5. return goodsRes.respData.infos;

  6. },

  7. async hasMore() {

  8. let result = await this.loadgoods(params);

  9. if(result.state === 'nomore') this.tipText = '没有更多了';

  10. this.goods = result.list;

  11. }

  12. }

  13. // 上拉加载调用hasMore函数,goods数组就会得到所有拼接数据

  14. // loadGoods可传三个参数 params函数需要参数 ,startNum开始的页码,clearlist清空数组

  15. // mixinList可传一个参数 needToast 没有数据是否需要toast提示

typeCheck 检测函数参数类型

  1. methods: {

  2. @typeCheck('number')

  3. btnClick(index){ ... },

  4. }

  5. // btnClick函数的参数index不为number类型 则报错

Buried 埋点处理方案,统计页面展现量和所有methods方法点击量,如果某方法不想设置埋点 可以 return 'noBuried' 即可

  1. @Buried

  2. methods: {

  3. btn1Click() {

  4. // 埋点为 btn1Click

  5. },

  6. btn2Click() {

  7. return 'noBuried'; // 无埋点

  8. },

  9. },

  10. created() {

  11. // 埋点为 view

  12. }

  13. // 统计页面展现量和所有methods方法点击量

decorators.js

  1. /**

  2. * 避免并发调用,在上一次操作结果返回之前,不响应重复操作

  3. * 如:用户连续多次点击同一个提交按钮,希望只响应一次,而不是同时提交多份表单

  4. * 说明:

  5. *    同步函数由于js的单线程特性没有并发问题,无需使用此decorator

  6. *    异步时序,为便于区分操作结束时机,此decorator只支持修饰async函数

  7. */

  8. export const noConcurrent = _noConcurrentTplt.bind(null,{mutexStore:'_noConCurrentLocks'});

  9.  

  10. /**

  11. * 避免并发调用修饰器模板

  12. * @param {Object} namespace 互斥函数间共享的一个全局变量,用于存储并发信息,多函数互斥时需提供;单函数自身免并发无需提供,以本地私有变量实现

  13. * @param {string} mutexStore 在namespace中占据一个变量名用于状态存储

  14. * @param {string} mutexId   互斥标识,具有相同标识的函数不会并发执行,缺省值:函数名

  15. * @param target

  16. * @param funcName

  17. * @param descriptor

  18. * @private

  19. */

  20. function _noConcurrentTplt({namespace={}, mutexStore='_noConCurrentLocks', mutexId}, target, funcName, descriptor) {

  21. namespace[mutexStore] = namespace[mutexStore] || {};

  22. mutexId = mutexId || funcName;

  23.  

  24. let oriFunc = descriptor.value;

  25. descriptor.value = function () {

  26. if (namespace[mutexStore][mutexId]) //上一次操作尚未结束,则无视本次调用

  27. return;

  28.  

  29. namespace[mutexStore][mutexId] = true; //操作开始

  30. let res = oriFunc.apply(this, arguments);

  31.  

  32. if (res instanceof Promise)

  33. res.then(()=> {

  34. namespace[mutexStore][mutexId] = false;

  35. }).catch((e)=> {

  36. namespace[mutexStore][mutexId] = false;

  37. console.error(funcName, e);

  38. }); //操作结束

  39. else {

  40. console.error('noConcurrent decorator shall be used with async function, yet got sync usage:', funcName);

  41. namespace[mutexStore][mutexId] = false;

  42. }

  43.  

  44. return res;

  45. }

  46. }

  47.  

  48. /**

  49. * 多函数互斥,具有相同互斥标识的函数不会并发执行

  50. * @param namespace 互斥函数间共享的一个全局变量,用于存储并发信息

  51. * @param mutexId   互斥标识,具有相同标识的函数不会并发执行

  52. * @return {*}

  53. */

  54. export function makeMutex({namespace, mutexId}) {

  55. if (typeof namespace !== "object") {

  56. console.error('[makeNoConcurrent] bad parameters, namespace shall be a global object shared by all mutex funcs, got:', namespace);

  57. return function () {}

  58. }

  59.  

  60. return _noConcurrentTplt.bind(null, {namespace, mutexStore:'_noConCurrentLocksNS', mutexId})

  61. }

  62.  

  63. /**

  64. * 捕获async函数中的异常,并进行错误提示

  65. * 函数正常结束时应 return 'ok',return其它文案时将toast指定文案,无返回值或产生异常时将toast默认文案

  66. * @param {string} defaultMsg  默认文案

  67. * @param {number, optional} duration 可选,toast持续时长

  68. */

  69. export function withErrToast({defaultMsg, duration=2000}) {

  70. return function (target, funcName, descriptor) {

  71. let oriFunc = descriptor.value;

  72. descriptor.value = async function () {

  73. let errMsg = '';

  74. let res = '';

  75. try {

  76. res = await oriFunc.apply(this, arguments);

  77. if (res != 'ok')

  78. errMsg = typeof res === 'string' ? res : defaultMsg;

  79. } catch (e) {

  80. errMsg = defaultMsg;

  81. console.error('caught err with func:',funcName, e);

  82. }

  83.  

  84. if (errMsg) {

  85. this.$toast({

  86. title: errMsg,

  87. type: 'fail',

  88. duration: duration,

  89. });

  90. }

  91. return res;

  92. }

  93. }

  94. }

  95.  

  96. /**

  97. * 分页加载

  98. * @param {[Boolean]} [是否加载为空显示toast]

  99. * @return {[Function]} [decrotor]

  100. */

  101. export function mixinList ({needToast = false}) {

  102. let oldList = [],

  103. pageNum = 1,

  104. /**

  105. * state [string]

  106. *   hasmore  [还有更多]

  107. *   nomore   [没有更多了]

  108. */

  109. state = 'hasmore',

  110. current = [];

  111. return function (target,name,descriptor) {

  112. const oldFunc  = descriptor.value,

  113. symbol   = Symbol('freeze');

  114. target[symbol] = false;

  115. /**

  116. * [description]

  117. * @param  {[Object]}   params={}       [请求参数]

  118. * @param  {[Number]}   startNum=null   [手动重置加载页数]

  119. * @param  {[Boolean]}  clearlist=false [是否清空数组]

  120. * @return {[Object]}   [{所有加载页数组集合,加载完成状态}]

  121. */

  122. descriptor.value = async function(params={},startNum=null,clearlist=false) {

  123. try {

  124. if (target[symbol]) return;

  125. // 函数执行前赋值操作

  126. target[symbol] = true;

  127. params.data.pageNum = pageNum;

  128. if (startNum !== null && typeof startNum === 'number') {

  129. params.data.pageNum = startNum;

  130. pageNum = startNum;

  131. }

  132. if (clearlist) oldList = [];

  133. // 释放函数,取回list

  134. let before = current;

  135. current = await oldFunc.call(this,params);

  136. // 函数执行结束赋值操作

  137. (state === 'hasmore' || clearlist) && oldList.push(...current);

  138. if ((current.length === 0) || (params.data.pageSize > current.length)) {

  139. needToast && this.$toast({title: '没有更多了',type: 'fail'});

  140. state = 'nomore';

  141. } else {

  142. state = 'hasmore';

  143. pageNum ;

  144. }

  145. target[symbol] = false;

  146. this.$apply();

  147. return { list : oldList,state };

  148. } catch(e) {

  149. console.error('fail code at: ' e)

  150. }

  151. }

  152. }

  153. }

  154.  

  155. /**

  156. * 检测工具

  157. */

  158. const _toString = Object.prototype.toString;

  159. // 检测是否为纯粹的对象

  160. const _isPlainObject = function  (obj) {

  161. return _toString.call(obj) === '[object Object]'

  162. }

  163. // 检测是否为正则

  164. const _isRegExp = function  (v) {

  165. return _toString.call(v) === '[object RegExp]'

  166. }

  167. /**

  168. * @description 检测函数

  169. *  用于检测类型action

  170. * @param {Array} checked 被检测数组

  171. * @param {Array} checker 检测数组

  172. * @return {Boolean} 是否通过检测

  173. */

  174. const _check = function (checked,checker) {

  175. check:

  176. for(let i = 0; i < checked.length; i ) {

  177. if(/(any)/ig.test(checker[i]))

  178. continue check;

  179. if(_isPlainObject(checked[i]) && /(object)/ig.test(checker[i]))

  180. continue check;

  181. if(_isRegExp(checked[i]) && /(regexp)/ig.test(checker[i]))

  182. continue check;

  183. if(Array.isArray(checked[i]) && /(array)/ig.test(checker[i]))

  184. continue check;

  185. let type = typeof checked[i];

  186. let checkReg = new RegExp(type,'ig')

  187. if(!checkReg.test(checker[i])) {

  188. console.error(checked[i] 'is not a ' checker[i]);

  189. return false;

  190. }

  191. }

  192. return true;

  193. }

  194. /**

  195. * @description 检测类型

  196. *   1.用于校检函数参数的类型,如果类型错误,会打印错误并不再执行该函数;

  197. *   2.类型检测忽略大小写,如string和String都可以识别为字符串类型;

  198. *   3.增加any类型,表示任何类型均可检测通过;

  199. *   4.可检测多个类型,如 "number array",两者均可检测通过。正则检测忽略连接符 ;

  200. */

  201. export function typeCheck() {

  202. const checker =  Array.prototype.slice.apply(arguments);

  203. return function (target, funcName, descriptor) {

  204. let oriFunc = descriptor.value;

  205. descriptor.value =  function () {

  206. let checked =  Array.prototype.slice.apply(arguments);

  207. let result = undefined;

  208. if(_check(checked,checker) ){

  209. result = oriFunc.call(this,...arguments);

  210. }

  211. return result;

  212. }

  213. }

  214. };

  215.  

  216. const errorLog = (text) => {

  217. console.error(text);

  218. return true;

  219. }

  220. /**

  221. * @description 全埋点

  222. *  1.在所有methods方法中埋点为函数名

  223. *  2.在钩子函数中'beforeCreate','created','beforeMount','mounted','beforeUpdate','activated','deactivated'依次寻找这些钩子

  224. *    如果存在就会增加埋点 VIEW

  225. *

  226. * 用法:

  227. *   @Buried

  228. *   在单文件导出对象一级子对象下;

  229. *   如果某方法不想设置埋点 可以 return 'noBuried' 即可

  230. */

  231. export function Buried(target, funcName, descriptor) {

  232. let oriMethods = Object.assign({},target.methods),

  233. oriTarget = Object.assign({},target);

  234. // methods方法中

  235. if(target.methods) {

  236. for(let name in target.methods) {

  237. target.methods[name] = function () {

  238. let result = oriMethods[name].call(this,...arguments);

  239. // 如果方法中返回 noBuried 则不添加埋点

  240. if(typeof result === 'string' && result.includes('noBuried')) {

  241. console.log(name '方法设置不添加埋点');

  242. } else if(result instanceof Promise) {

  243. result.then(res => {

  244. if(typeof res === 'string' && res.includes('noBuried')) { console.log(name '方法设置不添加埋点'); return; };

  245. console.log('添加埋点在methods方法中:' , name.toUpperCase ());

  246. this.$log(name);

  247. });

  248. }else{

  249. console.log('添加埋点在methods方法中:' , name.toUpperCase ());

  250. this.$log(name);

  251. };

  252. return result;

  253. }

  254. }

  255. }

  256. // 钩子函数中

  257. const hookFun = (hookName) => {

  258. target[hookName] = function() {

  259. let result =  oriTarget[hookName].call(this,...arguments);

  260. console.log('添加埋点,在钩子函数' hookName '中:', 'VIEW');

  261. this.$log('VIEW');

  262. return result;

  263. }

  264. }

  265.  

  266. const LIFECYCLE_HOOKS = [

  267. 'beforeCreate',

  268. 'created',

  269. 'beforeMount',

  270. 'mounted',

  271. 'beforeUpdate',

  272. 'activated',

  273. 'deactivated',

  274. ];

  275.  

  276. for(let item of LIFECYCLE_HOOKS) {

  277. if (target[item]) return hookFun(item);

  278. }

  279. }

 

 

def decorator(f):
  print "my decorator"
  return f
@decorator
def myfunc():
  print "my function"
myfunc()
# my decorator
# my function

这里的 @decorator 就是我们说的装饰器。在上面的代码中,我们利用装饰器给我们的目标方法执行前打印出了一行文本,并且并没有对原方法做任何的修改。代码基本等同于:

def decorator(f):
  def wrapper():
    print "my decorator"
    return f()
  return wrapper
def myfunc():
  print "my function"
myfunc = decorator(myfuc)

通过代码我们也不难看出,装饰器 decorator 接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容以后再返回一个方法,供以后调用,同时也失去了对原方法对象的访问。当我们对某个应用了装饰以后,其实就改变了被装饰方法的入口引用,使其重新指向了装饰器返回的方法的入口点,从而来实现我们对原函数的扩展、修改等操作。

ES7 的装饰器

ES7 中的 decorator 同样借鉴了这个语法糖,不过依赖于 ES5 的 Object.defineProperty 方法 。

Object.defineProperty

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for...in 或 Object.keys 方法), 这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用 Object.defineProperty() 添加的属性值是不可变的。

语法

Object.defineProperty(obj, prop, descriptor)
  1. obj:要在其上定义属性的对象。
  2. prop:要定义或修改的属性的名称。
  3. descriptor:将被定义或修改的属性描述符。
  4. 返回值:被传递给函数的对象。

在ES6中,由于 Symbol类型 的特殊性,用 Symbol类型 的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定义 key为 Symbol 的属性的方法之一。

属性描述符vnsc5858威尼斯城官网,

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。

数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。

  • 存取描述符是由 getter-setter 函数对描述的属性。
  • 描述符必须是这两种形式之一;不能同时是两者。

数据描述符和存取描述符均具有以下可选键值:

configurable

当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。

enumerable

enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。

当且仅当该属性的 enumerable 为 true 时,该属性才能够出现在对象的枚举属性中。默认为 false。
数据描述符同时具有以下可选键值:

value

该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。

writable

当且仅当该属性的 writable 为 true 时,value 才能被赋值运算符改变。默认为 false。

存取描述符同时具有以下可选键值:

get

一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。

set

一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。

如果一个描述符不具有value,writable,get 和 set 任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
用法

类的装饰

@testable
class MyTestableClass {
 // ...
}

function testable(target) {
 target.isTestable = true;
}

MyTestableClass.isTestable // true

上面代码中,@testable 就是一个装饰器。它修改了 MyTestableClass这 个类的行为,为它加上了静态属性isTestable。testable 函数的参数 target 是 MyTestableClass 类本身。

基本上,装饰器的行为就是下面这样。

@decorator
class A {}

// 等同于

class A {}
A = decorator(A) || A;

也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。

如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。

function testable(isTestable) {
 return function(target) {
  target.isTestable = isTestable;
 }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

上面代码中,装饰器 testable 可以接受参数,这就等于可以修改装饰器的行为。

注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

编辑:计算机教程 本文来源:es7你都懂了吗?今天带你了解es7的神器decoratorv

关键词: