惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
L
LINUX DO - 最新话题
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
Know Your Adversary
Know Your Adversary
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
L
Lohrmann on Cybersecurity
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Security Latest
Security Latest
T
The Exploit Database - CXSecurity.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Privacy & Cybersecurity Law Blog
K
Kaspersky official blog
The Last Watchdog
The Last Watchdog
Webroot Blog
Webroot Blog
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
C
Cyber Attacks, Cyber Crime and Cyber Security
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
V
Visual Studio Blog
O
OpenAI News
AI
AI
Hacker News: Ask HN
Hacker News: Ask HN
V2EX - 技术
V2EX - 技术
GbyAI
GbyAI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
Spread Privacy
Spread Privacy
Y
Y Combinator Blog
I
InfoQ
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
F
Fortinet All Blogs
C
CERT Recently Published Vulnerability Notes
T
The Blog of Author Tim Ferriss
C
Check Point Blog
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
N
News and Events Feed by Topic
Project Zero
Project Zero
小众软件
小众软件
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
B
Blog
G
Google Developers Blog

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
JavaScript Decorators for Declarative and Readable Code
2018-01-29 · via Minko Gechev's blog

Decorators in JavaScript are now in stage 2. They allow us to alter the definition of a class, method, or a property. There are already a few neat libraries which provide decorators and make our life easier by allowing us to write more declarative code with better performance characteristics.

In this blog post I’ll share a few decorators which I’m using on a daily basis. We’ll take a look at:

  • How to write more efficient React components with @autobind
  • How to cache results of computations using @memo
  • Improving separation of concerns and cohesion with aspect.js’s @beforeMethod and @afterMethod
  • Developing more decoupled code using Angular’s dependency injection with injection-js

Autobind

Autobind is a decorator which allows given method to be automatically bound to a class instance. For example:

class Superhero {
  constructor(name) {
    this.name = name;
  }

  @autobind
  getName() {
    return this.name;
  }
}

const superman = new Superhero('Superman');
const batman = new Superhero('Batman');

const supermanNameGetter = superman.getName;
const batmanNameGetter = batman.getName;

console.log(supermanNameGetter()); // Superman
console.log(batmanNameGetter()); // Batman

From the example above we can see that the getName method got automatically bound to a specific instance of the class. In case we didn’t use @autobind we’d have gotten the error:

Error: Cannot read property ’name’ of undefined

This decorator has a few quite useful applications.

Bind

React Event Handlers

Firstly, it’s quite handy to use @autobind with React in order to not create new functions for the event handlers each time when the render method gets invoked. For instance:

class Component extends React.Component {
  prop = 'Hello there!';

  clickHandler() {
    alert(this.prop);
  }

  render() {
    return <button onClick={() => this.clickHandler()}></button>
  }
}

This code will create a new arrow function each time when render gets invoked. Another alternative is:

// ...
render() {
  return <button onClick={this.clickHandler.bind(this)}></button>
}
// ...

…which suffers from the same problem, because bind will create a new instance of the clickHandler bound to this.

With @autobind we can refactor the code to:

class Component extends React.Component {
  prop = 'Hello there!';

  @autobind
  clickHandler() {
    alert(this.prop);
  }

  render() {
    return <button onClick={this.clickHandler}></button>
  }
}

Keeping a Reference to an Event Handler

Secondly, we can use @autobind to keep a reference to an event handler that we’ve attached to given DOM element. For example, take a look at the following Angular component:

@Component({...})
class DraggableComponent {
  private _documentMove: Function;

  ngOnInit() {
    document.addEventListener('mousemove', this._documentMove = e => {
      this.x = e.pageX;
      this.y = e.pageY;
      // More logic...
    });
  }

  ngOnDestroy() {
    // Perform clean up
    document.removeEventListener('mousemove', this._documentMove);
  }
}

This is a commonly used pattern for keeping a reference to an event handler so we can easily clean the subscription once the component gets destroyed. It works well, however, we can be achieved the same in a more elegant way:

