认识装饰器
ECMAScript的装饰器,@Decorator,其实也是借鉴其他语言的特性(如Python),不同语言之间有很多特性是相互借鉴的,这就是开源的好处,让各家能取长补短。本文只讨论函数装饰器,因为在js中函数装饰器会有问题,下面步入正题:什么是装饰器
从字面上看,装饰器 大概就是起一个锦上添花的作用吧,确是如此。所谓的装饰器本质上是一个函数,能够帮助类、类方法、函数增强能力的函数,同时提高代码复用性。
Python里的装饰器
翻看阮一峰大神的关于装饰器的教程,得知js里的装饰器并不适用于函数,因为存在函数提升,会让装饰器失效。
在Python中我们如何定义一个装饰器呢,如下
def logger(func):
def wrapper():
print("%s is running" % func.__name__)
return func()
return wrapper
@logger
def foo():
print("i am foo")
foo()
# 输出:
# foo is running
# i am foo
以上logger函数就是foo函数的装饰器,作用是在foo函数被调用时,打印foo is running的信息。在foo函数上面写上@logger就相当于foo = logger(foo)
实质上就是为foo函数包装了一层,在不改变foo函数内部的前提下,为其添加了在被调用时输出信息的功能。
Javascript里的装饰器
我们模仿上述Python的语法写如下js代码,当然需要babel的支持
var logger = function(func) {
return function() {
console.log(`${func.name} is running`);
return func();
}
}
@logger
function foo() {
console.log("i am foo");
}
foo()
// 报错
// repl: Leading decorators must be attached to a class declaration (9:0)
7 |
8 | @logger
> 9 | function foo() {
| ^
10 | console.log("i am foo");
11 | }
12 |
babel禁止了装饰器装饰函数的行为,但是我们知道@logger实质上的行为是foo = logger(foo),logger(foo)返回高阶函数,执行foo的时候就先会执行高阶函数的第一行代码即:console.log(`${func.name} is running`)
扩展
观察以上js代码,现在我们不止需要装饰器为函数打印信息,还需要做别的事情,并且我们的装饰器还需要能够传参数,看如下代码:
// 模拟@的功能
const decorate = function(target, decorator, decorator_params) {
return function() {
let args = [];
for (let i = 0, len = arguments.length; i < len; i++) {
args.push(arguments[i]);
}
decorator_params && args.push(decorator_params);
decorator.apply(target, args);
return target.apply(this, arguments);
}
}
// 需要装饰器干的事情
const logger = function(who) {
console.log(`${this.name} is running, called by ${who}`);
// do something
console.warn('我需要做更多的事');
}
function foo() {
console.log("i am foo");
}
foo = decorate(foo, logger, 'HandsomeWalker');
foo();
// 输出
// foo is running, called by HandsomeWalker
// 我需要做更多的事
// i am foo
总结
在实际应用场景中使用装饰器会很有用处,提升函数的扩展性,提高代码复用性。比如你需要在你所有的方法调用之前检查形参的个数,必须传递一定数量的参数,少了就报错。此时你就可以在不更改原有的函数代码的情况下,为原有函数加装装饰器函数,就达到了目的。
本文为个人心得体会,有错误是很正常的,欢迎指正