Maison > Article > interface Web > Explication détaillée et introduction au cas du principe du décorateur Javascript
Cet article vous apporte des connaissances pertinentes sur les principes des décorateurs en javascript. Les décorateurs JavaScript peuvent être empruntés à Python ou Java. La différence la plus évidente est que les décorateurs dans la plupart des langages doivent être sur une seule ligne. sont séparés, tandis que le décorateur js peut être sur une seule ligne. J'espère que cela sera utile à tout le monde.
Un mot descriptif commençant par @. Le verbe anglais decorator est decor, ce qui signifie décorer. La racine dek (prononcée dec) signifie « acceptation » dans la langue proto-indo-européenne. Autrement dit, quelque chose de nouveau devient quelque chose de nouveau (et devient meilleur).
Décrit sous un autre angle, les décorateurs travaillent principalement sur l'extérieur de l'objet décoré, plutôt que d'envahir les changements à l'intérieur de celui-ci. Le mode décorateur est également un mode de développement. Bien que son statut soit plus faible que MVC, IoC, etc., il reste un excellent mode.
Les décorateurs JavaScript peuvent être empruntés à Python ou Java. La différence la plus évidente est que les décorateurs dans la plupart des langues doivent être séparés ligne par ligne, tandis que les décorateurs en js peuvent être sur une seule ligne.
Par exemple : je suis entré dans le bâtiment du siège de l'entreprise avec ma carte d'employé. Chaque employé appartenant à un département et à un niveau différent, il ne peut entrer dans aucune pièce du bâtiment. Chaque pièce a une porte ; l'entreprise doit donc prévoir au moins une personne dans chaque bureau pour vérifier les visiteurs :
Enregistrez d'abord le visiteur
Vérifiez s'il a la permission d'entrer, sinon, demandez-lui de partir.
Enregistrez l'heure de départ
Une autre option consiste à installer une serrure de porte électronique. La serrure de porte transmet uniquement les informations de la carte de l'employé à la salle informatique et est vérifiée par un programme spécifique.
Appelons l'ancien mode stupide. Le code est le suivant :
function A101(who){ record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} function A102(who){record(who,new Date(),'enter'); if (!permission(who)) { record(who,new Date(),'no permission') return void; } // 继续执行 doSomeWork(); record(who,new Date(),'leave')} // ...
Toute personne expérimentée doit y avoir pensé immédiatement, encapsulant ces déclarations répétées dans une méthode et en l'appelant de manière uniforme. Oui, cela résout la plupart des problèmes, mais ce n’est pas assez « élégant ». En même temps, il y a un autre problème. S'il y a trop de « pièces », ou si seules les pièces impaires dans le bâtiment doivent être vérifiées mais que les numéros pairs ne sont pas vérifiés, ne serait-ce pas « anormal » ? Si vous utilisez le modèle décorateur, le code ressemblera à ceci :
@verify(who)class Building { @verify(who) A101(){/*...*/} @verify(who) A102(){/*...*/} //...}
verify est un décorateur de vérification, et son essence est un ensemble de fonctions.
Comme dans l'exemple précédent, le décorateur lui-même est en fait une fonction, qui est exécutée avant d'exécuter l'objet décoré.
En JavaScript, les types de décorateurs sont :
Classe
Méthodes d'accès (attribut get et set)
Champs
Méthodes
Paramètre s
Depuis le décorateur Le concept est encore au stade de proposition et n'est pas une fonction JS officiellement disponible, si vous souhaitez utiliser cette fonction, vous devez utiliser un outil de traduction, tel que l'outil Babel ou TypeScript pour compiler le code JS avant qu'il puisse être exécuté. Nous devons d'abord configurer l'environnement d'exploitation et configurer certains paramètres. (Le processus suivant suppose que l'environnement de développement NodeJS et les outils de gestion de packages ont été correctement installés)
cd project && npm initnpm i -D @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-env babel-plugin-parameter-decorator
Créez un fichier de configuration .babelrc comme suit :
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], "babel-plugin-parameter-decorator" ]}
En utilisant la commande de conversion suivante, nous pouvons obtenir le programme de conversion ES5 :
npx babel source.js --out-file target.js
Créez un programme JS qui utilise des décorateurs decor-class.js
@classDecoratorclass Building { constructor() { this.name = "company"; }} const building = new Building(); function classDecorator(target) { console.log("target", target);}
Ce qui précède est le programme de décoration le plus simple que nous utilisons babel pour C'est. "traduit" en un programme ES5, puis embelli pour obtenir le programme suivant.
"use strict"; var _class; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); }} var Building = classDecorator( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class; var building = new Building(); function classDecorator(target) { console.log("target", target);}
第12行就是在类生成过程中,调用函数形态的装饰器,并将构造函数(类本身)送入其中。同样揭示了装饰器的第一个参数是类的构造函数的由来。
稍微修改一下代码,依旧是尽量保持最简单:
class Building { constructor() { this.name = "company"; } @methodDecorator openDoor() { console.log("The door being open"); }} const building = new Building(); function methodDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
然后转换代码,可以发现,这次代码量突然增大了很多。排除掉_classCallCheck、_defineProperties和_createClass三个函数,关注_applyDecoratedDescriptor函数:
function _applyDecoratedDescriptor( target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ("value" in desc || desc.initializer) { desc.writable = true; } desc = decorators .slice() .reverse() .reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc;}
它在生成构造函数之后,执行了这个函数,特别注意,这个装饰器函数是以数组形式的参数传递的。然后到上述代码的17~22行,将装饰器逐个应用,其中对装饰器的调用就在第21行。
它发送了3个参数,target指类本身。property指方法名(或者属性名),desc是可能被先前装饰器被处理过的descriptor,如果是第一次循环或只有一个装饰器,那么就是方法或属性本身的descriptor。
JS关于类的定义中,支持get和set关键字针对设置某个字段的读写操作逻辑,装饰器也同样支持这类方法的操作。
class Building { constructor() { this.name = "company"; } @propertyDecorator get roomNumber() { return this._roomNumber; } _roomNumber = ""; openDoor() { console.log("The door being open"); }}
有心的读者可能已经发现了,存取器装饰的代码与上面的方法装饰代码非常接近。关于属性 get和set方法,其本身也是一种方法的特殊形态。所以他们之间的代码就非常接近了。
继续修改源代码:
class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; } const building = new Building(); function propertyDecorator(target, property, descriptor) { console.log("target", target); if (property) { console.log("property", property); } if (descriptor) { console.log("descriptor", descriptor); } console.log("=====end of decorator========="); }
转换后的代码,还是与上述属性、存取器的代码非常接近。但除了_applyDecoratedDescriptor外,还多了一个_initializerDefineProperty函数。这个函数在生成构造函数时,将声明的各种字段绑定给对象。
参数装饰器的使用位置较之前集中装饰器略有不同,它被使用在行内。
class Building { constructor() { this.name = "company"; } openDoor(@parameterDecorator num, @parameterDecorator zoz) { console.log(`${num} door being open`); }} const building = new Building(); function parameterDecorator(target, property, key) { console.log("target", target); if (property) { console.log("property", property); } if (key) { console.log("key", key); } console.log("=====end of decorator========="); }
转换后的代码区别就比较明显了,babel并没有对其生成一个特定的函数对其进行特有的操作,而只在创建完类(构造函数)以及相关属性、方法后直接调用了开发者自己编写的装饰器函数:
var Building = /*#__PURE__*/function () { function Building() { _classCallCheck(this, Building); this.name = "company"; } _createClass(Building, [{ key: "openDoor", value: function openDoor(num, zoz) { console.log("".concat(num, " door being open")); } }]); parameterDecorator(Building.prototype, "openDoor", 1); parameterDecorator(Building.prototype, "openDoor", 0); return Building;}();
以上所有的案例,装饰器本身均没有使用任何参数。然实际应用中,经常会需要有特定的参数需求。我们再回到一开头的例子中verify(who),其中需要传入一个身份变量。哪又怎么做?我们少许改变一下类装饰器的代码:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; }}
转换后得到
// ...var who = "Django";var Building = ((_dec = classDecorator(who)), _dec( (_class = function Building() { _classCallCheck(this, Building); this.name = "company"; }) ) || _class); // ...
请注意第4第5行,它先执行了装饰器,然后再用返回值将类(构造函数)送入。相对应的,我们就应该将构造函数写成下面这样:
function classDecorator(people) { console.log(`hi~ ${people}`); return function (target) { console.log("target", target); }; }
同样的,方法、存取器、属性和参数装饰器均是如此。
到此,我们已经可以将装饰器参数与目标对象结合起来,进行一些逻辑类的操作。那么再回到文章的开头的例子中:需求中要先验证来访者权限,然后记录,最后在来访者离开时再做一次记录。此时需要监管对象方法被调用的整个过程。
请大家留意那个方法装饰器的descriptor,我们可以利用这个对象来“重写”这个方法。
class Building { constructor() { this.name = "company"; } @methodDecorator("Gate") openDoor(firstName, lastName) { return `The door will be open, when ${firstName} ${lastName} is walking into the ${this.name}.`; }} let building = new Building();console.log(building.openDoor("django", "xiang")); function methodDecorator(door) { return function (target, property, descriptor) { let fn = descriptor.value; descriptor.value = function (...args) { let [firstName, lastName] = args; console.log(`log: ${firstName}, who are comming.`); // verify(firstName,lastName) let result = Reflect.apply(fn, this, [firstName, lastName]); console.log(`log: ${result}`); console.log(`log: ${firstName}, who are leaving.`); return result; }; return descriptor; };}
代码第17行,将原方法暂存;18行定义一个新的方法,20~25行,记录、验证和记录离开的动作。
log: Django, who are comming.log: The door will be open, when Django Xiang is walking in to the company.log: Django, who are leaving.The door will be open, when Django Xiang is walking in to the company
通过阅读转换后的代码,我们知道装饰器工作的时刻是在类被实例化之前,在生成之中完成装饰函数的动作。那么,如果不同类型的多个装饰器同时作用,其过程是怎样的?我们将先前的案例全部整合到一起看看:
const who = "Django";@classDecorator(who)class Building { constructor() { this.name = "company"; } @propertyDecorator roomNumber = ""; @methodDecorator openDoor(@parameterDecorator num) { console.log(`${num} door being open`); } @accessorDecorator get roomNumber() { return this._roomNumber; }} const building = new Building(); function classDecorator(people) { console.log(`class decorator`); return function (target) { console.log("target", target); };} function methodDecorator(target, property, descriptor) { console.log("method decorator");} function accessorDecorator(target, property, descriptor) { console.log("accessor decorator");} function propertyDecorator(target, property, descriptor) { console.log("property decoator");} function parameterDecorator(target, property, key) { console.log("parameter decorator");}
class decorator
parameter decorator
property decoator
method decorator
accessor decorator
还可以通过阅读转换后的源代码得到执行顺序:
类装饰器(在最外层)
参数装饰器(在生成构造函数最里层)
按照出现的先后顺序的:属性、方法和存取器
Decorator est un modèle de développement élégant, qui facilite grandement le processus de codage du développeur et améliore la lisibilité du code. Quand on fait appel à des décorateurs pour développer, il est encore très nécessaire de comprendre son mécanisme de fonctionnement.
Recommandations associées : Tutoriel d'apprentissage Javascript
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!