@Component({...})
class DragHandlerComponent {
  ngOnInit() {
    document.addEventListener('mousemove', this._documentMove);
  }

  ngOnDestroy() {
    // Perform clean up
    document.removeEventListener('mousemove', this._documentMove);
  }

  @autobind
  private _documentMove(e) {
    this.x = e.pageX;
    this.y = e.pageY;
    // More logic...
  }
}

This way we no longer need the arrow function to preserve the context because @autobind would have bound the _documentMove method to the proper value of this.

How to use it?

You can use @autobind from either core-decorators on npm or autobind-decorator if that’s the only decorator you need.

memo

Often we have pure methods in our classes. Such methods always:

  • Return the same result when invoked with the same set of arguments.
  • Do not perform side effects.

In such cases it makes sense to perform memoization over them!

Recently I released the decorator memo-decorator which does exactly that! For example, let’s suppose we have:

class Superhero {
  calculateAge(n) {
    if (n < 1) {
      throw new Error('Invalid argument');
    }
    if (n === 1 || n === 2) {
      return 1;
    }
    return this.calculateAge(n - 1) + this.calculateAge(n - 2);
  }
}

Now when we invoke calculateAge with given n, we’re always going to get the same result:

const hero = new Superhero();

hero.calculateAge(5); // 5
hero.calculateAge(5); // 5

hero.calculateAge(15); // 610
hero.calculateAge(15); // 610
hero.calculateAge(15); // 610

An obvious idea for an optimization is to cache the result and directly return it once calculated instead of going through the same computation. A simple way to perform caching is to use a Map with keys the arguments of the calculateAge method and values the results the method produces. That’s exactly what memo-decorator does!

class Superhero {
  @memo()
  calculateAge(n) {
    console.log('Calculating');
    if (n < 1) {
      throw new Error('Invalid argument');
    }
    if (n === 1 || n === 2) {
      return 1;
    }
    return this.calculateAge(n - 1) + this.calculateAge(n - 2);
  }
}

Notice the console.log statement that I added in the method. Here’s what the current behavior would be:

const hero = new Superhero();

hero.calculateAge(5); // return 5 and log 'Calculating'
hero.calculateAge(5); // return 5, does not log 'Calculating' since we get the result from the cache.

hero.calculateAge(15); // return 610 and log 'Calculating'
hero.calculateAge(15); // return 610, does not log 'Calculating' since we get the result from the cache.
hero.calculateAge(15); // return 610, does not log 'Calculating' since we get the result from the cache.

But what about methods which have multiple arguments?

In such cases we can provide a custom “resolver” which for given set of arguments returns the key to be used for caching:

class Superhero {
  @memo((n, a) => n + '-' + a)
  calculateAge(n, a) {
    console.log('Calculating');
    if (n < 1) {
      throw new Error('Invalid argument');
    }
    if (n === 1 || n === 2) {
      return 1;
    }
    return this.calculateAge(n - 1, a) + this.calculateAge(n - 2, a);
  }
}

As we can see from the snippet above, @memo receives a single, optional argument - a function. This function receives all the arguments passed to the decorated method. By default the implementation of the key resolver is the identity function:

How to use?

You can use the @memo decorator from the memo-decorator package on npm.

aspect.js

So far we discussed different JavaScript decorators. Now we’ll briefly show an entire paradigm!

Let’s suppose we have a class called DataMapper which sends requests over the network to a RESTful API. This abstraction is supposed to create, update, and delete users and their payment information:

class DataMapper {
  saveUser(user) {
    return fetch(...)
  }

  deleteUser(user) {
    return fetch(...)
  }

  updateUser(user) {
    return fetch(...)
  }

  saveUserPayment(user) {
    return fetch(...)
  }

  deleteUserPayment(user) {
    return fetch(...)
  }

  updateUserPayment(user) {
    return fetch(...)
  }
}

