Decoradores de JavaScript desde cero

Invitamos a los futuros estudiantes del curso "JavaScript Developer. Professional" a inscribirse en una lección abierta sobre el tema "Cómo hacer un bot de telegrama interactivo en Node.js" .



Y ahora compartimos la traducción tradicional de material útil.






Comprender las funciones del decorador

¿Qué es un decorador?

Un decorador es una instalación que le permite envolver una función en otra y ampliar sus capacidades. "Decora" el código existente envolviéndolo en otro código. Este truco es familiar para cualquiera que esté familiarizado con la composición de funciones o funciones de orden superior.





Los decoradores no son nuevos. También se utilizan en otros lenguajes, como Python, e incluso en programación funcional en JavaScript. Pero hablaremos de esto más tarde.





¿Por qué necesitamos decoradores?

Le permiten escribir código más limpio, adherirse al concepto de composición y extender una capacidad una vez desarrollada a múltiples funciones y clases. Al usar decoradores, puede escribir código que sea más fácil de depurar y mantener.





, , , . , .





2- , .





. Bit (Github). .





, .





Bit  Node, TypeScript, React, Vue, Angular JS.





Ejemplos de componentes React reutilizables en Bit.dev
React- Bit.dev

?

 — . , -. -, . , .





?

.





 — . , Java, , , . JavaScript , . , .





, . , , , .





, , .





//decorator function
const allArgsValid = function(fn) {
  return function(...args) {
  if (args.length != fn.length) {
      throw new Error('Only submit required number of params');
    }
    const validArgs = args.filter(arg => Number.isInteger(arg));
    if (validArgs.length < fn.length) {
      throw new TypeError('Argument cannot be a non-integer');
    }
    return fn(...args);
  }
}

//ordinary multiply function
let multiply = function(a,b){
	return a*b;
}

//decorated multiply function that only accepts the required number of params and only integers
multiply = allArgsValid(multiply);

multiply(6, 8);
//48

multiply(6, 8, 7);
//Error: Only submit required number of params

multiply(3, null);
//TypeError: Argument cannot be a non-integer

multiply('',4);
//TypeError: Argument cannot be a non-integer
      
      



- allArgsValid



, . , -. - , . . : , .





multiply



, . - allArgsValid



, , , . multiply



. , .





//ordinary add function
let add = function(a,b){
	return a+b;
}

//decorated add function that only accepts the required number of params and only integers
add = allArgsValid(add);

add(6, 8);
//14

add(3, null);
//TypeError: Argument cannot be a non-integer

add('',4);
//TypeError: Argument cannot be a non-integer
      
      



: , TC39

JavaScript . 2- .





JavaScript — .  — , .





,  — . ? .





, .





function log(fn) {
  return function() {
    console.log("Execution of " + fn.name);
    console.time("fn");
    let val = fn();
    console.timeEnd("fn");
    return val;
  }
}

class Book {
  constructor(name, ISBN) {
    this.name = name;
    this.ISBN = ISBN;
  }

  getBook() {
    return `[${this.name}][${this.ISBN}]`;
  }
}

let obj = new Book("HP", "1245-533552");
let getBook = log(obj.getBook);
console.log(getBook());
//TypeError: Cannot read property 'name' of undefined
      
      



, getBook



, - log



. obj.getBook



. this



, Book



. TypeError



.





, Book



getBook



.





function log(classObj, fn) {
  return function() {
    console.log("Execution of " + fn.name);
    console.time("fn");
    let val = fn.call(classObj);
    console.timeEnd("fn");
    return val;
  }
}

class Book {
  constructor(name, ISBN) {
    this.name = name;
    this.ISBN = ISBN;
  }

  getBook() {
    return `[${this.name}][${this.ISBN}]`;
  }
}

let obj = new Book("HP", "1245-533552");
let getBook = log(obj, obj.getBook);
console.log(getBook());
//[HP][1245-533552]
      
      



Book



- log



, obj.getBook



, this



.





, . , .





. Babel. JSFiddle



 — , . , : .





@. - log



:





@log
      
      



- . - , . target



, .





target



, . , . .





, Book



, .





function log(target) {
  return function(...args) {
    console.log("Constructor called");
    return new target(...args);
  };
}

@log
class Book {
  constructor(name, ISBN) {
    this.name = name;
    this.ISBN = ISBN;
  }

  getBook() {
    return `[${this.name}][${this.ISBN}]`;
  }
}

let obj = new Book("HP", "1245-533552");
//Constructor Called
console.log(obj.getBook());
//HP][1245-533552]
      
      



, log



  target



  . log



, target



, Book



. target



target.prototype.property.







, -, :





function logWithParams(...params) {
  return function(target) {
    return function(...args) {
      console.table(params);
      return new target(...args);
    }
  }
}

@log
@logWithParams('param1', 'param2')
class Book {
	//Class implementation as before
}

let obj = new Book("HP", "1245-533552");
//Constructor called
//Params will be consoled as a table
console.log(obj.getBook());
//[HP][1245-533552]
      
      



, , @



. , , .





, , . , :





  • target



     — , , ;





  • name



     — , ;





  • descriptor



     — , , .   .





descriptor. 4 :





  • configurable



     — , , ;





  • enumerable



     — , , ;





  • value



     — . ;





  • writable



     — , , .





Book



.





//readonly decorator function
function readOnly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

class Book {
  //Implementation here
  @readOnly
  getBook() {
    return `[${this.name}][${this.ISBN}]`;
  }

}

let obj = new Book("HP", "1245-533552");

obj.getBook = "Hello";

console.log(obj.getBook());
//[HP][1245-533552]
      
      



- readOnly



, getBook



Book



  . writable



false



. true



.





writable



, getBook



, , :





obj.getBook = "Hello";
console.log(obj.getBook);
//Hello
      
      



. TypeScript , JavaScript 3- .





-, , , . . , value



. initializer



. , initializer



. initializer



.





(undefined



), writable



.





. Book



.





function upperCase(target, name, descriptor) {
  if (descriptor.initializer && descriptor.initializer()) {
    let val = descriptor.initializer();
    descriptor.initializer = function() {
      return val.toUpperCase();
    }
  }

}

class Book {
  
  @upperCase
  id = "az092b";

  getId() {
    return `${this.id}`;
  }

  //other implementation here
}

let obj = new Book("HP", "1245-533552");

console.log(obj.getId());
//AZ092B
      
      



id . - upperCase



initializer



, , (  undefined



). , « » (. .: . truthy — , true



Boolean



), . getId



. , .





. , .





Angular

TypeScript Angular, Angular, @Component



, @NgModule



, @Injectable



, @Pipe



. . .





MobX

MobX 6- . @observable



, @computed



@action



. MobX , . :





« ES, . , ».





Core Decorators

JavaScript, . 0, , 3- .





, @readonly



, @time



, @deprecate



. .





Redux React

Redux React connect



, React Redux. connect



  .





//Before decorator
class MyApp extends React.Component {
  // ...define your main app here
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
//After decorator
@connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {
  // ...define your main app here
}
      
      



Felix Kling Stack Overflow .





connect



  , Redux . , 2- , , .





 — , . .





, , !






"JavaScript Developer. Professional".



" telegram Node.js".












All Articles