When should I use Arrow functions in ECMAScript 6?

2018-12-31 01:02发布

The question is directed at people who have thought about code style in the context of the upcoming ECMAScript 6 (Harmony) and who have already worked with the language.

With () => {} and function () {} we are getting two very similar ways to write functions in ES6. In other languages lambda functions often distinguish themselves by being anonymous, but in ECMAScript any function can be anonymous. Each of the two types have unique usage domains (namely when this needs to either be bound explicitly or explicitly not be bound). Between those domains there is a vast number of cases where either notation will do.

Arrow functions in ES6 have at least two limitations:

  • Don't work with new
  • Fixed this bound to scope at initialisation

These two limitations aside, arrow functions could theoretically replace regular functions almost anywhere. What is the right approach using them in practice? Should arrow functions be used e.g.:

  • "everywhere they work", i.e. everywhere a function does not have to be agnostic about the this variable and we are not creating an object.
  • only "everywhere they are needed", i.e. event listeners, timeouts, that need to be bound to a certain scope
  • with 'short' functions but not with 'long' functions
  • only with functions that do not contain another arrow function

What I am looking for is a guideline to selecting the appropriate function notation in the future version of ECMAScript. The guideline will need to be clear, so that it can be taught to developers in a team, and to be consistent so that it does not require constant refactoring back and forth from one function notation to another.

7条回答
人气声优
2楼-- · 2018-12-31 01:32

In addition to the great answers so far, I'd like to present a very different reason why arrow functions are in a certain sense fundamentally better than "ordinary" JavaScript functions. For the sake of discussion, let's temporarily assume we use a type checker like TypeScript or Facebook's "Flow". Consider the following toy module, which is valid ECMAScript 6 code plus Flow type annotations: (I'll include the untyped code, which would realistically result from Babel, at the end of this answer, so it can actually be run.)

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

Now see what happens when we use the class C from a different module, like this:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

As you can see, the type checker failed here: f2 was supposed to return a number, but it returned a string!

Worse, it seems that no conceivable type checker can handle ordinary (non-arrow) JavaScript functions, because the "this" of f2 does not occur in the argument list of f2, so the required type for "this" could not possibly be added as an annotation to f2.

Does this problem also affect people who don't use type checkers? I think so, because even when we have no static types, we think as if they're there. ("The first parameters must be a number, the second one a string" etc.) A hidden "this"-argument which may or may not be used in the function's body makes our mental bookkeeping harder.

Here is the runnable untyped version, which would be produced by Babel:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

查看更多
登录 后发表回答