Javascript デコレータの使用方法
この記事では、主に Javascript デコレーターの使用方法を紹介します。これは、必要な友人に参照してもらえるようにします。
最近、データベースと TypeScript を使用して開発された新しい Node プロジェクトを開きました。私はルーティング管理で多くのデコレーターを使用してきましたが、これは確かに良いことであることがわかりました。
Decorator はまだドラフト段階の機能ですが、現時点ではこの構文を直接サポートする環境はありませんが、babel などを介して古い構文に変換することで効果を実現できます。 は自信を持って @Decorator
です。 @Decorator
。
什么是装饰器
装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为。
通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。
个人认为装饰器是一种解决方案,而并非是狭义的@Decorator
,后者仅仅是一个语法糖罢了。
装饰器在身边的例子随处可见,一个简单的例子,水龙头上边的起泡器就是一个装饰器,在装上以后就会把空气混入水流中,掺杂很多泡泡在水里。
但是起泡器安装与否对水龙头本身并没有什么影响,即使拆掉起泡器,也会照样工作,水龙头的作用在于阀门的控制,至于水中掺不掺杂气泡则不是水龙头需要关心的。
所以,对于装饰器,可以简单地理解为是非侵入式的行为修改。
为什么要用装饰器
可能有些时候,我们会对传入参数的类型判断、对返回值的排序、过滤,对函数添加节流、防抖或其他的功能性代码,基于多个类的继承,各种各样的与函数逻辑本身无关的、重复性的代码。
函数中的作用
可以想像一下,我们有一个工具类,提供了一个获取数据的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
现在我们想要添加一个功能,记录该函数执行的耗时。
因为这个函数被很多人使用,在调用方添加耗时统计逻辑是不可取的,所以我们要在Model1
中进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
这样在调用方法后我们就可以在控制台看到耗时的输出了。
但是这样直接修改原函数代码有以下几个问题:
统计耗时的相关代码与函数本身逻辑并无一点关系,影响到了对原函数本身的理解,对函数结构造成了破坏性的修改
如果后期还有更多类似的函数需要添加统计耗时的代码,在每个函数中都添加这样的代码显然是低效的,维护成本太高
所以,为了让统计耗时的逻辑变得更加灵活,我们将创建一个新的工具函数,用来包装需要设置统计耗时的函数。
通过将Class
与目标函数的name
传递到函数中,实现了通用的耗时统计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
接下来,我们想控制其中一个Model
的函数不可被其他人修改覆盖,所以要添加一些新的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
可以看出,两个wrap
函数中有不少重复的地方,而修改程序行为的逻辑,实际上依赖的是Object.defineProperty
中传递的三个参数。
所以,我们针对wrap
在进行一次修改,将其变为一个通用类的转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
到了这一步以后,我们就可以称log
和seal
为装饰器了,可以很方便的让我们对一些函数添加行为。
而拆分出来的这些功能可以用于未来可能会有需要的地方,而不用重新开发一遍相同的逻辑。
Class 中的作用
就像上边提到了,现阶段在JS中继承多个Class
是一件头疼的事情,没有直接的语法能够继承多个 Class。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
所以,在React
中就有了一个mixin
的概念,用来将多个Class
的功能复制到一个新的Class
上。
大致思路就是上边列出来的,但是这个mixin
是React
中内置的一个操作,我们可以将其转换为更接近装饰器的实现。
在不修改原Class
的情况下,将其他Class
的属性复制过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
以上,就是装饰器在函数、Class
上的实现方法(至少目前是的),但是草案中还有一颗特别甜的语法糖,也就是@Decorator
デコレータとは何ですか?
デコレータは、クラス、関数、プロパティなどに追加の動作を追加できる装飾です。
一般的な理解は、元のコードの外側の層にラップされた処理ロジックの層として考えることができます。個人的には、単なる構文糖である狭義の @Decorator
ではなく、decorator が解決策だと思います。
- デコレーターの例はどこでも見られます。簡単な例は、蛇口に取り付けられた後、水の流れに空気を混ぜ、水中に大量の泡を混ぜます。
- ただし、バブラーが取り付けられているかどうかは、蛇口自体には影響しません。バブラーを取り外しても、水に泡が混じるかどうかを制御するのが蛇口の機能です。蛇口は気にする必要はありません。 したがって、デコレータは単に非侵入的な動作変更として理解できます。
- デコレータを使用する理由場合によっては、複数のクラス継承、何も持たないさまざまな反復コードに基づいて、受信パラメータのタイプを判断し、戻り値をソートおよびフィルタリングし、スロットル、アンチシェイクまたはその他の機能コードを関数に追加します。関数のロジック自体に関係します。
- データを取得する関数を提供するツール クラスがあると想像できます: 次に、実行にかかる時間を記録する関数を追加します。関数。 🎜この関数は多くの人によって使用されるため、呼び出し元に時間のかかる統計ロジックを追加することはお勧めできません。そのため、
1
2
3
4
5
6
7
8
9
10
11
12
13
@tag
class
A {
@method
hi () {}
}
function
tag(constructor) {
console.log(constructor === A)
// true
}
function
method(target) {
console.log(target.constructor === A, target === A.prototype)
// true, true
}
ログイン後にコピーログイン後にコピーModel1
でそれを変更する必要があります。 🎜🎜このように、呼び出し後に、コンソールには時間のかかる出力が表示されます。 🎜ただし、この方法で元の関数コードを直接変更する場合には、いくつかの問題があります: 🎜🎜🎜🎜 時間のかかる関連コードは、関数自体のロジックとは何の関係もなく、元の関数自体の理解に影響を及ぼし、関数の構造に破壊的なダメージを与える🎜🎜🎜🎜 今後、統計的に時間のかかるコードを追加する必要がある同様の関数がさらに増えた場合、そのようなコードを各関数に追加することは明らかに非効率であり、メンテナンスコストが高すぎます🎜🎜1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@name
class
Person {
sayHi() {
console.log(`My name is: ${this.name}`)
}
}
// 创建一个继承自Person的匿名类
// 直接返回并替换原有的构造函数
function
name(constructor) {
return
class
extends
constructor {
name = 'Niko'
}
}
new
Person().sayHi()
ログイン後にコピーログイン後にコピー
関数の役割
Class
と name
を関数に渡すことで、時間のかかる普遍的な統計が達成されます。 🎜1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
wrap
には多くの重複があることがわかります。関数と変更 プログラム動作のロジックは、実際には Object.defineProperty
で渡される 3 つのパラメーターに依存します。 🎜そこで、wrap
に変更を加えて、それを一般的なクラスに変換します。 🎜1 2 3 |
|
log
と seal
を呼び出すことができます。 > はデコレータであり、これを使用すると、一部の関数に動作を簡単に追加できます。 🎜これらの分離された関数は、同じロジックを再開発することなく、将来必要になる可能性がある場所で使用できます。 🎜Class の役割
🎜 上で述べたように、JS で複数のClass
を継承するのは、この段階では問題です。複数の Class を継承するための直接の構文はありません。 🎜1 |
|
React
には mixin
という概念があり、複数の Class
の関数を新しい関数にコピーするために使用されます。 クラス
。 🎜大まかな考え方は上記の通りですが、この mixin
は React
に組み込まれた操作であり、デコレータに近い実装に変換することができます。 🎜元の Class
を変更せずに、他の Class
のプロパティをコピーします: 🎜1 2 3 4 5 6 7 8 9 |
|
Class
> 実装メソッドのデコレータです。 (少なくとも現時点では) しかし、ドラフトには特に優れた構文糖衣があり、それは @Decorator
です。 🎜 デコレーターを使用するための多くの面倒な手順を節約するのに役立ちます。 🎜🎜@Decorator の使用方法🎜🎜 ドラフトのデコレーター、または TS によって実装されたデコレーターは、上記の 2 つのタイプをさらにカプセル化して、より詳細なデコレーター アプリケーションに分割します。 現在、どこでも使用される次のものをサポートしています。 🎜🎜🎜🎜関数🎜🎜🎜🎜get setアクセサ🎜实例属性、静态函数及属性
函数参数
@Decorator的语法规定比较简单,就是通过@
符号后边跟一个装饰器函数的引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
函数tag
与method
会在class A
定义的时候执行。
@Decorator 在 Class 中的使用
该装饰器会在class定义前调用,如果函数有返回值,则会认为是一个新的构造函数来替代之前的构造函数。
函数接收一个参数:
constructor 之前的构造函数
我们可以针对原有的构造函数进行一些改造:
新增一些属性
如果想要新增一些属性之类的,有两种方案可以选择:
创建一个新的
class
继承自原有class
,并添加属性针对当前
class
进行修改
后者的适用范围更窄一些,更接近mixin的处理方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
修改原有属性的描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
使用闭包来增强装饰器的功能
在TS文档中被称为装饰器工厂
因为@
符号后边跟的是一个函数的引用,所以对于mixin的实现,我们可以很轻易的使用闭包来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
多个装饰器的应用
装饰器是可以同时应用多个的(不然也就失去了最初的意义)。
用法如下:
1 2 3 |
|
执行的顺序为decorator2
-> decorator1
,离class
定义最近的先执行。
可以想像成函数嵌套的形式:
1 |
|
@Decorator 在 Class 成员中的使用
类成员上的 @Decorator 应该是应用最为广泛的一处了,函数,属性,get
、set
访问器,这几处都可以认为是类成员。
在TS文档中被分为了Method Decorator
、Accessor Decorator
和Property Decorator
,实际上如出一辙。
关于这类装饰器,会接收如下三个参数:
如果装饰器挂载于静态成员上,则会返回构造函数,如果挂载于实例成员上则会返回类的原型
装饰器挂载的成员名称
成员的描述符,也就是
Object.getOwnPropertyDescriptor
的返回值
Property Decorator
不会返回第三个参数,但是可以自己手动获取
前提是静态成员,而非实例成员,因为装饰器都是运行在类创建时,而实例成员是在实例化一个类的时候才会执行的,所以没有办法获取对应的descriptor
静态成员与实例成员在返回值上的区别
可以稍微明确一下,静态成员与实例成员的区别:
1 2 3 4 5 6 7 8 9 |
|
method1
和method2
是实例成员,method1
存在于prototype
之上,而method2
只在实例化对象以后才有。
作为静态成员的method3
和method4
,两者的区别在于是否可枚举描述符的设置,所以可以简单地认为,上述代码转换为ES5版本后是这样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
可以看出,只有method2
是在实例化时才赋值的,一个不存在的属性是不会有descriptor
的,所以这就是为什么TS在针对Property Decorator
不传递第三个参数的原因,至于为什么静态成员也没有传递descriptor
,目前没有找到合理的解释,但是如果明确的要使用,是可以手动获取的。
就像上述的示例,我们针对四个成员都添加了装饰器以后,method1
和method2
第一个参数就是Model.prototype
,而method3
和method4
的第一个参数就是Model
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
函数,访问器,和属性装饰器三者之间的区别
函数
首先是函数,函数装饰器的返回值会默认作为属性的value
描述符存在,如果返回值为undefined
则会忽略,使用之前的descriptor
引用作为函数的描述符。
所以针对我们最开始的统计耗时的逻辑可以这么来做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
访问器
访问器就是添加有get
、set
前缀的函数,用于控制属性的赋值及取值操作,在使用上与函数没有什么区别,甚至在返回值的处理上也没有什么区别。
只不过我们需要按照规定设置对应的get
或者set
描述符罢了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
属性
对于属性的装饰器,是没有返回descriptor
的,并且装饰器函数的返回值也会被忽略掉,如果我们想要修改某一个静态属性,则需要自己获取descriptor
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
对于一个实例的属性,则没有直接修改的方案,不过我们可以结合着一些其他装饰器来曲线救国。
比如,我们有一个类,会传入姓名和年龄作为初始化的参数,然后我们要针对这两个参数设置对应的格式校验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
首先,在类上边添加装饰器@validator
,然后在需要校验的两个参数上添加@validate
装饰器,两个装饰器用来向一个全局对象传入信息,来记录哪些属性是需要进行校验的。
然后在validator
中继承原有的类对象,并在实例化之后遍历刚才设置的所有校验信息进行验证,如果发现有类型错误的,直接抛出异常。
这个类型验证的操作对于原Class
来说几乎是无感知的。
函数参数装饰器
最后,还有一个用于函数参数的装饰器,这个装饰器也是像实例属性一样的,没有办法单独使用,毕竟函数是在运行时调用的,而无论是何种装饰器,都是在声明类时(可以认为是伪编译期)调用的。
函数参数装饰器会接收三个参数:
类似上述的操作,类的原型或者类的构造函数
参数所处的函数名称
参数在函数中形参中的位置(函数签名中的第几个参数)
一个简单的示例,我们可以结合着函数装饰器来完成对函数参数的类型转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
使用装饰器实现一个有趣的Koa封装
比如在写Node接口时,可能是用的koa
或者express
,一般来说可能要处理很多的请求参数,有来自headers
的,有来自body
的,甚至有来自query
、cookie
的。
所以很有可能在router
的开头数行都是这样的操作:
1 2 3 4 5 |
|
以及如果我们有大量的接口,可能就会有大量的router.get
、router.post
。
以及如果要针对模块进行分类,可能还会有大量的new Router
的操作。
这些代码都是与业务逻辑本身无关的,所以我们应该尽可能的简化这些代码的占比,而使用装饰器就能够帮助我们达到这个目的。
装饰器的准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
Koa服务的处理
上边是创建了所有需要用到的装饰器,但是也仅仅是把我们所需要的各种信息存了起来,而怎么利用这些装饰器则是下一步需要做的事情了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
上边的代码就已经搭建出来了一个Koa的封装,以及包含了对各种装饰器的处理,接下来就是这些装饰器的实际应用了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
很轻易的就实现了一个router
的创建,路径、method的处理,包括各种参数的获取,类型转换。
将各种非业务逻辑相关的代码统统交由装饰器来做,而函数本身只负责处理自身逻辑即可。
这里有完整的代码:GitHub。安装依赖后npm start
即可看到效果。
这样开发带来的好处就是,让代码可读性变得更高,在函数中更专注的做自己应该做的事情。
而且装饰器本身如果名字起的足够好的好,也是在一定程度上可以当作文档注释来看待了(Java中有个类似的玩意儿叫做注解)。
总结
合理利用装饰器可以极大的提高开发效率,对一些非逻辑相关的代码进行封装提炼能够帮助我们快速完成重复性的工作,节省时间。
但是糖再好吃,也不要吃太多,容易坏牙齿的,同样的滥用装饰器也会使代码本身逻辑变得扑朔迷离,如果确定一段代码不会在其他地方用到,或者一个函数的核心逻辑就是这些代码,那么就没有必要将它取出来作为一个装饰器来存在。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
原生JS基于window.scrollTo()封装垂直滚动动画工具函数
以上がJavascript デコレータの使用方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。
