この記事では、javascript デコレータの原理に関する関連知識を提供します。JavaScript デコレータは Python または Java から借用することができます。最も明らかな違いは、一部の言語のデコレータは分離する必要があることです。 js のデコレータは 1 行で済みますが、行単位で記述できます。
#@ で始まる説明的な単語。英語の動詞デコレーターは「decorate」で、「飾る」という意味です。ルート dek (デクと発音) は、印欧祖語で「受け入れ」を意味します。つまり、新しいものは新しいものになります(そしてより良くなります)。
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')} // ...
@verify(who)class Building { @verify(who) A101(){/*...*/} @verify(who) A102(){/*...*/} //...}
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
{ "presets": ["@babel/preset-env"], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }], "babel-plugin-parameter-decorator" ]}
@classDecoratorclass Building { constructor() { this.name = "company"; }} const building = new Building(); function classDecorator(target) { console.log("target", target);}
"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 は、開発者のコーディング プロセスを大幅に容易にし、コードの読みやすさを向上させるエレガントな開発モデルです。開発にデコレータを使用する場合、その動作メカニズムを理解することが依然として非常に必要です。
関連する推奨事項: JavaScript 学習チュートリアル
以上がJavascriptデコレータ原理の詳しい説明と事例紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。