喜欢 JS Decorators 那点事

曾经一直在嵌入式数通领域混迹着,之后移民到新西兰发现这些都没有了用武之地。两个月前决定开始学习前端,看了很多新的知识点。当然JS 是不得不学的一个语言,今天就讲讲Decorators的运用。在这篇文字里,我将一点点的抽丝剥茧来阐述Decorators,以至于一点点的把它的美展现出来。

如果你有过Python 的深厚背景的话,这个语法和思想应该是一见如故的感觉。学起来相对会简单很多。但是对于我这种只有过C背景的菜鸟来说 相对有点难度。不过要感谢 w3Schools。很多的语言的网站开发的语言概述 我基本上都是从上面看的。查了一下 Decorators 应该是 Typescript 里面的概念。

我一开始直接学习的 是 RN,所以对 typescript 和 Angular2 并没有太大的感觉 。但是当我读了不少代码的时候 我慢慢的发现很多的库文件都开始用typescript来编写了。还是那么的优美  我感觉是时候要来学习他们了。上周五 我非常好奇的学习了这个在ES7里面的新功能 Decorators. 我发现我有点迷恋上了它。它性感迷人的以至于我们不得不想方设法的把它运用到个个地方.  因为没有发现好的文章去写它,所以我决定把我学习的历程写出来,希望对想学习的朋友们有所帮助。

首先:Decorators 到底是一个什么东西?

下图是我从网上查到的 Decorators pattern 的一些信息:


是不是有点抽象? 那我们来个具体的模型 :



WeaponAccessory 在这里就是一个 decorator.   他们给BaseWeapon 增加了额外的功. 如果还没有看明白 不用紧张 我第一遍也没看明白。 多看几遍就好了。这里我们先给Decorators下一个定义:  Decorators are functions that give additional super-powers to different function。从这个定义中我们获知: 1. Decorators 是功能  2. 这个功能是增强了额外的超能量 给被修饰的功能。想想也是  Decorators的中文意思 就是 修饰,装修的意思。装修一个东西 就是让这个东西更好看 更美丽 更实用。是吧?

一句话: 就把它认为是一个object的一次外包装。

1.  实际的 object 是被 wrapper 封装住的

2. 如果你想访问一个object ,那你一定要先访问 它的 封装wrapper

Decorators 让我们可以注释和更改类以及类的属性 再设计阶段 

请注意: Decorators 仅仅只是函数

其次:我们到底怎么运用它呢?

Decorators 是在ES7里面提出的一个功能点  在这里我们将使用Babel 和 Typescript 来实现的编译器来实现他们 (在本文中 我将用 Typescript )

1. 怎么设置?

在你的 tsconfig.json 这个文件里面, 你应该把  experimentalDecorators 和 emitDecoratorMetadata 设置为 true.

{"compilerOptions":{

"module":"commonjs",

"experimentalDecorators":true,

"emitDecoratorMetadata":true,

"target":"es2015"

},

"exclude":[

    "node_modules"]

}

2.  闭嘴,观察

为了让事情简单话,我一直遵循这一个学习方法那就是:少说多做.  我会不管三七二十一先不停的写代码,关注输出,然后再去思考 

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {

    var oldValue = descriptor.value;

    descriptor.value = function() {

      console.log(`Calling "${propertyKey}" with`, arguments,target);

      let value = oldValue.apply(null, [arguments[1], arguments[0]]);

      console.log(`Function is executed`);

      return value + "; This is awesome";

    };

    return descriptor;

  }

  class JSMeetup {

    speaker = "Ruban";

    //@leDecorator

    welcome(arg1, arg2) {

      console.log(`Arguments Received are ${arg1} ${arg2}`);

      return `${arg1} ${arg2}`;

    }

  }

  const meetup = new JSMeetup();

  console.log(meetup.welcome("World", "Hello"));

一旦你运行了上面的代码 他们的输出会是:  

Arguments Received are World Hello 

World Hello

现在把代码里面的// 去掉,再运行它结果会是: 

Calling "welcome" with { '0': 'World', '1': 'Hello' } JSMeetup {}

 Arguments Received are Hello World

 Function is executed Hello World; 

This is awesome

是不是觉得很神奇?你只是用Decorator 注释了一个函数,结果就截然不同了

这到底为什么呢? 让我们一起揭开他们的面纱吧。

 Decorators的类型:

1.方法修饰( Method Decorator  )

2. 属性修饰(Property Decorators )

3.  类修饰(Class Decorator)

4. 参数修饰(Parameter Decorator  )

好了 不要急, 让我们一个个的来揭示其中的奥妙吧。(为了方便下面标题只写英文)

Method Decorators 

