Es7 brings more powerful methods such as async/await, decorators, etc. We are already familiar with async/await. Let’s talk about decorators.

What is a decorator?

Officially, a Decorator function that modifies the behavior of a class. This may not be easy for beginners to understand, but it means that we can use modifiers to modify the properties and methods of a class, for example, we can change the behavior of a function before it is executed. Because decorators are executed at compile time, it is possible to annotate and modify classes, attributes, and so on at design time. Decorators can be used not only on classes but also on objects, but decorators cannot modify functions because of variable promotion. A decorator is equivalent to wrapping a layer of behavior around a function inside an object. The decorator itself is a function that takes three parameters target (the target class to be decorated), name (the property name to be decorated), and descriptor (the property description object). We will show you the power of decorators later.

Are large frameworks using decorators?
  • TypeScript Annotate in Angular2 is another implementation of annotating decorators.

  • Redux2 in React also started doing a lot of refactoring using ES7 Decorators.

  • Vue If you use typescript, you’ll notice that Vue components are starting to use Decorators, and even Vuex is refactoring entirely with Decorators.

Let’s take a simple example of readOnly:

This is a Dog class


     
  1. class Dog {

  2.  bark () {

  3. Return 'Woof woof!! '

  4.  }

  5. }

Copy the code

Let’s add the @readonly modifier to it


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

  2. class Dog {

  3.  @readonly

  4.  bark () {

  5. Return 'Woof woof!! '

  6.  }

  7. }

  8. let dog = new Dog()

  9. Bark = 'wangwang!! ';

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

  11. // the readonly modifier changes the bark method of the Dog class to readonly

Copy the code

Let’s see how readOnly is implemented. The code is very simple. Okay


     
  1. / * *

  2. * @param target Target class Dog

  3. * @param name Specifies the name of the attribute to be modified, bark

  4. * @param descriptor Description object bark method of this property

  5. * /

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

  7. The original value of the // Descriptor object is as follows

  8. / / {

  9.  //   value: specifiedFunction,

  10.  //   enumerable: false,

  11.  //   configurable: true,

  12.  //   writable: true

  13.  // };

  14.  descriptor.writable = false;

  15.  return descriptor

  16. }

Copy the code

Readonly takes three parameters: target is the target class Dog, the second is the name of the property to be modified bark, which is a string, and the third is the description object of the property, the bark method. Here we modify the bark method to readonly with the readonly method. So when you modify the Bark method you get an error.

Decorator The useful decorator library core-decorators.js

npm install core-decorators –save


     
  1. // Marks an attribute or method as unwritable.

  2. @readonly  

  3. // Mark an attribute or method so that it cannot be deleted; It also prevents it from being reconfigured via Object.defineProperty

  4. @nonconfigurable  

  5. // Immediately apply the provided functions and arguments to the method, allowing you to wrap the method using any helper provided by LoDash. The first argument is the function to apply, and all other arguments are passed to the decorator function.

  6. @decorate  

  7. // If you don't have decorator language support like Babel 6, or even vanilla ES5 code with a compiler, you can use applyDecorators ().

  8. @extendDescriptor

  9. // Mark the property as not enumerable.

  10. @nonenumerable

  11. // Prevents property initializers from running until the modified property is actually found.

  12. @lazyInitialize

  13. // Forces calling this function to always reference this to the class instance, even if the function is passed or will lose its context.

  14. @autobind

  15. // Call console.warn () with deprecated messages. Provides a custom message to override the default message.

  16. @deprecate

  17. // Disallow any JavaScript console.warn () calls when invoking decorator functions.

  18. @suppressWarnings

  19. // Mark the property as enumerable.

  20. @enumerable

  21. // Check that the marked method does indeed override functions with the same signature on the prototype chain.

  22. @override  

  23. // Use console.time and console.timeEnd to provide a unique label for function timing, prefixed by classname.method by default.

  24. @time

  25. // Use console.profile and console.profileEnd to provide function analysis with a unique label with a default prefix of classname.method.

  26. @profile

Copy the code

There are many here but more about, know more about https://github.com/jayphelps/core-decorators

Here are some useful decorator method libraries that our team has written

Authors: Wu Peng and Luo Xue

  • NoConcurrent avoids concurrent calls and does not respond to repeated operations until the results of the previous operation are returned


     
  1. import {noConcurrent} from './decorators';

  2. methods: {

  3. @noconcurrent // Avoid concurrency, click submit and ignore subsequent clicks until the interface returns

  4.  async onSubmit(){

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

  6. / /...

  7.    return;

  8.  }

  9. }

Copy the code
  • MakeMutex multiple functions are mutually exclusive. Functions with the same mutex identifier are not executed concurrently


     
  1. import {makeMutex} from './decorators';

  2. let globalStore = {};

  3. class Navigator {

  4. @makemutex ({namespace:globalStore, mutexId:'navigate'}) // Avoid concurrent execution of jump related functions

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

  6. @makemutex ({namespace:globalStore, mutexId:'navigate'}) // Avoid concurrent execution of jump related functions

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

  8. }

Copy the code
  • WithErrToast catches exceptions in async functions and prompts for errors


     
  1. methods: {

  2. @witherrtoast ({defaultMsg: 'network error ', duration: 2000})

  3.  async pullData(){

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

  5. / /...

  6. Return 'other reasons '; // Toast suggests other reasons

  7. // return 'ok'; // Normal no prompt

  8.  }

  9. }

