Javascriptデコレータ原理の詳しい説明と事例紹介

WBOY
リリース: 2022-02-21 17:37:29
転載
1677 人が閲覧しました

この記事では、javascript デコレータの原理に関する関連知識を提供します。JavaScript デコレータは Python または Java から借用することができます。最も明らかな違いは、一部の言語のデコレータは分離する必要があることです。 js のデコレータは 1 行で済みますが、行単位で記述できます。

Javascriptデコレータ原理の詳しい説明と事例紹介

#@ で始まる説明的な単語。英語の動詞デコレーターは「decorate」で、「飾る」という意味です。ルート dek (デクと発音) は、印欧祖語で「受け入れ」を意味します。つまり、新しいものは新しいものになります(そしてより良くなります)。

別の観点から説明すると、デコレータは装飾されたオブジェクトの内部の変更に侵入するのではなく、主に装飾されたオブジェクトの外部で動作します。デコレータモードも開発モードであり、MVCやIoCなどに比べると格は劣りますが、それでも優秀なモードです。

JavaScript デコレータは、Python または Java から借用できます。より明らかな違いは、ほとんどの言語のデコレーターは 1 行ずつ区切る必要があるのに対し、js のデコレーターは 1 行で済むことです。

デコレータの重要性

例: 社員証を持って本社ビルに入りました。従業員ごとに所属する部署や階層が異なるため、建物内のどの部屋にも入ることができません。各部屋にはドアがあるため、会社は各オフィスに訪問者を確認する担当者を少なくとも 1 人配置する必要があります。

  1. 最初に訪問者を登録します

  2. 参加者に入場許可があるかどうかを確認し、許可されていない場合は退出するように依頼する

  3. 出発時刻を記録する

別のオプションもあります。ドアロックは従業員カード情報のみをコンピュータールームに送信し、特定のプログラムによって認証されます。

前者は一時的に愚かモードと呼ばれ、コードは次のとおりです。

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(){/*...*/}  
//...}
ログイン後にコピー

verify は検証デコレータであり、その本質は一連の関数です。

JavaScript デコレータ

前の例と同様、デコレータ自体は実際には関数であり、装飾されたオブジェクトを実行する前に実行されます。

JavaScript では、デコレータのタイプは次のとおりです。

  • クラス

  • アクセス メソッド (属性 get および set )

  • フィールド

  • メソッド

  • パラメータ

sinceデコレーターの概念はまだ提案段階にあり、正式に利用可能な JS 関数ではありません。この関数を使用したい場合は、Babel ツールや TypeScript などのトランスレーター ツールを使用して、JS コードをコンパイルしてから JS コードをコンパイルする必要があります。実行されました。最初に動作環境をセットアップし、いくつかのパラメータを設定する必要があります。 (次のプロセスは、NodeJS 開発環境とパッケージ管理ツールが正しくインストールされていることを前提としています)

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
ログイン後にコピー

次のように .babelrc 構成ファイルを作成します。

{  "presets": ["@babel/preset-env"],  "plugins": [    
["@babel/plugin-proposal-decorators", { "legacy": true }],    
["@babel/plugin-proposal-class-properties", { "loose": true }],    
"babel-plugin-parameter-decorator"  ]}
ログイン後にコピー

次の変換コマンドを使用すると、次のことができます。 ES5 変換プログラムの取得:

npx babel source.js --out-file target.js

クラス デコレータ

デコレータ JS プログラムの作成デコレータ-class.js

@classDecoratorclass Building {  
constructor() {    
this.name = "company";  
}}
const building = new Building();
function classDecorator(target) {  
console.log("target", target);}
ログイン後にコピー

上記は最も単純なデコレータ プログラムで、babel を使用してこれを ES5 プログラムに「変換」し、美化して次のプログラムを取得します。

"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行就是在类生成过程中,调用函数形态的装饰器,并将构造函数(类本身)送入其中。同样揭示了装饰器的第一个参数是类的构造函数的由来。

方法 (method)装饰器

稍微修改一下代码,依旧是尽量保持最简单:

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。

存取器(accessor)装饰

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");}
ログイン後にコピー

  1. class decorator

  2. parameter decorator

  3. property decoator

  4. method decorator

  5. accessor decorator

还可以通过阅读转换后的源代码得到执行顺序:

  1. 类装饰器(在最外层)

  2. 参数装饰器(在生成构造函数最里层)

  3. 按照出现的先后顺序的:属性、方法和存取器

概要

Decorator は、開発者のコ​​ーディング プロセスを大幅に容易にし、コードの読みやすさを向上させるエレガントな開発モデルです。開発にデコレータを使用する場合、その動作メカニズムを理解することが依然として非常に必要です。

関連する推奨事項: JavaScript 学習チュートリアル

以上がJavascriptデコレータ原理の詳しい説明と事例紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:csdn.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!