了解 JavaScript 中的装饰器
在Medium上找到我
在 Web 开发中,JavaScript 被认为是构建高度复杂的用户界面最推荐的语言,这些界面可能来自各种需求,尤其是业务需求。在本文中,我们将介绍 JavaScript 中一种名为“装饰器”的实用模式。
装饰器是一种可以用来动态地为另一个对象添加附加功能的对象,而无需更改该对象的实现。仅仅通过理解这个定义,我们很可能就会同意,装饰器对我们的应用程序代码很有用。
如果你和我一样,一开始可能会有点困惑,尤其是 TypeScript 的语法比较特殊。使用现代语法(目前 TypeScript 和 Babel 插件支持)给类添加装饰器时,感觉不太像 JavaScript。
以下是使用示例:
@filterMales // This is the decorator
class MyClass {
constructor(children) {
this.children = children
}
}
那些从未见过这类代码(特别是语法)的人,@filterMales
在意识到这是在应用装饰器时,可能会对装饰器感到有些畏惧。这种装饰器只是语法糖而已。理解和实现装饰器可能比你想象的要容易。如果你已经使用 JavaScript 开发了一段时间,你可能已经在不知不觉中实现了装饰器。它们简单却功能强大。
我们将查看 JavaScript 中的一些装饰器示例,并创建我们自己的装饰器,看看它对我们的代码有何用处。
什么时候使用装饰器比较好?
幸运的是,装饰器可以通过多种方式为我们提供帮助。
为现有对象添加动态行为
如前所述,一种非常有用的情况是,当您需要动态地向对象添加额外的逻辑,而不必处理某些替代方案(如子类化或继承)时。
请记住这一点:装饰者可以将东西注入对象中,而外界甚至不知道他们将如何做到这一点。
例如,假设我们有一个Frog
类将实现一个名为的方法lick
。青蛙有牙齿,因此我们还将随机实现一种getTeeths
方法来返回它们拥有的牙齿数量。
它可能看起来像这样:
function Frog(name) {
this.name = name
}
Frog.prototype.getTeeths = function() {
return 2
}
Frog.prototype.lick = function(target) {
console.log(`I'm going lick you, ${target.name}. You better taste delicious`)
}
// Or with classes
class Frog {
constructor(name) {
this.name = name
}
getTeeths() {
return 2
}
lick(target) {
console.log(
`I'm going lick you, ${target.name}. You better taste delicious`,
)
}
}
现实生活中,青蛙也分很多种,比如蟾蜍。蟾蜍仍然是青蛙,但青蛙又不是蟾蜍,这意味着它们之间一定存在一些不可混淆的区别特征。
由于蟾蜍是青蛙,我们可以构建一个withToad
装饰器,如果需要,它可以装饰青蛙的实例,以便它可以代表蟾蜍。
请记住,装饰器只应扩展或添加某些事物的额外行为,而不应改变其实现。
了解了这一点,withToad
装饰器实际上非常简单:
function withToad(frog) {
frog.getTeeths = function() {
return 0
}
}
const mikeTheFrog = new Frog('mike')
withToad(mikeTheFrog)
console.log(mikeTheFrog.getTeeths())
我们的装饰器withToad
重新实现了,getTeeths
所以它返回了0
因为蟾蜍没有牙齿。当我们使用这个装饰器时,我们实际上是在默默地装饰(在本例中是转换)一只青蛙,使它代表一只蟾蜍。
您可以使用继承的子类化来实现相同的目标,如下所示:
function Toad(name) {
Frog.call(this, name)
this.getTeeths = function() {
return 0
}
}
const kellyTheToad = new Toad('kelly')
// or using classes
class Toad extends Frog {
getTeeths() {
return 0
}
}
const kellyTheToad = new Toad('kelly')
这两种方法的区别在于,通过使用装饰器,您不必为蟾蜍创建类。
我们的例子展示了如何使用装饰器来操纵青蛙,使其更符合蟾蜍的特征。
现在我们来看一个更好的例子,了解如何使用装饰器来扩展功能。接下来的事情会变得有点有趣。
假设我们正在构建一个支持各种自定义预定义主题的应用程序,以便用户设置其控制面板的样式。我们将实现一个Theme
方法createStylesheet
,用于创建兼容的样式表,并实现一个applyStyles
解析此样式表并将其应用到 DOM 的方法,允许该方法自身调用applyStyle
以将它们应用到 DOM:
function Theme() {}
Theme.prototype.createStylesheet = function() {
return {
header: {
color: '#333',
fontStyle: 'italic',
fontFamily: 'Roboto, sans-serif',
},
background: {
backgroundColor: '#fff',
},
button: {
backgroundColor: '#fff',
color: '#333',
},
color: '#fff',
}
}
Theme.prototype.applyStylesheet = function(stylesheet) {
const bodyElem = document.querySelector('body')
const headerElem = document.getElementById('header')
const buttonElems = document.querySelectorAll('button')
this.applyStyles(bodyElem, stylesheet.background)
this.applyStyles(headerElem, stylesheet.header)
buttonElems.forEach((buttonElem) => {
this.applyStyles(buttonElem, stylesheet.button)
})
}
Theme.prototype.applyStyles = function(elem, styles) {
for (let key in styles) {
if (styles.hasOwnProperty(key)) {
elem.style[key] = styles[key]
}
}
}
一切看起来都很棒。我们现在已经定义了Theme
API,现在可以创建如下样式表:
const theme = new Theme()
const stylesheet = theme.createStylesheet()
目前的情况如下stylesheet
:
{
"header": {
"color": "#333",
"fontStyle": "italic",
"fontFamily": "Roboto, sans-serif"
},
"background": { "backgroundColor": "#fff" },
"button": { "backgroundColor": "#fff", "color": "#333" },
"color": "#fff"
}
现在我们可以像这样使用它,它将相应地装饰我们的网页:
theme.applyStylesheet(stylesheet)
继续牢记这一点:提供开放机会来支持插件开发
调用时,如何theme
返回一个自定义主题,createStylesheet
以便我们能够使用它来扩展,而不必使用默认主题?
这时装饰器就派上用场了,因为它允许我们返回一个不同的预定义默认主题来使用。
我们将创建一个装饰器,它将帮助我们应用一个blood
装饰主题,Theme
以便它为我们生成一个代表主题blood
而不是原始主题的默认样式表。
我们将这个装饰器称为bloodTheme
:
function bloodTheme(originalTheme) {
const originalStylesheet = originalTheme.createStylesheet()
originalTheme.createStylesheet = function() {
return {
name: 'blood',
...originalStylesheet,
header: {
...originalStylesheet.header,
color: '#fff',
fontStyle: 'italic',
},
background: {
...originalStylesheet.background,
color: '#fff',
backgroundColor: '#C53719',
},
button: {
...originalStylesheet.button,
backgroundColor: 'maroon',
color: '#fff',
},
primary: '#C53719',
secondary: 'maroon',
textColor: '#fff',
}
}
}
现在我们要做的theme
只是用一行来装饰:
const theme = new Theme()
bloodTheme(theme) // Applying the decorator
const stylesheet = theme.createStylesheet()
console.log(stylesheet)
主题现在为我们提供了一个默认blood
样式表:
{
"name": "blood",
"header": {
"color": "#fff",
"fontStyle": "italic",
"fontFamily": "Roboto, sans-serif"
},
"background": { "backgroundColor": "#C53719", "color": "#fff" },
"button": { "backgroundColor": "maroon", "color": "#fff" },
"color": "#fff",
"primary": "#C53719",
"secondary": "maroon",
"textColor": "#fff"
}
可以看到,代码/实现theme
并没有改变。应用自定义样式表也没有改变:
theme.applyStylesheet(stylesheet)
现在我们的网页将blood
应用主题样式:
我们可以创建任意数量的主题,并随时应用它们。这意味着我们将代码开放给插件,例如自定义主题。
应用临时行为
使用装饰器的另一个好时机是当我们正在寻找暂时将行为应用于对象的方法时,因为我们计划在将来删除它。
例如,如果圣诞节即将来临,我们可以轻松创建一个圣诞样式表并将其用作装饰器。这很棒,因为圣诞节结束后,我们可以轻松地将其从代码中移除。就我们之前的示例而言,我们只需删除该bloodTheme(theme)
行即可转换回原始样式表。
子类化/继承
使用装饰器的另一个好例子是,当代码变得庞大时,创建子类会变得难以管理。然而,与 Java 等静态语言相比,这个问题在 JavaScript 中并不严重——除非你在 JavaScript 中大量使用类继承的实现。
调试模式
另一个有用的用例是创建一个调试模式装饰器,当应用它时,它会将发生的每件事记录到控制台上。例如,这是一个debugTheme
在开发模式下对我们有用的装饰器:
function debugTheme(originalTheme) {
const stylesheet = originalTheme.createStylesheet()
console.log(
'%cStylesheet created:',
'color:green;font-weight:bold;',
stylesheet,
)
if (!stylesheet.primary) {
console.warn(
'A stylesheet was created without a primary theme color. There may be layout glitches.',
)
}
}
const theme = new Theme()
bloodTheme(theme)
if (process.env.NODE_ENV === 'development') debugTheme(theme)
当我们在模式下运行应用程序时,我们的控制台现在会提供有用的信息development
:
在Medium上找到我
鏂囩珷鏉ユ簮锛�https://dev.to/jsmanifest/learn-about-decorators-in-javascript-1361