Copy the code
  • MixinList is used for pagination loading, which returns spliced data and whether there is more data during pull-up loading


     
  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 = 'nomore';

  10.    this.goods = result.list;

  11.  }

  12. }

  13. // The goods array gets all the concatenated data by calling hasMore

  14. Params function takes arguments,startNum to start the page number, and clearList to empty the array

  15. // mixinList can pass a needToast argument. No data needs toast prompt

Copy the code

TypeCheck Indicates the check function parameter type


     
  1. methods: {

  2.  @typeCheck('number')

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

  4. }

  5. // if the btnClick parameter index is not number, an error is reported

Copy the code

For processing solutions of Buried points, count the page display volume and click volume of all methods. If a method does not want to set a Buried point, you can return ‘noBuried’


     
  1. @Buried

  2. methods: {

  3.  btn1Click() {

  4. // The embedded point is btn1Click

  5.  },

  6.  btn2Click() {

  7. return 'noBuried'; / / there is no point

  8.  },

  9. },

  10. created() {

  11. // The embedded point is view

  12. }

  13. // Count page views and all methods clicks

Copy the code

decorators.js


     
  1. / * *

  2. * Avoid concurrent calls and do not respond to repeated operations until the result of the previous operation is returned

  3. * For example, a user clicks the same submit button several times in a row and wants to respond only once, rather than submit multiple forms simultaneously

  4. * note:

  5. * Synchronous functions Do not need this decorator because of the single-threaded nature of JS, which has no concurrency issues

  6. * Asynchronous timing, this decorator only supports decorating async functions for the convenience of distinguishing when an operation ends

  7. * /

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

  9. / * *

  10. * Avoid concurrent calls to the decorator template

  11. * @param {Object} Namespace A global variable shared between mutex functions. It is used to store concurrent information. This variable is required when multiple functions are mutually exclusive. The single function itself does not need to provide concurrent, to the local private variable implementation

  12. * @param {string} mutexStore occupies a variable name in a namespace for state storage

  13. * @param {string} mutexId Specifies a mutually exclusive identifier. Functions with the same identifier will not be executed concurrently. Default value: function name

  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]) // If the last operation is not complete, ignore this call

  25.      return;

  26. namespace[mutexStore][mutexId] = true; // Start operation

  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. }); // No further action is required

  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. * Multiple functions are mutually exclusive. Functions with the same mutex identifier will not be executed concurrently

  44. * @param namespace A global variable shared between mutex functions to store concurrent information

  45. * @param mutexId mutually exclusive identifier. Functions with the same identifier will not be executed concurrently

  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. * Catch exceptions in async functions and give error messages

  57. * When the function normally ends, return 'OK'. When return other documents, toast specified document. If no value is returned or an exception is generated, toast default document

  58. * @param {string} defaultMsg default copy

  59. * @param {number, optional} duration Toast duration

  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. * Paging load

  88. * @param {[Boolean]} [whether to load empty toast]

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

  90. * /

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

  92.  let oldList = [],

  93.      pageNum = 1,

  94. / * *

  95.  * state [string]

  96. * Hasmore [and more]

  97. * nomore [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. // The function performs the pre-assignment

  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. // Get the list

  124.        let before = current;

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

  126. // The function performs the end assignment

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

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

  129. NeedToast && this.$toast({title: 'no more ',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. * Testing tools

  146. * /

  147. const _toString = Object.prototype.toString;

  148. // Check whether the object is pure

  149. const _isPlainObject = function  (obj) {

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

  151. }

  152. // Check if it is regular

  153. const _isRegExp = function  (v) {

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

  155. }

  156. / * *

  157. * @description check function

  158. * Used to detect type action

  159. * @param {Array} checked Array

  160. * @param {Array} checker checks arrays

  161. * @return {Boolean} Whether the test passes

  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 Indicates the detection type

  185. * 1. The type of the parameter used to check the function. If the type is wrong, an error will be printed and the function will not be executed.

  186. * 2. Type detection ignores case, for example, string and string can be recognized as string types;

  187. * 3. Add any type, indicating that any type can be detected;

  188. * 4. Multiple types can be detected, such as "number array", both can be detected. Regular detection ignores the connecter;

  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 full buried point

  210. * 1. Embedded points in all methods are function names

  211. * 2. In the hook functions 'beforeCreate','created','beforeMount',' Mounted ','beforeUpdate','activated','deactivated' find these hooks in order

  212. * Add buried VIEW if it exists

  213. *

  214. * usage:

  215. *   @Buried

  216. * Under a single file export object level child object;

  217. * Return 'noBuried' if a method doesn't want to set a buried point

  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. // if noBuried is returned in the method, noBuried is added

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

  229. Console. log(name + 'method set not to add buried point ');

  230.        } else if(result instanceof Promise) {

  231.          result.then(res => {

  232. If (typeof res === 'string' && res.includes('noBuried')) {console.log(name + 'method set not to add buried point '); return; };

  233. Console. log(' add buried point to methods: ', name.toupperCase ());

  234.            this.$log(name);

  235.          });

  236.        }else{

  237. Console. log(' add buried point to methods: ', name.toupperCase ());

  238.          this.$log(name);

  239.        };

  240.        return result;

  241.      }

  242.    }

  243.  }

  244. // in the hook function

  245.  const hookFun = (hookName) => {

  246.    target[hookName] = function() {

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

  248. Console. log(' add buried point, in hook function '+ 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. }

Copy the code