Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【译】Create and Test Decorators in JavaScript (在Javascritp中创建和验证装饰器) #22

Open
minhuaF opened this issue Jun 29, 2021 · 0 comments

Comments

@minhuaF
Copy link
Owner

minhuaF commented Jun 29, 2021

原文地址:https://netbasal.com/create-and-test-decorators-in-javascript-85e8d5cf879c

I remember the first time I saw this sexy symbol. Yes, you know what I’m talking about. The at sign (@), above or before class, property or a method — a Decorator.
我记得我第一次看见这个性感符号的时候,是的,你知道我说的是什么。在类、属性或者方法的前面或者后面的符号(@)—— 装饰器。

Decorators are a mechanism for observing, modifying, or replacing classes, methods or properties in a declarative fashion.
装饰器是一种以声明方式观察、修改或者替换类型、方法或者属性的机制。

Decorators are a proposed standard in ECMAScript2016. In Typescript, we can enable them by setting the “experimentalDecorators” compiler flag, or with babel by installing the babel-plugin-transform-decorators plugin.
装饰器是ECMAScript2016中的一个提议标准。在Typescript中,我们可以通过设置“experimentalDecorators”编辑器标志来启动它们,或者借助babel通过安装babel-plugin-transform-decorators插件来启动。

Creating decorators is actually quite easy. Let’s explore the various decorators.
创建装饰器真的很容易。让我们开始探索各种各样的装饰器。

Class Decorators Class 装饰器

A Class decorator is a function that takes the constructor of the class as the only parameter. If the class decorator returns a value, it will replace the class declaration with the provided constructor function, i.e., override the constructor.
Class装饰器是将类的构造函数作为唯一参数的函数。如果Class装饰器返回一个值,它将用提供的构造函数替换类声明,也就是重写构造函数。

Let’s see an example of how to override the constructor.
看一个重写构造函数的例子

function clean(constructor) {
  return class extends constructor {
    ngOnDestroy() {
      console.log('Cleaning....')
      // Auto Clean things and call the original method
      constructor.prototype.ngOnDestroy.apply(this, arguments);
    }
  }
}

@clean
class HelloComponent {
  ngOnDestroy() {
    console.log('ngOnDestroy - HelloComponent');
  }
}

const helloComponet = new HelloComponent();
helloComponet.ngOnDestroy();

编译之后的代码:

var _class;

function clean(constructor) {
  return class extends constructor {
    ngOnDestroy() {
      console.log('Cleaning....'); // Auto Clean things and call the original method

      constructor.prototype.ngOnDestroy.apply(this, arguments);
    }

  };
}

let HelloComponent = clean(_class = class HelloComponent {
  ngOnDestroy() {
    console.log('ngOnDestroy - HelloComponent');
  }

}) || _class;

const helloComponet = new HelloComponent();
helloComponet.ngOnDestroy();

We are returning a brand new Class that extends the original constructor function, in our case, the HelloComponent.
我们返回了一个全新的类,它继承了原始的构造函数,在我们的例子中,就是 HelloComponent

Let’s see an example of how to decorate the existing constructor. Let’s say we work with React and we want to know when React calls the render method.
让我们看一个如何装饰现有构造函数的示例。假设我们使用React,我们想要知道React何时调用render方法。

function logRender(constructor) {
  const original = constructor.prototype.render;

  constructor.prototype.render = function(){
    console.log(`Rendering ${constructor.name}...`);
    return original.apply(this, arguments);
  }
}

@logRender
class Todos extends Component {
  render() {
    return null
  }
}

We start by saving a reference to the original method, create a new one, call what we need and return the original method, letting React do its magic.
我们首先保存对原始方法的引用,然后创建一个新的犯方法,执行我们需要的内容然后返回原始的方法,让React表现出魔法。

You can find a real-life example in this repository.
你可以在这个仓库中找到一些真实的例子。

Method Decorators (Method装饰器)

