WEBKT

JavaScript 装饰器详解:从入门到进阶,玩转代码的“魔法”

28 0 0 0

JavaScript 装饰器详解:从入门到进阶,玩转代码的“魔法”

1. 装饰器:给你的代码加点“料”

2. 装饰器的基本用法

3. 装饰器的种类

3.1 类装饰器

3.2 属性装饰器

3.3 方法装饰器

3.4 访问器装饰器

3.5 参数装饰器

4. 装饰器工厂

5. 装饰器的执行顺序

6. 装饰器的注意事项

7. 装饰器的应用场景

JavaScript 装饰器详解:从入门到进阶,玩转代码的“魔法”

“装饰器”这个词,听起来是不是有点像装修房子时用的各种饰品?在 JavaScript 的世界里,装饰器 (Decorator) 确实也起到了类似的作用——它可以“装饰”你的代码,让你的代码更强大、更灵活,更易于维护。

不过,先别急着激动,咱们得先弄明白,这“装饰器”到底是个啥玩意儿。

1. 装饰器:给你的代码加点“料”

想象一下,你写了一个函数,用来计算两个数的和:

function add(x, y) {
return x + y;
}

很简单,对吧?但现在,你有了新的需求:

  • 你想在每次调用这个函数之前,都打印一条日志,记录下输入的参数。
  • 你还想在计算结果之后,检查一下结果是不是大于 10,如果大于 10,就额外加上 5。

按照传统的做法,你可能会直接修改 add 函数的代码:

function add(x, y) {
console.log(`Calling add with arguments: ${x}, ${y}`);
let result = x + y;
if (result > 10) {
result += 5;
}
return result;
}

这样做虽然能满足需求,但却破坏了 add 函数的“纯粹性”。原本它只是一个简单的加法函数,现在却混杂了日志记录和结果检查的逻辑。如果以后需求又变了,你还得继续往 add 函数里塞东西,这会让你的代码变得越来越臃肿,越来越难以理解。

这时候,装饰器就派上用场了。它可以让你在不修改原始函数代码的情况下,给它添加额外的功能。

2. 装饰器的基本用法

在 JavaScript 中,装饰器本质上就是一个函数,它可以“装饰”类、类的属性、方法、访问器(getter/setter)以及方法的参数。

装饰器的语法很简单,就是在被装饰的对象前面加上 @ 符号,后面跟着装饰器函数的名称。

让我们用装饰器来改造一下上面的 add 函数:

