In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces "how to understand and master JS decorator" related knowledge, in the actual case operation process, many people will encounter such a dilemma, then let the editor lead you to learn how to deal with these situations! I hope you can read it carefully and be able to achieve something!
1. Preface
The decorator is a proposal in the latest ECMA, a class-related syntax used to annotate or modify classes and class methods. Decorators are also widely used in languages such as Python and Java. Decorator is an important way to realize AOP (aspect-oriented) programming.
Here is a simple example of using a decorator, and this @ readonly can set the count property to read-only. As you can see, the decorator greatly improves the simplicity and readability of the code.
Class Person {@ readonly count = 0;}
Since the browser does not support the decorator, in order to allow you to see the effect normally, I have made a simple configuration using Parcel. You can go to the clone warehouse and then run all the examples involved in this article. Warehouse address: [learn es6] [https://github.com/yinguangyao/ES6]
This article involves the knowledge of [Object.defineProperty] [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty], higher-order functions, etc.). If you have not understood the relevant concepts before, it is recommended to understand them before reading this article.
two。 Decorator mode
Before we start talking about decorators, let's start with the classic decorator mode. The decorator pattern is a structural design pattern that allows you to add new functionality to an existing object without changing its structure as a wrapper for an existing class. Generally speaking, in code design, we should follow the principle of "use more combinations and use less inheritance". Add some additional responsibilities to an object dynamically through the decorator pattern. In terms of added functionality, the decorator pattern is more flexible than generating subclasses.
2.1 an example of League of Legends
When I went back from work to gang up with my friends happily, when I was using Yasso to "face the Wind", it suddenly occurred to me, if I were asked to design Yasso heroes, how should I achieve it?
After thinking about it, I will definitely design a hero class first.
Class Hero {attack () {}}
Then, implement a Yasuo class to inherit the Hero class.
Class Yasuo extends Hero {attack () {console.log ("steel flash");}}
While I was still thinking about this question, my teammates had already played Tai long, and the mark of Tai long buff appeared on my Yasso. It suddenly occurred to me, how to add Tai long buff to heroes? Then can't you add the attribute of Tai long buff? Of course not, you know, League of Legends inside the big dragon buff will increase revenue. Well, I've got an idea for the smart one. Wouldn't it be nice to inherit it again?
Class BaronYasuo extends Yasuo {}
Impressive, but what if there are other buff on Yasso? After all, there are red buff, blue buff, Tai long buff and so on in LOL. Is it not necessary to add as many classes as there are?
We can think about this in a different way, if we think of buff as our clothes. In different seasons, we will change into different clothes, and in winter, we will even stack multiple clothes. When buff disappears, it is tantamount to taking the dress off. As shown in the following figure:
Clothes play a decorative role for people, and buff is only an enhancement for Yasso. So, do you have an idea? Yes, you can create a Buff class, pass in the hero class and get a new enhanced hero class.
Class RedBuff extends Buff {constructor (hero) {this.hero = hero;} / / Red buff deals additional damage extraDamage () {} attack () {returnthis.hero.attack () + this.extraDamage ();}} class BlueBuff extends Buff {constructor (hero) {this.hero = hero } / / skill CD (minus 10%) CDR () {returnthis.hero.CDR () * 0.9;}} class BaronBuff extends Buff {constructor (hero) {this.hero = hero;} / / halve backSpeed () {returnthis.hero.backSpeed * 0.5;}
Once all the buff classes are defined, they can be applied directly to heroes, doesn't it look a lot more refreshing? This way of writing looks a lot like a combination of functions.
Const yasuo = new Yasuo (); const redYasuo = new RedBuff (yasuo); / / Red buff Yasso const blueYasuo = new BlueBuff (yasuo); / / Blue buff Yasso const redBlueYasuo = new BlueBuff (redYasuo); / / Red and Blue buff Asso
3. ES7 decorator
Decorator (decorator) is a proposal in ES7 and is currently in the stage-2 phase. The address of the proposal is: [JavaScript Decorators] [https://github.com/tc39/proposal-decorators]. The decorator is very similar to the function combinations (compose) and higher-order functions mentioned earlier. The decorator uses @ as the identifier and is placed in front of the decorated code. In other languages, there are already more mature decorator schemes.
3.1Decoration in Python
Let's take a look at an example of a decorator in Python:
Def auth (func): def inner (request,*args,**kwargs): v = request.COOKIES.get ('user') if not v: return redirect (' / login') return func (request,*args,**kwargs) return inner @ auth def index (request): v = request.COOKIES.get ("user") return render (request, "index.html") {"current_user": v})
The auth decorator determines whether the user is logged in by checking the cookie. The auth function is a high-order function that takes a func function as an argument and returns a new inner function. Check the cookie in the inner function to determine whether to jump back to the login page or continue to execute the func function. You can use this auth decorator on all functions that require permission verification, which is concise and non-invasive.
3.2 JavaScript decorator
The decorator in JavaScript is similar to Python's decorator, depending on Object.defineProperty, and is generally used to decorate classes, class properties, and class methods. The use of decorator can achieve some functions without directly modifying the code, so that the real aspect-oriented programming can be achieved. This is similar to Proxy to some extent, but is simpler to use than Proxy.
Note: the decorator is currently in stage-2, which means that there may be changes after the syntax. There are already some plans for decorators for functions, objects, etc. See [Future built-in decorators] [https://github.com/tc39/proposal-decorators/blob/master/NEXTBUILTINS.md#applying-built-in-decorators-to-other-syntactic-forms]]
3.3 class decorator
When decorating a class, the decorator method generally takes a target class as a parameter. Here is an example of adding a static attribute test to the target class:
Const decoratorClass = (targetClass) = > {targetClass.test = '123'} @ decoratorClass class Test {} Test.test; / /' 123'
In addition to modifying the class itself, you can also add new properties to the instance by modifying the prototype. Here is an example of adding a speak method to the target class:
Const withSpeak = (targetClass) = > {const prototype = targetClass.prototype; prototype.speak = function () {console.log ('I can speak', this.language);}} @ withSpeak class Student {constructor (language) {this.language = language;}} const student1 = new Student ('Chinese'); const student2 = new Student (' English'); student1.speak (); / / I can speak Chinese student2.speak () / / I can speak Chinese
Using the properties of higher-order functions, you can also pass parameters to the decorator to determine what to do with the class.
Const withLanguage = (language) = > (targetClass) = > {targetClass.prototype.language = language;} @ withLanguage ('Chinese') class Student {} const student = new Student (); student.language; / /' Chinese'
If you often write code for react-redux, you will also encounter situations where you need to map data from store to components. Connect is a high-level component that receives two functions mapStateToProps and mapDispatchToProps and a component App and finally returns an enhanced version of the component.
Class App extends React.Component {} connect (mapStateToProps, mapDispatchToProps) (App)
With the decorator, connect can be written more elegantly.
@ connect (mapStateToProps, mapDispatchToProps) class App extends React.Component {}
3.4 Class attribute decorator
The class attribute decorator can be used in the properties, methods, and get/set functions of a class, and generally receives three parameters:
Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community
Target: the decorated class
Name: name of the member of the class
Descriptor: property descriptor, which the object passes to Object.defineProperty. There are a lot of interesting things that can be done using the class property decorator, such as the example of readonly at the beginning:
Function readonly (target, name, descriptor) {descriptor.writable = false; return descriptor;} class Person {@ readonly name = 'person'} const person = new Person (); person.name =' tom'
It can also be used to count the execution time of a function in order to do some performance optimization later.
Function time (target, name, descriptor) {const func = descriptor.value; if (typeof func = 'function') {descriptor.value = function (... args) {console.time (); const results = func.apply (this, args); console.timeEnd (); return results } class Person {@ time say () {console.log ('hello')}} const person = new Person (); person.say ()
In react's well-known state management library mobx, we also use decorators to set class attributes as observable attributes, so as to achieve responsive programming.
Import {observable, action, autorun} from 'mobx' class Store {@ observable count = 1; @ action changeCount (count) {this.count = count;}} const store = new Store (); autorun (() = > {console.log (' count is', store.count);}) store.changeCount (10); / / modifying the value of count causes functions in autorun to execute automatically.
3.5 decorator combination
What if you want to use multiple decorators? The decorator is stackable and is executed sequentially according to the distance from the decorated class / attribute.
Class Person {@ time @ log say () {}}
In addition, in the proposal for decorators, there is an example of a decorator that combines a variety of decorators. It hasn't been used yet. Declare a composite decorator xyz by using decorator, which combines a variety of decorators.
Decorator @ xyz (arg, arg2 {@ foo @ bar (arg) @ baz (arg2)} @ xyz (1,2) class C {}
It is the same as the following.
@ foo @ bar (1) @ baz (2) class C {}
4. What interesting things can decorators do?
4.1 multiple inheritance
We have used the mixin approach when we talked about JavaScript multiple inheritance before, and combining decorators here can even further simplify the use of mixin. The mixin method will receive a list of parent classes and decorate the target class with it. Our ideal usage would be as follows:
@ mixin (Parent1, Parent2, Parent3) class Child {}
The principle is the same as the previous implementation of multiple inheritance, you only need to copy the prototype properties and instance properties of the parent class. A new Mixin class is created to copy all the properties above mixins and targetClass.
Const mixin = (... mixins) = > (targetClass) = > {mixins = [targetClass,... mixins]; function copyProperties (target, source) {for (let key of Reflect.ownKeys (source)) {if (key! = = 'constructor' & & key! = =' prototype' & & key! = = 'name') {let desc = Object.getOwnPropertyDescriptor (source, key) Object.defineProperty (target, key, desc);} class Mixin {constructor (... args) {for (let mixin of mixins) {copyProperties (this, new mixin (.args)); / / copy instance properties}} for (let mixin of mixins) {copyProperties (Mixin, mixin) / / copy static attributes copyProperties (Mixin.prototype, mixin.prototype); / / copy prototype attributes} return Mixin;} export default mixin
Let's test whether this mixin method works properly.
Class Parent1 {p1 () {console.log ('this is parent1')}} class Parent2 {p2 () {console.log (' this is parent2')}} class Parent3 {p3 () {console.log ('this is parent3')}} @ mixin (Parent1, Parent2) Parent3) class Child {C1 = () = > {console.log ('this is child')}} const child = new Child () Console.log (child)
Finally, the child object printed out in the browser looks like this, which proves that the mixin can work properly.
Note: the Child class here is the previous Mixin class.
Image.png-69.4kB
You might ask, why create an extra Mixin class? Why can't you modify targetClass's constructor directly? Didn't you say earlier that Proxy can intercept constructor? Congratulations, you've thought of a usage scenario for Proxy. Yes, it is more elegant to use Proxy here.
Const mixin = (... mixins) = > (targetClass) = > {function copyProperties (target, source) {for (let key ofReflect.ownKeys (source)) {if (key! = = 'constructor' & & key! = =' prototype' & & key! = = 'name') {let desc = Object.getOwnPropertyDescriptor (source, key) Object.defineProperty (target, key, desc);} for (let mixin of mixins) {copyProperties (targetClass, mixin); / / copy static attribute copyProperties (targetClass.prototype, mixin.prototype) / / copy prototype properties} / / intercept the construct method, and copy returnnewProxy (targetClass, {construct (target, args) {const obj = new target (.args); for (let mixin of mixins) {copyProperties (obj, new mixin () / / copy instance properties} return obj;}});}
4.2 Anti-shaking and throttling
In the past, we often use throttling functions in frequently triggered scenarios to optimize performance. Take the React component binding scrolling event as an example:
Class App extends React.Component {componentDidMount () {this.handleScroll = _ .throttle (this.scroll, 500); window.addEveneListener ('scroll', this.handleScroll);} componentWillUnmount () {window.removeEveneListener (' scroll', this.handleScroll);} scroll () {}}
Binding events in a component requires attention that it should be unbound when the component is destroyed. Because the throttling function returns a new anonymous function, in order to effectively unbind later, we have to save this anonymous function so that it can be used later. But with the decorator, we don't have to set the throttle method manually at every binding event, we just need to add a throttle decorator to the scroll function.
Const throttle = (time) = > {let prev = newDate (); return (target, name, descriptor) = > {const func = descriptor.value; if (typeof func = = 'function') {descriptor.value = function (... args) {const now = newDate () If (now-prev > wait) {fn.apply (this, args); prev = newDate ();}
It is much simpler to use than before.
Class App extends React.Component {componentDidMount () {window.addEveneListener ('scroll', this.scroll);} componentWillUnmount () {window.removeEveneListener (' scroll', this.scroll);} @ throttle (50) scroll () {}}
The implementation of anti-shake (debounce) function decorator is similar to the throttle function, and I won't say much about it here.
Const debounce = (time) = > {let timer; return (target, name, descriptor) = > {const func = descriptor.value If (typeof func = = 'function') {descriptor.value = function (... args) {if (timer) clearTimeout (timer) timer = setTimeout (() = > {fn.apply (this, args)}, wait)}}
If you are interested in throttling and anti-shake functions, you can read this article: [function throttling and function anti-shake] [https://juejin.im/entry/58c0379e44d9040068dc952f]]
4.3 data format verification
The class property decorator is used to verify the type of the properties of the class.
Const validate = (type) = > (target, name) = > {if (typeof target [name]! = = type) {thrownewError (`attribute ${name} must be ${type} type`)} class Form {@ validate ('string') name = 111gamma / Error: attribute name must be ${type} type}
If you find it too troublesome to manually check the properties one by one, you can also verify the entire class by writing validation rules.
Const rules = {name: 'string', password:' string', age: 'number'} const validator = rules = > targetClass = > {returnnewProxy (targetClass, {construct (target, args) {const obj = new target (... args) For (let [name, type] ofObject.entries (rules)) {if (typeof obj [name]! = = type) {thrownewError (`${name} must be ${type}`)} return obj }) @ validator (rules) class Person {name = 'tom' password =' 123' age = '21'} const person = new Person ()
4.4 core-decorators.js
Core-decorators is a JS library that encapsulates common decorators and summarizes the following decorators (only a few are listed).
Autobind: auto-bind this, Farewell Arrow function and bind
Readonly: set the class property to read-only
Override: check that the method of the subclass correctly overrides the method of the parent class with the same name
Debounce: anti-shake function
Throttle: throttling function
Enumerable: make a class method enumerable
Nonenumerable: make a class attribute inenumerable
Time: print function execution time
Mixin: mix multiple objects into classes (unlike the mixin above)
This is the end of "how to understand and master JS decorator". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.