Now, for debugging purposes we want to add logging for all of these methods. Going over each individual method and adding a log statement will be quite annoying, and what if we want to drop the log statements for our production build?

A better approach would be to use Aspect-Oriented Programming (AOP), which can help us to resolve this issue by just:

@Wove()
class DataMapper {
  // ...
}

class DataMapperAspect {
  @beforeMethod({
    classNamePattern: /DataMapper/,
    methodNamePattern: /.*/
  })
  log(m: Metadata) {
    console.log(`Method ${m.method.name} called with ${m.method.args.join(', ')}`);
  }
}

The semantics of this snippet is:

Invoke log before the invocation of each method from a class which name matches the pattern /DataMapper/.

As value of the property methodNamePattern that we pass as an argument to the @beforeMethod decorator we specify another pattern. If we want, we can get more restrictive:

@Wove()
class DataMapper {
  // ...
}

class DataMapperAspect {
  @beforeMethod({
    classNamePattern: /DataMapper/,
    methodNamePattern: /^save.*/
  })
  log(m: Metadata) {
    console.log(`Method ${m.method.name} called with ${m.method.args.join(', ')}`);
  }
}

This way the log method will be called only before the invocation of methods with save prefix.

Separation of Concerns

What else aspect.js provides?

The aspect-oriented paradigm is extremely powerful. With aspect.js we can achieve the effect of any of the decorators listed above…and do much more!

You can find more about AOP here.

How to use?

aspect.js can be found on npm.

injection-js

Another extremely convenient use case for the JavaScript decorators is for dependency injection (DI)! Angular already takes advantage of this technique in order to provide a flexible way to wire up the dependencies in our application. I am sure I don’t have to convince you how useful the DI pattern is for helping us to write more testable and coherent code.

Did you know that you don’t necessary need Angular in order to use its dependency injection mechanism? That’s right, you can use the DI independently from the framework with the package injection-js!

Injection-js can be use with TypeScript, ES5, ES2016, ES2017, etc, however, it looks most natural with TypeScript because of its type annotations.

Here’s an example with TypeScript:

import 'reflect-metadata';
import { ReflectiveInjector, Injectable, Injector } from 'injection-js';

class Http {}

@Injectable()
class Service {
  constructor(private http: Http) {}
}

const injector = ReflectiveInjector.resolveAndCreate([
  Service,
  Http
]);

console.log(injector.get(Service) instanceof Service);

Once we invoke the get method of the injector instance, the injector will automatically figure out that Service depends on Http so it’ll create an instance of Http and pass it to the constructor of Service.

…and here’s an equivalent example with plain JavaScript, with parameter decorators support:

import { ReflectiveInjector, Injectable, Injector, Inject } from 'injection-js';

class Http {}

@Injectable()
class Service {
  constructor(@Inject(Http) private http) {}
}

const injector = ReflectiveInjector.resolveAndCreate([
  Service,
  Http
]);

console.log(injector.get(Service) instanceof Service);

Notice that since in JavaScript we do not have type annotations we had to specify the type of the dependency of Service by using @Inject.

Also, keep in mind that at the moment both snippets can be compiled only with TypeScript, since babel does not have parameter decorator support yet.

How to use?

Just install injection-js. For further instructions on how to use the DI in your Vue, React, Node.js application you can just follow the Angular documentation. Also, here’s how I introduced injection-js support in my React app, sometime back.

Conclusion

JavaScript decorators are on it’s way to the ECMAScript standard. There are a lot of libraries and frameworks out there taking advantage of them!

In this article we made a quick overview of only a few - @autobind, @memo, a few decorators from aspect.js, and injection-js.

We saw how we can develop more efficient React components and how to elegantly preserve a reference to event listeners by using @autobind. After that we saw how we can use the @memo decorator for applying memoization to pure methods. Our next stop was aspect.js package which implements the Aspect-Oriented Programming paradigm by using ES decorators! Finally, we took a look at injection-js which allows us to use the DI pattern for our Node, Vue, and React applications!