Preface
Original Link: Nealyang/personalBlog
ES6 doesn't have to be introduced much anymore. Before ES6, decorators probably weren't that important because you just needed to add a wrapper layer, but now, with the advent of grammatical sugar classes, the code gets complicated and difficult when we want to share or extend methods between classesTo maintain, and that's where our Decorator comes in.
Object.defineProperty
Simply stated about Object.defineProperty is that it accurately adds and modifies properties of objects
grammar
Object.defineProperty(obj,prop,descriptor)
- ojb: Object on which to define properties
- prop: Name of the property to be defined or modified
- Descriptor: Attribute descriptor to be defined or modified
This method returns the object passed to the function
In ES6, because of the special nature of the Symbol type, using a value of the Symbol type to make the key of an object is different from the general definition or modification, and Object.defineProperty is one of the ways to define a key as a property of the Symbol.
Common attributes added by assignment operations are enumerable and can be presented during the enumeration of attributes (for...in or Object.keys methods), and their values can be changed or deleted.This method allows you to modify the default additional options (or configurations).By default, attribute values added using Object.defineProperty() are not modifiable
Generic Descriptor
There are two main forms of attribute descriptors currently present in objects: data descriptors and access descriptors.A data descriptor is an attribute with a value that may or may not be writable.Access descriptors are properties described by getter-setter functions.Descriptors must be one of these two forms; they cannot be both.
Both data descriptors and access descriptors have the following optional key values:
configurable
The property descriptor can only be changed if and only if the property's configurable is true, and the property can also be deleted from the corresponding object.Default to false
enumerable
This property can only appear in an object's enumeration property if and only if its enumerable is true.Default to false.
Data descriptors also have the following optional key values:
value
The value corresponding to this property.It can be any valid JavaScript value (number, object, function, etc.).Default is undefined.
writable
value can only be changed by the assignment operator if and only if the property's writable is true.Default to false
Access descriptors also have the following optional key values:
get
A method that provides a getter to an attribute, or undefined if no getter exists.When the property is accessed, the method is executed, and no parameters are passed in when the method is executed, but a this object is passed in (where this is not necessarily the object defining the property due to inheritance).Default is undefined.
set
A method that provides a setter to an attribute, or undefined if no setter exists.This method is triggered when the attribute value is modified.The method will accept the unique parameter, which is the new parameter value for the attribute.Default is undefined.
If a descriptor does not have any of the keywords value,writable,get, and set, it will be considered a data descriptor.If a descriptor has both (value or writable) and (get or set) keywords, an exception will be raised
For more usage examples and descriptions, see: MDN
Decorator Mode
Before we look at Decorator, let's look at the use of Decorator mode. We all know that Decorator mode can add blame to an object while the program is running, without changing the object itself.The feature is to add additional functions of responsibility without affecting the characteristics of previous objects.
like...this:
This is a relatively simple passage, just look at the code:
let Monkey = function () {} Monkey.prototype.say = function () { console.log('I'm just a wild monkey right now'); } let TensionMonkey = function (monkey) { this.monkey = monkey; } TensionMonkey.prototype.say = function () { this.monkey.say(); console.log('With the hoop curse, I'll forget the world's troubles!'); } let monkey = new TensionMonkey(new Monkey()); monkey.say();
Execution results:
Decorator
Decorator is actually a grammatical sugar. Behind it is the use of es5's Object.defineProperty(target,name,descriptor) to understand Object.defineProperty. Please move on This link: MDN Document
The general principle behind it is as follows:
class Monkey{ say(){ console.log('At the moment, I'm just a wild monkey'); } }
Execute the code above, roughly as follows:
Object.defineProperty(Monkey.prototype,'say',{ value:function(){console.log('At the moment, I'm just a wild monkey')}, enumerable:false, configurable:true, writable:true })
If we decorate him with ornaments
class Monkey{ @readonly say(){console.log('Now I am read-only')} }
The properties of this decorator execute the following code before Object.defineProperty registers the say property for Monkey.prototype:
let descriptor = { value:specifiedFunction, enumerable:false, configurable:true, writeable:true }; descriptor = readonly(Monkey.prototype,'say',descriptor)||descriptor; Object.defineProperty(Monkey.prototype,'say',descriptor);
From the pseudo code above, we can see that Decorator just executed a decorative function that belongs to a class intercepting Object.defineProperty before Object.defineProperty registered the property for Monkey.prototype.So it has the same formal parameters as Object.defineProperty:
- obj: the target object of the action
- prop:Property name of the role
- descriptor: descriptor for this property
Here's a look at simple use
Use in class
- Create a new class that inherits from the original class and add attributes
@name class Person{ sayHello(){ console.log(`hello ,my name is ${this.name}`) } } function name(constructor) { return class extends constructor{ name="Nealyang" } } new Person().sayHello() //hello ,my name is Nealyang
- Modify for current class (similar to mixin)
@name @seal class Person { sayHello() { console.log(`hello ,my name is ${this.name}`) } } function name(constructor) { Object.defineProperty(constructor.prototype,'name',{ value:'One Side' }) } new Person().sayHello() //If you modify an attribute function seal(constructor) { let descriptor = Object.getOwnPropertyDescriptor(constructor.prototype, 'sayHello') Object.defineProperty(constructor.prototype, 'sayHello', { ...descriptor, writable: false }) } new Person().sayHello = 1;// Cannot assign to read only property 'sayHello' of object '#<Person>'
When it comes to mixins, let me simulate a mixin
class A { run() { console.log('I can run!') } } class B { jump() { console.log('I can jump!') } } @mixin(A, B) class C {} function mixin(...args) { return function (constructor) { for (const arg of args) { for (let key of Object.getOwnPropertyNames(arg.prototype)) { if (key === 'constructor') continue; Object.defineProperty(constructor.prototype, key, Object.getOwnPropertyDescriptor(arg.prototype, key)); } } } } let c = new C(); c.jump(); c.run(); // I can jump! // I can run!
So far we seem to have written a lot of code, right.This one, for the sake of investing in Decorator completely, here...Just start.
Use in class members
This type of decorator should be written in the way we know it best, accepting three parameters:
- If the decorator is mounted on a static member, the constructor is returned, and if it is mounted on an instance member, the prototype of the class is returned
- Member name mounted by decorator
- Return value of Object.getOwnPropertyDescriptor
First, let's clarify the difference between static and instance members
class Model{ //Instance members method1(){} method2 = ()=>{} // Static members static method3(){} static method4 = ()=>{} }
Method1 and method2 are instance members, but method1 exists on the prototype, and method2 only exists after the object is instantiated.
method3 and method4 are static members. The difference between them is whether descriptor settings can be enumerated. We can see through babel transcoding:
The above code is messy and can be easily understood as:
function Model () { // Members are only assigned when instantiated this.method2 = function () {} } // Members are defined on the prototype chain Object.defineProperty(Model.prototype, 'method1', { value: function () {}, writable: true, enumerable: false, // Settings cannot be enumerated configurable: true }) // Members are defined on the constructor and are default enumerable Model.method4 = function () {} // Members are defined on constructors Object.defineProperty(Model, 'method3', { value: function () {}, writable: true, enumerable: false, // Settings cannot be enumerated configurable: true })
You can see that only method2 is assigned when instantiated, and a nonexistent attribute does not have a descriptor, so that's why the third parameter is not passed for Property Decorator. There is no reasonable explanation as to why static members do not pass descriptors, butIf it is explicitly intended to be used, it can be obtained manually.
As in the example above, once we've added decorators for all four members, the first parameter for method1 and method2 is Model.prototype, while the first parameter for method3 and method4 is Model.
class Model { // Instance members @instance method1 () {} @instance method2 = () => {} // Static members @static static method3 () {} @static static method4 = () => {} } function instance(target) { console.log(target.constructor === Model) } function static(target) { console.log(target === Model) }
Use of function, accessor, property decorators
- The return value of the function decorator defaults to the presence of the value descriptor as an attribute and is ignored if returned to undefined
class Model { @log1 getData1() {} @log2 getData2() {} } // Scenario one, returning a new value descriptor function log1(tag, name, descriptor) { return { ...descriptor, value(...args) { let start = new Date().valueOf() try { return descriptor.value.apply(this, args) } finally { let end = new Date().valueOf() console.log(`start: ${start} end: ${end} consume: ${end - start}`) } } } } // Option 2. Modify existing descriptors function log2(tag, name, descriptor) { let func = descriptor.value // Get the previous function first // Modify the corresponding value descriptor.value = function (...args) { let start = new Date().valueOf() try { return func.apply(this, args) } finally { let end = new Date().valueOf() console.log(`start: ${start} end: ${end} consume: ${end - start}`) } } }
- The accessor's Decorator is the get set prefix function, which controls the assignment and evaluation of attributes. It is no different from the function decorator in use.
class Modal { _name = 'Niko' @prefix get name() { return this._name } } function prefix(target, name, descriptor) { return { ...descriptor, get () { return `wrap_${this._name}` } } } console.log(new Modal().name) // wrap_Niko
- There is no descriptor return for an attribute decorator, and the return value of the decorator function is ignored. If we need to modify a static attribute, we need to get the descriptor ourselves
class Modal { @prefix static name1 = 'Niko' } function prefix(target, name) { let descriptor = Object.getOwnPropertyDescriptor(target, name) Object.defineProperty(target, name, { ...descriptor, value: `wrap_${descriptor.value}` }) } console.log(Modal.name1) // wrap_Niko
There are no directly modified schemes for the properties of an instance, but we can combine some other ornaments to save the nation.
For example, we have a class that will pass in name and age as initialization parameters, and then we will set the corresponding formatting checks for these two parameters
const validateConf = {} // Store verification information @validator class Person { @validate('string') name @validate('number') age constructor(name, age) { this.name = name this.age = age } } function validator(constructor) { return class extends constructor { constructor(...args) { super(...args) // Traverse all the check information for validation for (let [key, type] of Object.entries(validateConf)) { if (typeof this[key] !== type) throw new Error(`${key} must be ${type}`) } } } } function validate(type) { return function (target, name, descriptor) { // Pass in the property name and type to be checked to the global object validateConf[name] = type } } new Person('Niko', '18') // throw new error: [age must be number]
Function parameter decorator
const parseConf = {} class Modal { @parseFunc addOne(@parse('number') num) { return num + 1 } } // Perform formatting operations before function calls function parseFunc (target, name, descriptor) { return { ...descriptor, value (...arg) { // Get Formatting Configuration for (let [index, type] of parseConf) { switch (type) { case 'number': arg[index] = Number(arg[index]) break case 'string': arg[index] = String(arg[index]) break case 'boolean': arg[index] = String(arg[index]) === 'true' break } return descriptor.value.apply(this, arg) } } } } // Add corresponding formatting information to global objects function parse(type) { return function (target, name, index) { parseConf[index] = type } } console.log(new Modal().addOne('10')) // 11
Decorator use case
log
Add the log function to a method and check the input parameters
let log = type => { return (target,name,decorator) => { const method = decorator.value; console.log(method); decorator.value = (...args) => { console.info(`${type} Underway: ${name}(${args}) = ?`); let result; try{ result = method.apply(target,args); console.info(`(${type}) Success : ${name}(${args}) => ${result}`); }catch(err){ console.error(`(${type}) fail: ${name}(${args}) => ${err}`); } return result; } } } class Math { @log('add') add(a, b) { return a + b; } } const math = new Math(); // (add) success: add (2,4) => 6 math.add(2, 4);
time
Time for statistical method execution:
function time(prefix) { let count = 0; return function handleDescriptor(target, key, descriptor) { const fn = descriptor.value; if (prefix == null) { prefix = `${target.constructor.name}.${key}`; } if (typeof fn !== 'function') { throw new SyntaxError(`@time can only be used on functions, not: ${fn}`); } return { ...descriptor, value() { const label = `${prefix}-${count}`; count++; console.time(label); try { return fn.apply(this, arguments); } finally { console.timeEnd(label); } } } } }
debounce
Anti-shake the method being executed
class Toggle extends React.Component { @debounce(500, true) handleClick() { console.log('toggle') } render() { return ( <button onClick={this.handleClick}> button </button> ); } } function _debounce(func, wait, immediate) { var timeout; return function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } } } function debounce(wait, immediate) { return function handleDescriptor(target, key, descriptor) { const callback = descriptor.value; if (typeof callback !== 'function') { throw new SyntaxError('Only functions can be debounced'); } var fn = _debounce(callback, wait, immediate) return { ...descriptor, value() { fn() } }; } }
More examples of core-decorators come later
Nealyang/PersonalBlog In addition to the notes.
Reference resources
Exchange of learning
Focus on Public Number: [Selected Front End of Full Stack] Get good text recommendations every day.
Reply [1] from the public number, join the front-end learning group of the whole stack and communicate with each other.