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)

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IMWeb前端团队

ECMAScript 6 新特性总结

本文作者:IMWeb 张颖 原文出处:IMWeb社区 未经同意,禁止转载 前言 个人感觉ECMAScript 6总体上来说:添加了块级作用域,增加了一...

2876
来自专栏逆向技术

C++反汇编第二讲,不同作用域下的构造和析构的识别

               C++反汇编第二讲,不同作用域下的构造和析构的识别 目录大纲:   1.全局(静态)对象的识别,(全局静态全局一样的,都是编译期间...

17510
来自专栏猿人谷

PHP常用库函数介绍+常见疑难问题解答

      最近在苦学PHP,虽然PHP在整体功能上不如Java强大,但相比PHP而言Java算是较重量级的,所以在小中型系统的开发上,使用PHP的趋势不可挡,...

1848
来自专栏GreenLeaves

JavaScript之JSON

一、简介:Json是JavaScript中读取结构化数据更好的方式。因为Json数据可以直接传给eval(),而且不必创建DOM对象。Json是一种数据格式,不...

1867
来自专栏极客编程

ECMAScript 6教程 (二) 对象和函数

上面代码的方法一是直接用标识符作为属性名,方法二是用表达式作为属性名,这时要将表达式放在方括号之内。

854
来自专栏debugeeker的专栏

《coredump问题原理探究》Linux x86版5.8节C风格数据结构内存布局之结构体数组结构体coredump

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

501
来自专栏编程微刊

回调函数路由,模板渲染

1395
来自专栏Coding迪斯尼

自制Monkey编程语言编译器:增加数组操作API和Mapsh数据类型

983
来自专栏学海无涯

5.元组

993
来自专栏xingoo, 一个梦想做发明家的程序员

汇编语言 手记8

栈有两个基本的操作:入栈和出栈 入栈:将一个新的元素放到栈顶 出栈:从栈顶取出一个元素 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。 栈的操作规则:...

1775

扫码关注云+社区