此文已由作者吴佳祥授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

好吧我承认这是篇任务。

最近看到个消息,ES2017已经定稿了,心想,我去,还完全没了解ES2016呢,ES8就定稿了,out了,这可咋办,赶紧Google(Baidu)去!

不过从ES6(2015)之后,tc39的规划是一年一个版本,所以ES7跟ES8也不会像ES6那么大的步子。粗略瞟了一眼,咦,装饰器(Decorator)还没到 Stage 3啊,好吧不过已经到了2了,想必之后还是会慢慢纳入的,就先了解一下吧。与之相关的,就是AOP(面向切面编程)和装饰器模式了。

AOP是对OOP(面向对象编程)的一个横向的补充,主要作用就是把一些业务无关的功能抽离出来,例如日志打印、统计数据、数据验证、安全控制、异常处理等等。这些功能都与某些核心业务无关,但又随处可见,如果都是复制粘贴未免太没逼格,而且难以维护,不优雅。把它们抽离出来,用“动态”插入的方式嵌到各业务逻辑中。这样的好处是业务模块可以变得比较干净,不受污染,同时这些功能点能够得到很好的复用,给模块解耦。

由于语言的特性,在JavaScript中可以轻松地实现AOP技术。

让我们假设一个业务无关的功能--绑定变量:

有三个变量a、b、c,要保证b、c在修改前后,a一直等于b与c的和。

先分析一下,此处的业务无关功能点(即切面)应该就是“a一直等于b与c的和”。即在业务逻辑走完之后,需要重新将b+c赋值给c;同时,我们还应该保证赋给b与c的值应该都是Number类型。

首先,我们先来个ES3版本都可以兼容的办法,使用装饰器模式来实现AOP。

什么叫装饰器模式?即提供一个和原功能一样调用方法的装饰器,装饰器里边植入了切面。由于在JS中函数是一等公民,所以我们可以提供一个装饰器函数来实现AOP。

var a = b = c = 0;

function setA (action) {
    return function (value) {
        action(value);
        a = b + c;
    };
} function validateValue (action) {
    return function (value) {
        if (typeof value !== 'number') {
            throw new Error('你传了个什么鬼进来');
        }
        action(value);
    };
} var setB = validateValue(setA(function (value) {
    b = value;
})); var setC = validateValue(setA(function (value) {
    c = value;
})); setB(10);                    // a === 10;
setC(1);                     // a === 11;
setC('什么鬼');              // Uncaught Error: 你传了个什么鬼进来

用装饰器函数来实现AOP在实际的编程中还是挺有用的,有利于把逻辑划分成更小粒度的模块,同时也符合函数式编程(FP)的思想。

可是这个方法其实有个恶心的地方,我不想把赋值的“=”用函数来代替啊,肿么破?

难(tao)过(yan)的是,JS中没有提供运算符重载的功能。不过ES5中提供了一个新的API可以让我们实现重载“=”运算符-- Object.defineProperty 以及 Object.defineProperties ,相关用法可以点击链接查看。由于这个API的操作对象是一个Object,所以我们可以把a、b、c三个变量包在一个对象中。

var accessorDecorator = (function () {
    var context = {b:0,c:0};
    return {
        set: function (action) {
            return function (value) {
                action.call(this, value);
                wrapper.a = this.b + this.c;
            }.bind(this);
        }.bind(context),
        get: function (action) {
            return action.bind(context);
        }
    };
})(); var wrapper = Object.defineProperties({}, {
    a: {
        value: 0,
        writable: true
    },
    b: {
        set: validateValue(accessorDecorator.set(function (value) {
            this.b = value;
        })),
        get: accessorDecorator.get(function () {
            return this.b;
        })
    },
    c: {
        set: validateValue(accessorDecorator.set(function (value) {
            this.c = value;
        })),
        get: accessorDecorator.get(function () {
            return this.c;
        })
    }
}); function validateValue (action) {
    return function (value) {
        if (typeof value !== 'number') {
            throw new Error('你传了个什么鬼进来');
        }
        action(value);
    };
} wrapper.b = 10;              // wrapper.a === 10;
wrapper.c = 1;               // wrapper.a === 11;
wrapper.c = '什么鬼';        // Uncaught Error: 你传了个什么鬼进来

需要提及的一点是,现在非常流行火热的Vue.js的数据绑定原理就是通过这个实现的。

在文章的开头,还提到了新的ES草案--装饰器语法(Decorator),事实上,这也算是 Object.defineProperty 的一个语法糖。使用装饰器,可以让我们上面的代码变得更简洁(好吧这很JAVA)。

class Wrapper {
    a = 0;     @validateValue
    @setA
    b = 0;     @validateValue
    @setA
    c = 0;
}function validateValue (target, key, descriptor) {    const action = descriptor.set;
    descriptor.set = (value) => {        if (typeof value !== 'number') {            throw new Error('你传了个什么鬼进来');
        }
        action(value);
    };
}function setA (target, key, descriptor) {    const action = descriptor.set;
    descriptor.set = (value) => {
        action(value);
        target.a = target.b + target.c;
    };
}let wrapper = new Wrapper;

更JAVA的是,ES6中提供了 Proxy与 Reflect对象。所以我们的这段代码现在可以这么写:

let validateProxy = new Proxy({a: 0, b: 0, c: 0}, {
    set(target, key, value, receiver) {
        if (key in target && typeof value !== 'number') {
            throw new Error('你传了个什么鬼进来');
        }
        return Reflect.set(target, key, value, receiver);
    }
}); let wrapper = new Proxy(validateProxy, {
    set(target, key, value, receiver) {
        let done = Reflect.set(target, key, value, receiver);
        if (key === 'b' || key === 'c') {
            Reflect.set(target, 'a', target.b + target.c, receiver);
        }
        return done;
    }
});

