Angular装饰器——两种实现方式

选中文字可对指定文章内容进行评论啦,绿色背景文字可以点击查看评论额。

装饰器主要作用

我们知道装饰器的两个主要作用

1、在运行时更改对象的功能而不影响对象的现有功能

2、将通用行为包装成简单,可复用的代码片段,减少模板代码的数量

装饰器的定义

我们首先看看定义:

Decorators are functions called on classes, class elements, or other JavaScript syntax forms during definition. Decorators have three primary capabilities: They can replace the value that is being decorated with a matching value that has the same semantics. They can associate metadata with the value that is being decorated. This metadata can then be read externally and used for metaprogramming and introspection. They can provide access to the value that is being decorated, via metadata.

首先,我们知道装饰器是一个函数,而且定义在类,类元素(方法或者属性),其他js语法形式上。装饰器有三个主要能力:

1、用具有相同语义的配置值取代修饰的值。(属于改变行为功能)

2、将元数据与被修饰的值相关联,然后可以从外部读取此元数据并用于元编程和自我检查。(属于改变行为功能)

3、通过元数据提供对修饰的值的访问。

 

装饰器的技术本质

装饰器在本质上是一个高阶函数,它接受一个函数作为参数,返回另一个函数作为返回值。

从上一节我们知道装饰器有四类:类装饰器,属性装饰器,方法装饰器和访问装饰器。

今天我们学着使用两种方式来实现装饰器。

业务场景

我有一个提醒的类,属性包括提醒消息,提醒类型,方法有将提醒消息打印出来。

基于函数实现装饰器

代码如下:

export class Notifications{

    type:string;

    message:string;

    constructor() {
        this.type = 'Success';
        this.message = 'I have a dream';
    }

    notifyUser = function(){
        console.log(`${this.type} notification: ${this.message}`);
    }
}

现在我们使用装饰增强此类的功能,提供一个延迟若干秒后打印消息的功能,注意,我不会修改原始类额。

增加一个高阶函数

export const delayMiliseconds = (fn: Function, delay:number = 0) => () => {
    setTimeout(() => fn(), delay);
    return 'notifyUser is called';
};

此时调用执行一下:

const notification = new Notifications();
notification.notifyUser();
const delayNotification = delayMiliseconds(notification.notifyUser,3000);
delayNotification();

我们发现的确会延迟3秒执行,但是会报一个错误:

原因是返回的那个函数的this属性不在指向原始的对象,因此找不到type属性,我们修改一下

export class DelayedNotification {
    type: string;

    message:string;

    constructor(type) {
        this.type = type;
        this.message = 'I have a dream';
    }

    public notifyUser = delayMiliseconds(() => {
        console.log(`${this.type} notification: ${this.message} checkTime:`, new Date().getSeconds());
    }, 3000);
}

调用一下试试:

const notification = new Notifications();
notification.notifyUser();
const delayNotification = new DelayedNotification('Success');
delayNotification.notifyUser();

打印日志如下:

嗯,现在起到了增强原始类的作用。有一个问题,这个类并不通用,因为我不能每次增强都要重新创建一个类吧。

因此,我做了一个变通:

export function functionBasedNotificationFactory() {
	const type = 'Success';

	 function notifyUser() {
		console.log(`${type} notification`);
	}

	return {
		name: 'Success',
		notifyUser: delayMiliseconds(notifyUser, 300)
	}
}

我对基础类做一个扩展,,然后调用:

functionBasedNotificationFactory().notifyUser();

可以看出,我也起到了同样的效果,这次不用再增加一个类啦,其实这里的本质是用返回的对象装饰了老对象的notifyUser方法。虽然起到了装饰的作用,似乎还是不那么通用。

 

基于类实现装饰器

首先我们定义装饰器工厂方法:

export function delayMiliseconds( milliseconds: number = 0 ) {
    return function (
      target: Object, 
      propertyKey: string | symbol,
      descriptor: PropertyDescriptor
    ) {
  
     const originalMethod = descriptor.value;
  
     descriptor.value = function (...args) {
            setTimeout(() => {
              originalMethod.call(this, ...args);
             }, milliseconds); 
          };
  
      return descriptor;
    };
}

可以看到,我们对装饰器的属性描述符进行了扩展,在延迟一定的时间后执行原始函数。

我们解释一下返回的函数的三个参数:

  • target -  either the constructor function of the class for a static member or the prototype of the class for an instance member. In our example, Notification.prototype would be a target.
  • propertyKey - the method name that is being decorated.
  • PropertyDescriptor - describes a property on an [Object]:

