专栏首页前端小吉米JS 完美的 AOP 编程

JS 完美的 AOP 编程

看到 decorator这个词的时候,让我回想起了python中的decorator.而,当我看到 decorator中的 @的时候, 我tm确定,这尼玛不就是python吗? 但, too young too naive. es6中的decorator和python很相似,但却又非常的不一样.因为,在js中,decorator是不能用来装饰函数的.(因为有函数提升) so, decorator在js中是用来干嘛的呢?

神马是decorator

decorator是以一种近乎trick的方式,让你写更少的代码,完成更多的事情. 事实上,es 是借助了python中的decorator来,完善自身的语法树. 在es6中,decorator可以用来装饰类 || 类方法 || 类属性。 不过,在修饰类,方法,属性时,会有很大的不同.

decorator修饰类

使用decorator修饰类,他传入的参数代表的是还未实例化的类

// 使用decorator 装饰 类
// @param {class} target_class 相当于就是Target
function dec(target_class){
    target_class.prototype.demo=123
}
@dec
class Target{}

let target = new Target();
console.log(target.demo) // 123

ok, 这是比较简单的demo. 现在我们将decorator的数量增加,假设,现在想要在class上装饰两个decorator. 他们的执行顺序是怎样的呢?

多重decorator

实际让我们以一种通俗的方式, 来讲解一下。 ES6中的decorator和AOP编程中的before很类似. 先看一下运行的实例吧.

function dec_1(){
    console.log(1);
}
function dec_2(){
    console.log(2);
}
@dec_1
@dec_2
class Target{

}
// 执行结果
// 2
// 1
// 执行顺序为: dec_2 => dec_1

说白了, 这其实就是before的一个trick

function before (fn){
    return function(){
        fn()
        // do sth...
    }
}

这样, 你就可以在执行函数时,不断嵌套很多层的内容了. 或者更好的说,es6中的decorator是以一种由下到上的方式, 来执行装饰的内容的.

修饰属性

使用decorator修饰类中的属性, 其实和 Object.defineProperty的行为一模一样.可以对类中的属性访问做一下限制.(感觉和Proxy差多啊) 所以, 这实际上是限制了decorator不能用来作为参数处理的一个trick. 但作为属性限制,也是极好用的. 修饰属性的decorator接受三个参数.

/**
 * 装饰者
 * @param  {Object} 类为实例化的工厂类对象
 * @param  {String} name   修饰的属性名
 * @param  {Object} desc   描述对象
 * @return {descr}        返回一个新的描述对象
 */
function decorator(target,name,desc){
}

所以, 由于一开始设计decorator的初衷就是为了可拆卸化保护属性. 如果大家,对于属性的了解有疑问的话,可以参考我前面一篇的文章.es中的属性. 接下来,我们可以利用decorator来简单的写一个readonly的保护权限.

function readonly(target,key,desc){
    desc.writable=false;
    return desc;
}
class Target{
    @readonly
    demo=123;
}
new Target().demo=345; // 报错

所以, 针对于这一特性.我们还可以写很多其他的trick. 比如, 无法被for...in...遍历的属性, 无法使用delete删除的属性.

function noLoop(target,key,desc){
    desc.Enumerable=false;
    return desc;
}
function banDelete(target,key,desc){
    desc.configurable = false;
    return desc;
}

动态修改参数

有时候想要对函数传参做相关的属性和格式化设置,使用 decorator 的模式就非常方便了。前面已经知道,修饰一个函数可以获得的参数有:

/**
 * 装饰者
 * @param  {Object} 类为实例化的工厂类对象
 * @param  {String} name   修饰的属性名
 * @param  {Object} desc   描述对象
 * @return {descr}        返回一个新的描述对象
 */
function decorator(target,name,desc){
}

在这里对函数进行修饰,主要使用的是 desc 该对象。要对函数参数修改的基本步骤为:

  • 保留原始函数
  • 重新定义函数,使用 ...args 参数获取传参
  • 通过 apply 调用原始函数

现在,我们想对一个 Class Dog 的 setting 方法属性进行重新定义。原始 Dog 类为:

class Dog{
    constructor(name){
        this.setting(name)
    }

    setting(name){
        this._name = name;
    }
}

这里,我们额外增加一个 decoratorSetting 函数:

function decorateSetting(target, key, descriptor) {
  const method = descriptor.value; // 保存原始函数
  descriptor.value = (...args)=>{
  // 动态添加 villainhr- 的前缀
    args[0] = "villainhr-" + args[0]; // this is parameter (name)

    // 通过 apply 方法调用原始函数
    return method.apply(target, args);

  }
  return descriptor;
}

然后,使用 decorateSetting 修饰 Dog 类中的 setting 方法。

class Dog{
    constructor(name){
        this.setting(name)
    }
    @decorateSetting
    setting(name){
        this._name = name;
    }
}

然后运行一下,可以得到:

let pony = new Dog('pony'); // pony.name is villainhr-pony

到这里,也就成功的对函数进行了修饰。如果想线上运行,可以使用:babel-decorator online repr。

ok, descorator的最好用的部分,上面已经差不多参数清楚了。最后,我们在补充一下,使用 decorator 必要的 babel 转码配置。

babel 转码配置

直接贴代码了:

// 先使用 npm 下载额外的包
npm i --save-dev babel-plugin-transform-decorators-legacy

// webpack 配置文件
{
  test: /\.jsx?$/,
  loader: 'babel',
  query: {
    plugins: ['transform-decorators-legacy' ],
    presets: ['es2015', 'stage-0', 'react']
  }
}

// babelrc 配置文件
{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": [
    ["transform-decorators-legacy"],
  ]
}

如果,大家有什么疑问. 或者有什么新的用法,可以直接楼下回复.

本文分享自微信公众号 - 前端小吉米(villainThr),作者:villainhr

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 冲顶大会有前端什么事吗?

    villainhr
  • 如何写出一手好的小程序之多端架构篇

    为了大家能更好的开发出一些高质量、高性能的小程序,这里带大家理解一下小程序在不同端上架构体系的区分,更好的让大家理解小程序一些特有的代码写作方式。

    villainhr
  • 不再碎片化学习,快速掌握 H5 直播技术

    villainhr
  • Hibernate EntityManager

    Java Persistence API(JPA)是EJB3.0规范之一,定义了对数据库数据进行持久化操作的接口。HIbernate

    Hongten
  • Struts2 Wildcard 和DMI

    关于Struts2 Action中的最基本method配置我就不说,那个比较死板。关于这个method有两个动态调用Action方法的方法:WildCard(...

    the5fire
  • ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件

    虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上,不过我们依...

    蒋金楠
  • SpringBoot系列之@PropertySource读取yaml文件

    最近在做实验,想通过@PropertySource注解读取配置文件的属性,进行映射,习惯上用properties都是测试没问题的,偶然换成yaml文件,发现都读...

    用户1208223
  • 实现导航栏下拉菜单列表页面

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    林老师带你学编程
  • java的枚举enum根据code获取name等

    DencyCheng
  • MySQL一些花样招式

    AlicFeng

扫码关注云+社区

领取腾讯云代金券