随着ECMAScript的新标准的定稿,AOP的实现在JavaScript中是越来越容易了。在实际编码中使用AOP和装饰器模式,可以将一些业务无关的代码从业务逻辑中抽离出来,使得业务逻辑更加清晰,不受污染,同时也有利于这些业务无关代码的复用与维护。

网易云免费体验馆,0成本体验20+款云产品!

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 一个体验好的Windows 任务栏缩略图开发心得

用AOP来让你的JS代码变得更有可维护性吧的更多相关文章

  1. Console命令详解,让调试js代码变得更简单

    Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上手.我曾经翻译过一篇<Firebug入门指南>,介绍了一些基本用法.今天,继续介绍它的高级用法. ======= ...

  2. [转] Console命令详解,让调试js代码变得更简单

    http://www.cnblogs.com/see7di/archive/2011/11/21/2257442.html Firebug是网页开发的利器,能够极大地提升工作效率. 但是,它不太容易上 ...

  3. Firebug控制台详解,让调试js代码变得更简单

    http://www.open-open.com/lib/view/open1373120100347.html Firebug是网页开发的利器,能够极大地提升工作效率. Firebug控制台详解 控 ...

  4. 如何让你的JS代码写的更漂亮

    感觉这篇文章总结的js的规范写法不错,拿来收藏.转自:https://mp.weixin.qq.com/s/AtR94IL9BW9EXOTnKOilmA 1. 按强类型风格写代码 JS是弱类型的,但是 ...

  5. 原始的js代码和jquery对比

    Even a task as simple as this can be complicated without jQuery at our disposal. In plain JavaScript ...

  6. js混淆加密,通过混淆Js代码让别人(很难)无法还原

    js混淆加密,通过混淆Js代码让别人(很难)无法还原   使用js的混淆加密,其目的是为了保护我们的前端代码逻辑,对应一些搞技术吃饭的公司来说,为了防止被竞争对手抓取或使用自己的代码,就会考虑如何加密 ...

  7. 几道JS代码手写面试题

    几道JS代码手写面试题   (1) 高阶段函数实现AOP(面向切面编程)    Function.prototype.before = function (beforefn) {        let ...

  8. Firebug调试js代码

    Firebug功能异常强大,不仅可以调试DOM,CSS,还可以调试JS代码,下面介绍一下调试JS. 1.认识console对象 console对象是Firebug内置的对象,该对象可以在代码中写入,可 ...

  9. 浏览器控制台js代码与后台不同步

    原因:浏览器会缓存js 如果是将js代码直接通过<script>标签插入jsp页面中则不存在这个问题 在加载页面的时候会重新加载js代码 如果直接将js代码以文件的形式引入,那么每次在修改 ...

随机推荐

  1. [POI2005]Sza-Template

    Description Byteasar 想在墙上涂一段很长的字符,他为了做这件事从字符的前面一段中截取了一段作为模版. 然后将模版重复喷涂到相应的位置后就得到了他想要的字符序列.一个字符可以被喷涂很 ...

  2. Easy Game LightOJ - 1031

    Easy Game LightOJ - 1031 upd:似乎有复杂度更优越的做法,见http://www.cnblogs.com/hehe54321/p/8431020.html 题意:A和B玩一个 ...

  3. 转 做了两款数据库监控工具(mysql and nosql),打算在近期开源

    http://www.cnblogs.com/leefreeman/p/7297549.html 监控指标:https://www.linuxidc.com/Linux/2015-08/122009. ...

  4. 【python】入门级识别验证码

    前情:这篇文章所提及的内容是博主上个暑假时候做的,一直没有沉下心来把自己的心得写在纸面上,所幸这个假期闲暇时候比较多,想着能写多少是多少,于是就有了此篇. 验证码?我也能破解? 关于验证码的介绍就不多 ...

  5. -bash: mysql: command not found 之 MAC

    第一次尝试: ln -s /usr/local/mysql/bin/mysql /usr/bin/mysql 提示:Operation not permitted 再次,加sudo附上管理员权限,依旧 ...

  6. Spotlight安装

    刚才技术群的一个朋友在安装Spotligh出现了一些问题,所以本人临时写个简单的教程 1.下载安装包(安装包地址:https://pan.baidu.com/s/1c2tmqyc),解压,然后傻瓜式安 ...

  7. 基于jmeter和shell的接口性能自动化

    基于jmeter和shell的接口性能自动化 1. 总体需求 由于性能测试中涉及的查询接口多,版本迭代频繁,版本更新后自动跑一轮查询业务的性能,可以及时发现一些开发修复bug触发的非预期的bug,利用 ...

  8. [译] 用win7自带工具找出svchost.exe的CPU使用率达到100%的元凶

    本文是我对自己上一篇转载的博客 <Figuring out why my SVCHOST.EXE is at 100% CPU without complicated tools in Wind ...

  9. Outlook 数据文件(.pst 和 .ost)简介

    使用 Microsoft Outlook 时,电子邮件.日历.任务和其他项目保存在邮件服务器或计算机上,或者同时保存在这两个位置.如果 Outlook 项目保存在计算机上,则它们保存在 Outlook ...

  10. Android(java)学习笔记179:多媒体之加载大图片到内存(Bitmap API)

    1. Bitmap (API使用) android里面的bitmap中,一个像素点需要4个byte去表示,这是因为android表示颜色是" argb ":其中 a 表示是透明度, ...