资讯专栏INFORMATION COLUMN

5 分钟即可掌握的 JavaScript 装饰者模式与 AOP

chunquedong / 1019人阅读

摘要:下装饰者的实现了解了装饰者模式和的概念之后,我们写一段能够兼容的代码来实现装饰者模式原函数拍照片定义函数装饰函数加滤镜用装饰函数装饰原函数这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过函数来添加。

什么是装饰者模式

当我们拍了一张照片准备发朋友圈时,许多小伙伴会选择给照片加上滤镜。同一张照片、不同的滤镜组合起来就会有不同的体验。这里实际上就应用了装饰者模式:是通过滤镜装饰了照片。在不改变对象(照片)的情况下动态的为其添加功能(滤镜)。

需要注意的是:由于 JavaScript 语言动态的特性,我们很容易就能改变某个对象(JavaScript 中函数是一等公民)。但是我们要尽量避免直接改写某个函数,这会导致代码的可维护性、可扩展性变差,甚至会污染其他业务。

什么是 AOP

想必大家对"餐前洗手、饭后漱口"都不陌生。这句标语其实就是 AOP 在生活中的例子:吃饭这个动作相当于切点,我们可以在这个切点前、后插入其它如洗手等动作。

AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。利用AOP可以对业务逻辑的各个部分进行隔离,也可以隔离业务无关的功能比如日志上报、异常处理等,从而使得业务逻辑各部分之间的耦合度降低,提高业务无关的功能的复用性,也就提高了开发的效率。

在 JavaScript 中,我们可以通过装饰者模式来实现 AOP,但是两者并不是一个维度的概念。 AOP 是一种编程范式,而装饰者是一种设计模式。

ES3 下装饰者的实现

了解了装饰者模式和 AOP 的概念之后,我们写一段能够兼容 ES3 的代码来实现装饰者模式:

// 原函数
var takePhoto =function(){
    console.log("拍照片");
}
// 定义 aop 函数
var after=function( fn, afterfn ){ 
    return function(){
        let res = fn.apply( this, arguments ); 
        afterfn.apply( this, arguments );
        return res;
    }
}
// 装饰函数
var addFilter=function(){
    console.log("加滤镜");
}
// 用装饰函数装饰原函数
takePhoto=after(takePhoto,addFilter);

takePhoto();

这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过aop函数after来添加。

ES5 下装饰者的实现

在 ES5 中引入了Object.defineProperty,我们可以更方便的给对象添加属性:

let takePhoto = function () {
    console.log("拍照片");
}
// 给 takePhoto 添加属性 after
Object.defineProperty(takePhoto, "after", {
    writable: true,
    value: function () {
        console.log("加滤镜");
    },
});
// 给 takePhoto 添加属性 before
Object.defineProperty(takePhoto, "before", {
    writable: true,
    value: function () {
        console.log("打开相机");
    },
});
// 包装方法
let aop = function (fn) {
    return function () {
        fn.before()
        fn()
        fn.after()
    }
}

takePhoto = aop(takePhoto)
takePhoto()
基于原型链和类的装饰者实现

我们知道,在 JavaScript 中,函数也好,类也好都有着自己的原型,通过原型链我们也能够很方便的动态扩展,以下是基于原型链的写法:

class Test {
    takePhoto() {
        console.log("拍照");
    }
}
// after AOP
function after(target, action, fn) {
    let old = target.prototype[action];
    if (old) {
        target.prototype[action] = function () {
            let self = this;
            fn.bind(self);
            fn(handle);
        }
    }
}
// 用 AOP 函数修饰原函数
after(Test, "takePhoto", () => {
    console.log("添加滤镜");
});

let t = new Test();
t.takePhoto();
使用 ES7 修饰器实现装饰者

在 ES7 中引入了@decorator 修饰器的提案,参考阮一峰的文章)。修饰器是一个函数,用来修改类的行为。目前Babel转码器已经支持。注意修饰器只能装饰类或者类属性、方法。三者的具体区别请参考 MDN Object.defineProperty ;而 TypeScript 的实现又有所不同:TypeScript Decorator。

接下来我们通过修饰器来实现对方法的装饰:

function after(target, key, desc) {
    const { value } = desc;
    desc.value = function (...args) {
        let res = value.apply(this, args);
        console.log("加滤镜")
        return res;
    }
    return desc;
}