Method Decorators是第一个我们将 要 一起学习的修饰类型. 我们将会学习到它的一些本质。通过使用Method Decorators 我们将会学会很好的掌控输入函数和输出函数. 

修饰签名(Decorator Signature)

MethodDecorator = (target: Object, key: string, descriptor: TypedPropertyDescriptor) => TypedPropertyDescriptor | Void;

前件:

当我们学习写第一个Decorators,让我们一起学习一下基本知识:.

理解Decorators的参数

1. target -> 被修饰的对象

2. key -> 被修饰方法的名字

3  descriptor -> 给定的属性的标识符.  你可以看到这个属性标识符通过调用 这个函数: functionObject.getOwnPropertyDescriptor() 

Property Descriptors: 

看下面的输出结果 可以更好的有一个直观的概念:


着这里我们定义 一个object ,命名为o,其包含属性:  foo , bar 和 foobar.  然后我们打印每一个属性的 属性描述标识 用 Object.getOwnPropertyDescriptor().  在这里让我们快速过一下value , enumerable , configurable 和 writable 的意思.

value – > 实际的数值 或者函数 被这个object 属性拥有的

enumerable -> 是否这么属性可以被枚举。可能在便利的时候会被用到

configurable – >这个属性是否可以被配置

writable -> 这个属性是否可以被写入

正如我们所发现,所有的属性和方法都有他们自己独有的描述。Decorators会更改在这个描述去添加额外的功能。当我们知道了怎么更改和定义这些属性的时候,我们就可以用Decorators 去做一些改变了

实例( Method Decorator)

在下面的例子里,我们将去更改输入和输出的方法:

function leDecorator(target, propertyKey: string, descriptor: PropertyDescriptor): any {

    var oldValue = descriptor.value;

    descriptor.value = function() {

      console.log(`Calling "${propertyKey}" with`, arguments,target);

      // Executing the original function interchanging the arguments

      let value = oldValue.apply(null, [arguments[1], arguments[0]]);

      //returning a modified value

      return value + "; This is awesome";

    };

    return descriptor;

  }

  class JSMeetup {

    speaker = "Ruban";

    //@leDecorator

    welcome(arg1, arg2) {

      console.log(`Arguments Received are ${arg1}, ${arg2}`);

      return `${arg1} ${arg2}`;

    }

  }

  const meetup = new JSMeetup();

  console.log(meetup.welcome("World", "Hello"));

如果我们run 上面的代码(Decorators被注释掉的情况下), 输出如下:

Arguments Received are World, Hello

World Hello

