zoukankan      html  css  js  c++  java
  • 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类

    1. class Dog {

    2.  bark () {

    3.    return '汪汪汪!!'

    4.  }

    5. }

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

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

    2. class Dog {

    3.  @readonly

    4.  bark () {

    5.    return '汪汪汪!!'

    6.  }

    7. }

    8. let dog = new Dog()

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

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

    11. // 这里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方法的时候就是报错了。

    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

    下面给大家介绍一些我们团队写的一些很实用的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.  @makeMutex({namespace:globalStore, mutexId:'navigate'}) //避免跳转相关函数并发执行

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

    8. }

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

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

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

    14. * @param target

    15. * @param funcName

    16. * @param descriptor

    17. * @private

    18. */

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

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

    21.  mutexId = mutexId || funcName;

    22.  let oriFunc = descriptor.value;

    23.  descriptor.value = function () {

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

    25.      return;

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

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

    28.    if (res instanceof Promise)

    29.      res.then(()=> {

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

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

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

    33.        console.error(funcName, e);

    34.      }); //操作结束

    35.    else {

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

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

    38.    }

    39.    return res;

    40.  }

    41. }

    42. /**

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

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

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

    46. * @return {*}

    47. */

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

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

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

    51.    return function () {}

    52.  }

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

    54. }

    55. /**

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

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

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

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

    60. */

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

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

    63.    let oriFunc = descriptor.value;

    64.    descriptor.value = async function () {

    65.      let errMsg = '';

    66.      let res = '';

    67.      try {

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

    69.        if (res != 'ok')

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

    71.      } catch (e) {

    72.        errMsg = defaultMsg;

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

    74.      }

    75.      if (errMsg) {

    76.        this.$toast({

    77.          title: errMsg,

    78.          type: 'fail',

    79.          duration: duration,

    80.        });

    81.      }

    82.      return res;

    83.    }

    84.  }

    85. }

    86. /**

    87. * 分页加载

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

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

    90. */

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

    92.  let oldList = [],

    93.      pageNum = 1,

    94.  /**

    95.  * state [string]

    96.  *   hasmore  [还有更多]

    97.  *   nomore   [没有更多了]

    98.  */

    99.  state = 'hasmore',

    100.  current = [];

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

    102.    const oldFunc  = descriptor.value,

    103.          symbol   = Symbol('freeze');

    104.    target[symbol] = false;

    105.    /**

    106.     * [description]

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

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

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

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

    111.     */

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

    113.      try {

    114.        if (target[symbol]) return;

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

    116.        target[symbol] = true;

    117.        params.data.pageNum = pageNum;

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

    119.          params.data.pageNum = startNum;

    120.          pageNum = startNum;

    121.        }

    122.        if (clearlist) oldList = [];

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

    124.        let before = current;

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

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

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

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

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

    130.          state = 'nomore';

    131.        } else {

    132.          state = 'hasmore';

    133.          pageNum++;

    134.        }

    135.        target[symbol] = false;

    136.        this.$apply();

    137.        return { list : oldList,state };

    138.      } catch(e) {

    139.        console.error('fail code at: ' + e)

    140.      }

    141.    }

    142.  }

    143. }

    144. /**

    145. * 检测工具

    146. */

    147. const _toString = Object.prototype.toString;

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

    149. const _isPlainObject = function  (obj) {

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

    151. }

    152. // 检测是否为正则

    153. const _isRegExp = function  (v) {

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

    155. }

    156. /**

    157. * @description 检测函数

    158. *  用于检测类型action

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

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

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

    162. */

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

    164.  check:

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

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

    167.      continue check;

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

    169.      continue check;

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

    171.      continue check;

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

    173.      continue check;

    174.    let type = typeof checked[i];

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

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

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

    178.      return false;

    179.    }

    180.  }

    181.  return true;

    182. }

    183. /**

    184. * @description 检测类型

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

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

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

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

    189. */

    190. export function typeCheck() {

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

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

    193.    let oriFunc = descriptor.value;

    194.    descriptor.value =  function () {

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

    196.      let result = undefined;

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

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

    199.      }

    200.      return result;

    201.    }

    202.  }

    203. };

    204. const errorLog = (text) => {

    205.  console.error(text);

    206.  return true;

    207. }

    208. /**

    209. * @description 全埋点

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

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

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

    213. *

    214. * 用法:

    215. *   @Buried

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

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

    218. */

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

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

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

    222.  // methods方法中

    223.  if(target.methods) {

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

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

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

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

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

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

    230.        } else if(result instanceof Promise) {

    231.          result.then(res => {

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

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

    234.            this.$log(name);

    235.          });

    236.        }else{

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

    238.          this.$log(name);

    239.        };

    240.        return result;

    241.      }

    242.    }

    243.  }

    244.  // 钩子函数中

    245.  const hookFun = (hookName) => {

    246.    target[hookName] = function() {

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

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

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

    250.      return result;

    251.    }

    252.  }

    253.  const LIFECYCLE_HOOKS = [

    254.    'beforeCreate',

    255.    'created',

    256.    'beforeMount',

    257.    'mounted',

    258.    'beforeUpdate',

    259.    'activated',

    260.    'deactivated',

    261.  ];

    262.  for(let item of LIFECYCLE_HOOKS) {

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

    264.  }

    265. }

     
  • 相关阅读:
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    Live2D 看板娘
    1236:区间合并
  • 原文地址:https://www.cnblogs.com/zhuanzhuanfe/p/9101922.html
Copyright © 2011-2022 走看看