A Method decorator is a function that takes three parameters.
Mathod装饰器是一个有三个参数的方法。

The target:

Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
对于静态成员的类的构造函数,或者实例成员的类的原型。

The key:
The method name.
方法的名字。

The descriptor:
The Property Descriptor for the method.
方法的属性描述。

Let’s see an example of how to decorate an existing method with the setTimeout API. But before we can proceed, if you remember, the setTimeout function takes a number of milliseconds to wait before executing the code, so we need to learn how to pass options to a decorator by using a Decorator Factory.
让我们看一个如何使用setTimeout装饰现有方法的示例。但是在我们继续之前,如果你还记得,setTimeout方法会在执行前等待几毫秒,所以我们需要学习如何使用装饰器工厂传递参数给装饰器。

A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.
装饰器工厂只是一个函数,它返回装饰器运行时调用的表达式。

Or in plain English, function that returns function.
用简单的英语来说,就是返回函数的函数。

function clean(constructor) {
  return class extends constructor {
    ngOnDestroy() {
      console.log('Cleaning....')
      // Auto Clean things and call the original method
      constructor.prototype.ngOnDestroy.apply(this, arguments);
    }
  }
}

function timeout(milliseconds = 0) {
  return function(target, propertyKey, descriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function() {
      setTimeout(() => {
        originalMethod.apply(this, arguments);
      }, milliseconds)
    };

    return descriptor;
  }
}

@clean
class HelloComponent {
  ngOnDestroy() {
    console.log('ngOnDestroy - HelloComponent');
  }

  @timeout() 
  demoMethod() {
    console.log('demoMethod');
  }
  
  @timeout(2000)
  demoMethod2() {
    console.log('demoMethod2');
  }
}

const helloComponet = new HelloComponent();
helloComponet.ngOnDestroy();
helloComponet.demoMethod();
helloComponet.demoMethod2();

We can get a reference to the original method from the descriptor value property.
Then, we will override the original value and create a new function that wraps the original with setTimeout.
我们可以从descriptorvalue 属性中获取到原方法的引用。
然后,我们会重写这个原始的value并创建一个被setTimeout包裹的新函数。

Remember that you can also use a method decorator with static methods, for example:
请记住,你也可以使用静态方法的方法装饰器,例如:

class Test {
    
    @log
    static someMethod() {}
}

The only difference in this case is you will get the constructor function of the class and not the prototype of the class.
在这种情况下,唯一的区别就是你会获取到类的构造函数,而不是类似的原型。

You can find a real-life examples in this repository.
你可以在这个仓库找到一些示例。

Property Decorators (Property 装饰器)

A Property Decorator is declared just before a property declaration. Same as the others, it’s a function that takes two parameters.
属性装饰器是在属性声明之前声明的。与其他函数一样,它是一个有两个参数的函数。

The target:

Either the constructor function of the class for a static member, or the prototype of the class for an instance member.
要不就是对于静态成员来说是类的构造方法,要不就是对于实例成员来说是类的原型。

The key:

The property name.
属性名字

Let’s see an example of how to decorate a property.
看一个如何装饰属性的例子。

/**
 * 属性装饰器
 */

function logProperty(target, key) {
  let value;
  const getter = function() {
    console.log(`Get => ${key}`);
    return value;
  }

  const setter = function(newVal) {
    console.log(`Set: ${key} => ${newVal}`);
    value = newVal;
  }

  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  })
}

The logProperty decorator above redefines the decorated property on the object. We can define a new property to the constructor’s prototype by using the Object.defineProperty() method. Here, we’re using get to return the value and log it. Secondly, we’re using set to directly write a value to the internal property and log it.
上面的 logProperty decorator重新定义了对象上的属性。我们可以使用Object.defineProperty()方法在构造函数的原型上定义一个新属性。这里,我们使用get返回值并记录它。其次,我们使用set直接向内部属性写入一个值并记录它。