但是当我们使用Decorators的时候(去掉//): 

Arguments Received are Hello, World

Hello World; This is awesome

到底发生了什么?

在这个修饰函数中,这个描述标识 包含了与它相关的特定的属性(前面我们讲过). 首先,我们把原始函数保存到一个变量中  (var oldValue = descriptor.value; ) . 然后,我们更改这个描述标识中的值,然后把它返回. 正如我们所见的更改函数,我们执行了原始函数但是更改了参数,同时返回了更改结果. 

是不是很神奇?

Decorators 包装了我们的方法. 通过使用Decorators, 我们基本上有能力掌控输入哈数和输出函数. 我们是不是很有魔法? 哈哈

需要深思的几点:

1. Decorators 被调用是Class 被声明的时候,而不是被实例化的时候。要是后者的话 那就是虚函数了  哈哈   C++的 

2. 函数Decorators 会返回一个值

3. 正如上面的例子,最好是先保存存在的标识值,然后返回一个新的。原因是当多Decorators的时候 其他的Decorators 有可能无法访问原始的东西

4. 不要使用箭头语法当设置标识里面的变量的时候

我们能用Decorators 做点什么呢?

1. 做Log

2. 格式化

3.  权限检测

4.  屏蔽复写的函数 

5. 打时间戳

6. 流量限制

哈哈 当然 可能还有很多其他的用法。我们可以继续追加。。。。。

在我们结束之前 我们再过一遍主题思想: 

Decorator Factory

我们可以通过使用 decorator factories 去定制化的实现我们想要的功能 :

function leDecoratorFactory(randomData: string) {

    return (target, propertyKey: string, descriptor: PropertyDescriptor): any => {

      var oldValue = descriptor.value;

      descriptor.value = function () {

        console.log(`Calling "${propertyKey}" with`, arguments, target);

        let value = oldValue.apply(null, [arguments[1], arguments[0]]);

        console.log(`Function is executed`);

        return value + "; This is awesome; " + randomData;

      }

      return descriptor;

    }

  }

  class JSMeetup {

    speaker = "Ruban";

    @leDecoratorFactory("Extra Data")

    welcome(arg1, arg2) {

      console.log(`Arguments Received are ${arg1} ${arg2}`);

      return `${arg1} ${arg2}`;

    }

  }

  const meetup = new JSMeetup();

  console.log(meetup.welcome("World", "Hello"));

上面的代码的输出如下:(仔细看看 和前一个例子有什么微小的差别,找到了吗? cheer!!!!!!!!!!!!)

Arguments Received are Hello World

Hello World; This is awesome; Extra Data

总结?

Decorator Factory 可以接受用户的参数并且返回一个修饰函数.

我们可以不用再去创建相同的修饰函数了

我们可以通过一个点去访问多个不同的Decorators

重点:

Decorators 是从上到下的评估

Decorators 是从下到上的执行

Property Decorators

Property decorators 和上面讲的方法修饰很相似. 我们可以通过她去从新定义 getters,setters, 还有其他的属性类似枚举,配置等

修饰签名:

PropertyDecorator = (target: Object, key: string) => void;

前提条件:

为了更好的礼节和写属性修饰,我们需要深入了解 Object.defineProperty



思考点:

1. 没有返回值

 2. defineProperty的使用

用途:

1. 更改数据

2. 验证合法性

3. 格式化

Class Decorators

Class Decorators 基本上是更改类的构造函数. 通过使用类修饰,我们可以更改或者添加新的属性,方法 到类里面去

修饰签名: 

ClassDecorator = (target: TFunction) => TFunction;

例子: 

function AwesomeMeetup(constructor: T) {

    return class extends constructor implements extra {

      speaker: string = "Ragularuban";

      extra = "Tadah!";

    }

  }

  //@AwesomeMeetup

  class JSMeetup {

    public speaker = "Ruban";

    constructor() {

    }

    greet() {

      return "Hi, I'm " + this.speaker;

    }

  }

  interface extra {

    extra: string;

  }

  const meetup = new JSMeetup() as JSMeetup & extra;

  console.log(meetup.greet());

  console.log(meetup.extra);

如果不开启修饰类的话 结果如下:

Hi, I’m Ruban

undefined

开启修饰类的结果如下:

Hi, I’m Ragularuban

Tadah!

由此可见类修饰接受一个参数,就是它自己. 我们可以通过它更改这个类 .

思考点:

当原始函数的构造函数被调用的时候 这个扩展类就会被调用

场景:

记录

任何你想更改的东西

Parameter Decorator:

到目前为止,我们可以认为参数修饰是被用作改变函数的参数。但是 还是有点小不同的  

它可以用来标记需要受注意的参数. 我们标记这些参数,然后用函数修饰去做动作.

修饰签名: 

ParameterDecorator = (target: Object, propertyKey: string, parameterIndex: number) => void;

例子:


function logParameter(target: any, key: string, index: number) {

    var metadataKey = `myMetaData`;

    if (Array.isArray(target[metadataKey])) {

      target[metadataKey].push(index);

    }

    else {

      target[metadataKey] = [index];

    }

  }

  function logMethod(target, key: string, descriptor: any): any {

    var originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {

      var metadataKey = `myMetaData`;

      var indices = target[metadataKey];

      console.log('indices', indices);

      for (var i = 0; i < args.length; i++) {

        if (indices.indexOf(i) !== -1) {

          console.log("Found a marked parameter at index" + i);

          args[i] = "Abrakadabra";

        }

      }

      var result = originalMethod.apply(this, args);

      return result;

    }

    return descriptor;

  }

  class JSMeetup {

    //@logMethod

    public saySomething(something: string, @logParameter somethingElse: string): string {

      return something + " : " + somethingElse;

    }

  }

  let meetup = new JSMeetup();

  console.log(meetup.saySomething("something", "Something Else"));

没有修饰的结果:

something : Something Else

修饰后的结果:

something : Abrakadabra

发生了什么?

Parameter Decorator 有三个参数(target, key,index).  在参数修饰中 , 我们基本上标记出参数再一个数组中 (myMetaData ). 

然后,我们使用函数修饰来读取这些标识的参数(in myMetaData),最后更改这些参数  

思考点:

没有返回值

通常不不能单独使用

使用:

依赖注入

标记参数然后进行判断合法性

好了 今天就到此结束了。

最后给几个不错的开源项目 拜读一下大师怎么具体使用的 :

Core Decorators

Lo-Dash Decorators

Routing Controllers

TypeDI – Dependency Injection based on Typescript and Decorators

TypeORM – ORM based on Typescript and Decorators

Class Validator

Class Transformer

由于是第一次写东西. 写的不好的地方 请多多指教。 希望对大家有帮助  

Fred  

奥克兰

你可能感兴趣的:(喜欢 JS Decorators 那点事)