// 定义一个日志装饰器
function log(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with arguments: ${args}`);
return originalMethod.apply(this, args);
};
return descriptor;
}
// 定义一个结果检查装饰器
function checkResult(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
let result = originalMethod.apply(this, args);
if (result > 10) {
result += 5;
}
return result;
};
return descriptor;
}
// 使用装饰器
class Calculator {
@log
@checkResult
add(x, y) {
return x + y;
}
}
const calculator = new Calculator();
const sum = calculator.add(5, 7); // 输出:Calling add with arguments: 5,7
console.log(sum); // 输出:17

看到了吗?我们并没有修改 add 函数本身的代码,而是通过 @log@checkResult 这两个装饰器,给它添加了日志记录和结果检查的功能。

3. 装饰器的种类

根据装饰对象的不同,装饰器可以分为以下几种:

  • 类装饰器:用于装饰整个类。
  • 属性装饰器:用于装饰类的属性。
  • 方法装饰器:用于装饰类的方法。
  • 访问器装饰器:用于装饰类的访问器(getter/setter)。
  • 参数装饰器:用于装饰方法的参数。

每种装饰器都有自己独特的用法和适用场景,咱们接下来一一介绍。

3.1 类装饰器

类装饰器顾名思义,就是用来装饰整个类的。它接收一个参数,就是被装饰的类本身。

function addPoweredBy(engine) {
return function(target) {
target.poweredBy = engine
}
}
@addPoweredBy('V8')
class Car {
//类定义
}
console.log(Car.poweredBy) //V8

在这个例子中,addPoweredBy 是一个类装饰器,它接收一个参数 engine,并返回一个函数。这个返回的函数接收一个参数 target,也就是被装饰的类 Car。在函数内部,我们给 Car 类添加了一个静态属性 poweredBy,并将其值设置为 engine

3.2 属性装饰器

属性装饰器用于装饰类的属性。它接收两个参数:

  • target:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
  • propertyKey:属性的名称。
function readOnly(target, propertyKey) {
Object.defineProperty(target, propertyKey, {
writable: false
});
}
class Person {
@readOnly
name = 'John Doe';
}
const person = new Person();
person.name = 'Jane Doe'; // TypeError: Cannot assign to read only property 'name' of object

在这个例子中,readOnly 是一个属性装饰器,它将 Person 类的 name 属性设置为只读。当我们试图修改 person.name 的值时,就会抛出类型错误。

3.3 方法装饰器

方法装饰器用于装饰类的方法。它接收三个参数:

  • target:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
  • name:方法的名称。
  • descriptor:方法的属性描述符。
function enumerable(value) {
return function (target, propertyKey, descriptor) {
descriptor.enumerable = value;
};
}
class MyClass {
@enumerable(false)
myMethod() {}
}

在这个例子中,enumerable是一个方法装饰器工厂,它接收一个布尔值value作为参数,返回一个方法装饰器。返回的装饰器函数会将目标方法(myMethod)的属性描述符(descriptor)的enumerable属性设置为传入的value值。

3.4 访问器装饰器

访问器装饰器用于装饰类的访问器(getter/setter)。它的参数和方法装饰器相同。

function configurable(value) {
return function (target, propertyKey, descriptor) {
descriptor.configurable = value;
};
}
class Point {
constructor(x, y) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() { return this._x; }
}

在这个例子中,configurable 是一个访问器装饰器,它将 Point 类的 x 访问器的 configurable 属性设置为 false,这意味着我们不能删除或重新定义这个访问器。

3.5 参数装饰器

参数装饰器用于装饰方法的参数。它接收三个参数:

  • target:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
  • name:方法的名称。
  • parameterIndex:参数在方法参数列表中的索引。
function required(target, name, parameterIndex) {
// ...
}
class MyClass {
myMethod(@required param1, param2) {
// ...
}
}

参数装饰器通常用于实现参数验证、类型检查等功能。由于参数装饰器本身不能修改方法的行为,所以它通常会和方法装饰器或类装饰器一起使用。

4. 装饰器工厂

有时候,我们希望装饰器能够接收一些参数,以便更灵活地控制装饰的行为。这时候,我们就可以使用装饰器工厂。

装饰器工厂就是一个返回装饰器的函数。它接收一些参数,并根据这些参数返回一个定制化的装饰器。

function color(value) {
return function (target) {
target.color = value;
};
}
@color('red')
class MyClass {}
console.log(MyClass.color); // 输出:red

在这个例子中,color 就是一个装饰器工厂。它接收一个参数 value,并返回一个类装饰器。这个类装饰器会将 MyClass 类的 color 属性设置为 value

5. 装饰器的执行顺序

当一个对象上有多个装饰器时,它们的执行顺序是怎样的呢?

  • 对于类装饰器,它们的执行顺序是从下往上(或者说从右往左)。
  • 对于其他类型的装饰器,它们的执行顺序是从上往下(或者说从左往右)。
  • 对于同类型的装饰器,它们的执行顺序也是从上往下(或者说从左往右)。
function d1(target) { console.log('d1'); }
function d2(target) { console.log('d2'); }
@d1
@d2
class MyClass {}
// 输出:
// d2
// d1

6. 装饰器的注意事项

  • 装饰器目前还是一个实验性特性,需要使用 Babel 等工具进行转译才能在浏览器中运行。
  • 装饰器不能用于装饰函数声明或函数表达式,只能用于装饰类和类的方法、属性、访问器、参数。
  • 装饰器只在定义时执行,而不是在运行时执行,所以性能开销很小。

7. 装饰器的应用场景

装饰器在实际开发中有很多应用场景,例如:

  • 日志记录:记录函数的调用时间、参数、返回值等信息。
  • 性能监控:统计函数的执行时间,找出性能瓶颈。
  • 权限控制:检查用户是否有权限执行某个操作。
  • 输入验证:验证函数的参数是否符合要求。
  • 缓存:缓存函数的计算结果,避免重复计算。
  • 注入依赖:自动注入函数所需的依赖项。
  • React/Redux:连接 Redux store,将组件与 Redux 状态管理连接起来。
  • MobX:将类或属性转换为可观察对象。
  • Angular:定义组件、指令、管道等。

总之, 装饰器是一个非常强大的工具,可以帮助你写出更简洁、更优雅、更易于维护的代码。如果你还没有用过装饰器,不妨现在就开始尝试一下,相信你会爱上它的!

希望这篇“接地气”的装饰器详解,能让你对 JavaScript 装饰器有一个更深入的理解。如果你还有什么疑问,或者想了解更多关于装饰器的知识,欢迎随时提问,我会尽力解答。

记住,编程就像装修房子,装饰器就是你的“魔法”工具,用好了它,你的代码就能焕发出不一样的光彩!

技术宅小V JavaScript装饰器Decorator

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/8291