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
class Dog {
bark () {
Return 'Woof woof!! '
}
}
Copy the code
Let’s add the @readonly modifier to it
import { readOnly } from "./decorators";
class Dog {
@readonly
bark () {
Return 'Woof woof!! '
}
}
let dog = new Dog()
Bark = 'wangwang!! ';
// Cannot assign to read only property 'bark' of [object Object]
// 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
/ * *
* @param target Target class Dog
* @param name Specifies the name of the attribute to be modified, bark
* @param descriptor Description object bark method of this property
* /
function readonly(target, name, descriptor) {
The original value of the // Descriptor object is as follows
/ / {
// value: specifiedFunction,
// enumerable: false,
// configurable: true,
// writable: true
// };
descriptor.writable = false;
return descriptor
}
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
// Marks an attribute or method as unwritable.
@readonly
// Mark an attribute or method so that it cannot be deleted; It also prevents it from being reconfigured via Object.defineProperty
@nonconfigurable
// 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.
@decorate
// If you don't have decorator language support like Babel 6, or even vanilla ES5 code with a compiler, you can use applyDecorators ().
@extendDescriptor
// Mark the property as not enumerable.
@nonenumerable
// Prevents property initializers from running until the modified property is actually found.
@lazyInitialize
// Forces calling this function to always reference this to the class instance, even if the function is passed or will lose its context.
@autobind
// Call console.warn () with deprecated messages. Provides a custom message to override the default message.
@deprecate
// Disallow any JavaScript console.warn () calls when invoking decorator functions.
@suppressWarnings
// Mark the property as enumerable.
@enumerable
// Check that the marked method does indeed override functions with the same signature on the prototype chain.
@override
// Use console.time and console.timeEnd to provide a unique label for function timing, prefixed by classname.method by default.
@time
// Use console.profile and console.profileEnd to provide function analysis with a unique label with a default prefix of classname.method.
@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
import {noConcurrent} from './decorators';
methods: {
@noconcurrent // Avoid concurrency, click submit and ignore subsequent clicks until the interface returns
async onSubmit(){
let submitRes = await this.$http({... });
/ /...
return;
}
}
Copy the code
-
MakeMutex multiple functions are mutually exclusive. Functions with the same mutex identifier are not executed concurrently
import {makeMutex} from './decorators';
let globalStore = {};
class Navigator {
@makemutex ({namespace:globalStore, mutexId:'navigate'}) // Avoid concurrent execution of jump related functions
static async navigateTo(route){... }
@makemutex ({namespace:globalStore, mutexId:'navigate'}) // Avoid concurrent execution of jump related functions
static async redirectTo(route){... }
}
Copy the code
-
WithErrToast catches exceptions in async functions and prompts for errors
methods: {
@witherrtoast ({defaultMsg: 'network error ', duration: 2000})
async pullData(){
let submitRes = await this.$http({... });
/ /...
Return 'other reasons '; // Toast suggests other reasons
// return 'ok'; // Normal no prompt
}
}
Copy the code
-
MixinList is used for pagination loading, which returns spliced data and whether there is more data during pull-up loading
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 = 'nomore';
this.goods = result.list;
}
}
// The goods array gets all the concatenated data by calling hasMore
Params function takes arguments,startNum to start the page number, and clearList to empty the array
// mixinList can pass a needToast argument. No data needs toast prompt
Copy the code
TypeCheck Indicates the check function parameter type
methods: {
@typeCheck('number')
btnClick(index){ ... },
}
// 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’
@Buried
methods: {
btn1Click() {
// The embedded point is btn1Click
},
btn2Click() {
return 'noBuried'; / / there is no point
},
},
created() {
// The embedded point is view
}
// Count page views and all methods clicks
Copy the code
decorators.js
/ * *
* Avoid concurrent calls and do not respond to repeated operations until the result of the previous operation is returned
* 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
* note:
* Synchronous functions Do not need this decorator because of the single-threaded nature of JS, which has no concurrency issues
* Asynchronous timing, this decorator only supports decorating async functions for the convenience of distinguishing when an operation ends
* /
export const noConcurrent = _noConcurrentTplt.bind(null,{mutexStore:'_noConCurrentLocks'});
/ * *
* Avoid concurrent calls to the decorator template
* @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
* @param {string} mutexStore occupies a variable name in a namespace for state storage
* @param {string} mutexId Specifies a mutually exclusive identifier. Functions with the same identifier will not be executed concurrently. Default value: function name
* @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]) // If the last operation is not complete, ignore this call
return;
namespace[mutexStore][mutexId] = true; // Start operation
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);
}); // No further action is required
else {
console.error('noConcurrent decorator shall be used with async function, yet got sync usage:', funcName);
namespace[mutexStore][mutexId] = false;
}
return res;
}
}
/ * *
* Multiple functions are mutually exclusive. Functions with the same mutex identifier will not be executed concurrently
* @param namespace A global variable shared between mutex functions to store concurrent information
* @param mutexId mutually exclusive identifier. Functions with the same identifier will not be executed concurrently
* @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})
}
/ * *
* Catch exceptions in async functions and give error messages
* 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
* @param {string} defaultMsg default copy
* @param {number, optional} duration Toast duration
* /
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;
}
}
}
/ * *
* Paging load
* @param {[Boolean]} [whether to load empty toast]
* @return {[Function]} [decrotor]
* /
export function mixinList ({needToast = false}) {
let oldList = [],
pageNum = 1,
/ * *
* state [string]
* Hasmore [and more]
* nomore [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;
// The function performs the pre-assignment
target[symbol] = true;
params.data.pageNum = pageNum;
if (startNum ! == null && typeof startNum === 'number') {
params.data.pageNum = startNum;
pageNum = startNum;
}
if (clearlist) oldList = [];
// Get the list
let before = current;
current = await oldFunc.call(this,params);
// The function performs the end assignment
(state === 'hasmore' || clearlist) && oldList.push(... current);
if ((current.length === 0) || (params.data.pageSize > current.length)) {
NeedToast && this.$toast({title: 'no more ',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)
}
}
}
}
/ * *
* Testing tools
* /
const _toString = Object.prototype.toString;
// Check whether the object is pure
const _isPlainObject = function (obj) {
return _toString.call(obj) === '[object Object]'
}
// Check if it is regular
const _isRegExp = function (v) {
return _toString.call(v) === '[object RegExp]'
}
/ * *
* @description check function
* Used to detect type action
* @param {Array} checked Array
* @param {Array} checker checks arrays
* @return {Boolean} Whether the test passes
* /
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 Indicates the detection type
* 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.
* 2. Type detection ignores case, for example, string and string can be recognized as string types;
* 3. Add any type, indicating that any type can be detected;
* 4. Multiple types can be detected, such as "number array", both can be detected. Regular detection ignores the connecter;
* /
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 full buried point
* 1. Embedded points in all methods are function names
* 2. In the hook functions 'beforeCreate','created','beforeMount',' Mounted ','beforeUpdate','activated','deactivated' find these hooks in order
* Add buried VIEW if it exists
*
* usage:
* @Buried
* Under a single file export object level child object;
* Return 'noBuried' if a method doesn't want to set a buried point
* /
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);
// if noBuried is returned in the method, noBuried is added
if(typeof result === 'string' && result.includes('noBuried')) {
Console. log(name + 'method set not to add buried point ');
} else if(result instanceof Promise) {
result.then(res => {
If (typeof res === 'string' && res.includes('noBuried')) {console.log(name + 'method set not to add buried point '); return; };
Console. log(' add buried point to methods: ', name.toupperCase ());
this.$log(name);
});
}else{
Console. log(' add buried point to methods: ', name.toupperCase ());
this.$log(name);
};
return result;
}
}
}
// in the hook function
const hookFun = (hookName) => {
target[hookName] = function() {
let result = oriTarget[hookName].call(this,... arguments);
Console. log(' add buried point, in hook function '+ 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);
}
}
Copy the code