target对应一个类的构造器或者实例成员的原型,propertyKey对应装饰成员的名字,而PrppertyDescriptor对应描述一个成员的属性对象。

我们可以看看PropertyDescriptor的定义:


interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get? (): any;
    set? (v: any): void;
}

我们的代码对value进行了扩展。

使用我们的装饰器:

export class Notifications{

    type:string;

    message:string;

    constructor() {
        this.type = 'Success';
        this.message = 'I have a dream';
    }

    @delayMiliseconds(3000)
    notifyUser() : void {
        console.log(`${this.type} notification: ${this.message} checkTime:`,new Date().getSeconds());
    }
}

//调用
const notification = new Notifications();
notification.notifyUser();

 

所以使用基于类的方式来实现装饰器会有更好的扩展性。下一节我们尝试来实现一个复杂的装饰器。

 

版权声明:著作权归作者所有。

相关推荐

使用Node.js实现一个简单的web服务器

这是使用Node.js写的一个简单Web服务器示例,分为三部分:响应http请求路由url读取静态文件响应新建一个app.js文件作为此web服务器的入口。响应http请求首先我们引入http模块,创建一个http服务器。const http = require('http'); const hostname = '127.0.0.1'

使用redux-actions重构redux实现的计数器

在很多Redux入门的文章里经常会用到计数器的示例,这里使用redux-action对redux实现的计数器做下重构,以便了解下redux-action的用法。Redux实现的计数器创建用于增减的两个Action// ACTION const COUNTER_INCREMENT = 'COUNTER_INCREMENT'; const COUNTE

Linux安装Git的简易方式

在Linux安装Git首先要确定Linux系统的分发版本,这里介绍两种基于Debian和基于Red Hat的分发版本。基于Debian(包括Ubuntu)分发的Linux安装Git在debian系列的Linux使用apt安装,指令如下:sudo apt-get update sudo apt-get upgrade sudo apt-ge

ES6简化版的JavaScript中间件模式的实现

JavaScript中间件模式的目的是分解前端业务逻辑,通过next方法层层传递给下一个业务。比较经典的是express和koa。这是使用ES6实现的一个简版的中间件模式:class Middleware { use(fn) { this.go = (stack => next => stack(fn.bind(this, next.bind(this))))(this.go)

雪花算法实现-分布式系统

一、订单id的特殊性订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。二、雪花算法这里的订单id是通过一个工具类生成的,而工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。三、简单原理雪花算法会生成一个64位的二进制数据,为一个Long型。(转换成字符串后长度最多19位) ,其基本

Celery多种的安装方式

Celery提供了多种的安装方式:通过pip安装通过easy_install直接使用源码安装使用pip安装celery$ pip install Celery使用easy_install安装celery$ easy_install Celery直接使用源码安装1、从pypi下载celery2、执行以下命令安装$ tar xvfz celery-0.0.0.tar.gz$ cd celery-0.0

MacOS 安装yarn几种方式

macOS有几种安装yarn的方式,如下:使用homebrew安装yarnbrew install yarn使用homebrew安装,如果node.js没有安装,那么它会自动安装node.js使用MacPorts安装yarnsudo port install yarnMacPorts也会自动按node.js使用脚本安装yarnmacOS属于Unix系统的分支,在Unix环境下,使用脚本安装是很方便

axios捕获错误的两种方式:async/await以及promis

以下为axios捕获错误异常的两种方式:基于async/await机制基于promis机制axios基于async/await捕获错误异常下面的脚本必须写在async函数里try { const response = await axios.get('https://your.site/api/v1/bla/ble/bli'); console.log(response);} catc

curl post请求发送json数据两种方式(Window/Linux)

设置请求头Content-Typecurl发送post请求,默认的content-type是:application/x-www-form-urlencoded。要发送json格式,则需要设置请求头的content-type为application/json。使用-H 或--header参数设置content type:-H "Content-Type: application/json"发送数据

CKEditor5 Observable——装饰方法

上一节我们学习了在CK5中,如何绑定多个属性以及绑定多个Observable对象,今天我们学习如何装饰方法。 首先,我们提出一个问题,为什么会有装饰方法呢?以及什么叫做装饰?所谓装饰,就是在不改变原来方法功能的前提下,增加方法的功能,众所周知在java的IO流中,就有很多地方用到了装饰。 而在CK5中,装饰是什么意思呢?请看下面这段话:Decorating object met