We can also use the new Reflect API instead of the defineProperty() method.
我们还可以使用新的 Reflect API来代替 defineProperty()方法。

Reflect.deleteProperty[key];
Reflect.defineProperty(target, key, {
    get: getter,
    set: setter,
})

Now we can use our decorator like this:
现在可以这样使用我们的装饰器:

class Person { 
	
  @logProperty
  public name: string;

  constructor(name : string) { 
    this.name = name;
  }
}

let p = new Person("netanel");
p.name = "Netanel";
const name = p.name;

// Set: name => netanel
// Set: name => Netanel
// Get => name

You can find a real-life example in this repository.
你可以在这个仓库找到一些真实的例子。

Parameter Decorators(参数装饰器)

I’m going to skip this explanation because it’s the least common and it usually comes in conjunction with a method decorator. You can find a detailed example in the official documentation.
我将跳过这一段的讲解,因为它并不常见,它通常与方法装饰器结合使用,可以在官方文档中找到一个详细示例。

Composing Decorators(复式装饰器)

When multiple decorators apply to a single declaration, their evaluation is similar to function composition in mathematics. For example, the following code:
当多个装饰器应用到单个声明中,他们的计算类似于数学中的函数组合,类型下面的例子:

class TodosService {

  @decoratorTwo
  @decoratorOne
  someMethod(){

  }
}

is equivalent to decoratorTwo(decoratorOne(someMethod)).
等价于decoratorTwo(decoratorOne(someMethod))

Testing Decorators (测试装饰器)

I’m going to use jest for testing, but you can use whatever test library you like. We have two ways to test decorators.
我将使用jest作为测试,但是你可以用任何你喜欢的库。我们有两个方式去测试装饰器。

  1. Use them in your tests like in your code.
  2. 在测试中使用,就像在代码中。
describe('@logRender Decorator', () => {

  @logRender
  class Todos {
    render() {
      return ...
    }
  }

  it('should call console.log', () => {
     const spy = jest.spyOn(console, 'log');
     const todos = new Todos().render();
     expect(spy).toBeCalledWith('Rendering Todos...');
  });

});
  1. Since decorators are just functions we can test them like any other function.
  2. 由于装饰器只是函数,我们可以测试其他函数一样测试它们。
describe('@logRender Decorator', () => {

  class Todos {
    render() {
      return ...
    }
  }
  
  it('should call console.log', () => {
    const spy = jest.spyOn(console, 'log');
    logRender(Todos);
    const todos = new Todos().render();
    expect(spy).toBeCalledWith('Rendering Todos...');
  });

});

Let’s close the article by testing the @timeout decorator.
让我们通过测试@timeout装饰器来结束文章。

jest.useFakeTimers();

class Test {
  method() {
    console.log('Worked');
  }
}

describe('@timeout', function () {

  it('should call setTimeout', function () {
    timeout(1000)(Test.prototype, 'method', Object.getOwnPropertyDescriptor(Test.prototype, 'method'));
    new Test().method();
    expect(setTimeout['mock'].calls.length).toBe(1);
    expect(setTimeout['mock'].calls[0][1]).toBe(1000);
  });
  
});

We are passing the necessary parameters manually to the timeout decorator and leveraging the useFakeTimers feature from jest to check if our setTimeout function was called.
我们将手动将必要的参数传递给超时装饰器,并利用jest中的useFakeTimers特性来检查是否调用了setTimeout函数。

Wrapping Up(总结)

You can leverage decorators in your apps and create powerful things with them. Decorators are not only for frameworks or libraries, they are worth the time to learn as they can make your code more extensible and even more readable. Decorators also promote code reuse. Give them a try sometime soon 🙏
你可以在你的引用中使用装饰器来创建伟大的东西。装饰器不仅仅是在框架和库中使用,它们非常值得学习,因为装饰器能是你的代码更具有可扩展性和有更好的可读性。装饰器也能促进代码重用。尽快试试吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant