动手实现js函数装饰器

js中的函数装饰器

Posted by HandsomeWalker on September 28, 2018

认识装饰器

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

总结

在实际应用场景中使用装饰器会很有用处,提升函数的扩展性,提高代码复用性。比如你需要在你所有的方法调用之前检查形参的个数,必须传递一定数量的参数,少了就报错。此时你就可以在不更改原有的函数代码的情况下,为原有函数加装装饰器函数,就达到了目的。

本文为个人心得体会,有错误是很正常的,欢迎指正