class Test{
    @after
    takePhoto(){
        console.log("拍照")
    }
}

let t = new Test()
t.takePhoto()

可以看到,使用修饰器的代码非常简洁明了。

场景:性能上报

装饰者模式可以应用在很多场景,典型的场景是记录某异步请求请求耗时的性能数据并上报:

function report(target, key, desc) {
    const { value } = desc;
    desc.value = async function (...args) {
        let start = Date.now();
        let res = await value.apply(this, args);
        let millis = Date.now()-start;
        // 上报代码
        return res;
    }
    return desc;
}

class Test{
    @report
    getData(url){
    // fetch 代码
    }
}

let t = new Test()
t.getData()

这样使用@report修饰后的代码就会上报请求所消耗的时间。扩展或者修改report函数不会影响业务代码,反之亦然。

场景:异常处理

我们可以对原有代码进行简单的异常处理,而无需侵入式的修改:

function handleError(target, key, desc) {
    const { value } = desc;
    desc.value = async function (...args) {
        let res;
        try{
            res = await value.apply(this, args);
        }catch(err){
            // 异常处理
            logger.error(err)
        }
        return res;
    }
    return desc;
}

class Test{
    @handleError
    getData(url){
        // fetch 代码
    }
}

let t = new Test()
t.getData()

通过以上两个示例我们可以看到,修饰器的定义很简单,功能却非常强大。

小结

我们一步一步通过高阶函数、原型链、Object.defineProperty@Decorator分别实现了装饰者模式。接下来在回顾一下:

装饰者模式非常适合给业务代码附加非业务相关功能(如日志上报),就如同给照片加滤镜;

装饰者模式非常适合无痛扩展别人的代码(你经常需要接手别人的项目吧)

有些朋友可能会觉得装饰者模式和 vue 的 mixin 机制很像,其实他们都是“开放-封闭原则”和“单一职责原则”的体现。

附上代码 jsbin 地址:

ES3

ES5

原型链

ES7

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104942.html

相关文章

  • javascript设计模式--装饰模式

    摘要:装饰者模式定义装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。 装饰者模式 定义 : 装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。 在不改变对象自身的基础上,在程序运行期间给对象动态地添加一些额外职责 特点 : 可以动态的...

    haoguo 评论0 收藏0
  • JavaScript装饰模式

    摘要:用户名不能为空密码不能为空校验未通过使用优化代码返回的情况直接,不再执行后面的原函数用户名不能为空密码不能为空 本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于设计模式的看法,推荐看看本书作者的建议。 什么是装饰者模式? 给对象动态增加职责的方式成为装饰者模式。 装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活...

    Taste 评论0 收藏0
  • 面试官:“谈谈Spring中都用到了那些设计模式?”。

    摘要:会一直完善下去,欢迎建议和指导,同时也欢迎中用到了那些设计模式中用到了那些设计模式这两个问题,在面试中比较常见。工厂设计模式使用工厂模式可以通过或创建对象。 我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 41k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://github.com/Snailclimb... JDK 中用到了那...

    Astrian 评论0 收藏0
  • 学学AOP装饰模式

    摘要:但是,这样做的后果就是,我们会不断的改变本体,就像把凤姐送去做整形手术一样。在中,我们叫做引用装饰。所以,这里引入的装饰模式装饰亲切,熟悉,完美。实例讲解装饰上面那个例子,只能算是装饰模式的一个不起眼的角落。 装饰者,英文名叫decorator. 所谓的装饰,从字面可以很容易的理解出,就是给 土肥圆,化个妆,华丽的转身为白富美,但本体还是土肥圆。 说人话.咳咳~ 在js里面一切都是对...

    nihao 评论0 收藏0
  • javascript设计模式 --- 装饰模式

    摘要:设计模式装饰者模式何为装饰者,意思就是,在不影响对象主功能的情况下,再添加一些额外的功能,使对象具备更多的功能。与继承相比,装饰者是一种更灵活轻便的做法。 javascript设计模式 --- 装饰者模式 何为装饰者,意思就是,在不影响对象主功能的情况下,再添加一些额外的功能,使对象具备更多的功能。与继承相比,装饰者是一种更灵活轻便的做法。下面我们看看javascript里装饰者模式 ...

    kumfo 评论0 收藏0

发表评论

0条评论

chunquedong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<