Czy „Funkcje strzałek” i „Funkcje” są równoważne / wymienne?

519

Funkcje strzałek w ES2015 zapewniają bardziej zwięzłą składnię.

  • Czy mogę teraz zastąpić wszystkie moje deklaracje / wyrażenia funkcji funkcjami strzałek?
  • Na co muszę uważać?

Przykłady:

Funkcja konstruktora

function User(name) {
  this.name = name;
}

// vs

const User = name => {
  this.name = name;
};

Metody prototypowe

User.prototype.getName = function() {
  return this.name;
};

// vs

User.prototype.getName = () => this.name;

Metody obiektowe (dosłowne)

const obj = {
  getName: function() {
    // ...
  }
};

// vs

const obj = {
  getName: () => {
    // ...
  }
};

Callbacki

setTimeout(function() {
  // ...
}, 500);

// vs

setTimeout(() => {
  // ...
}, 500);

Funkcje Variadic

function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// vs
const sum = (...args) => {
  // ...
};
Felix Kling
źródło
5
Podobne pytania dotyczące funkcji strzałek pojawiają się coraz bardziej, gdy ES2015 staje się coraz bardziej popularny. Nie czułem, że istnieje dobre kanoniczne pytanie / odpowiedź na ten problem, więc stworzyłem ten. Jeśli uważasz, że jest już dobry, daj mi znać, a zamknę go jako duplikat lub usuń. Popraw przykłady lub dodaj nowe.
Felix Kling,
2
Co z JavaScript ecma6 zmienić normalną funkcję na funkcję strzałki ? Oczywiście normalne pytanie nigdy nie może być tak dobre i ogólne, jak pytanie napisane specjalnie jako kanoniczne.
Bergi,
Spójrz na ten przykład Plnkr Zmienna thisma bardzo różne timesCalledprzyrosty tylko o 1 przy każdym wywołaniu przycisku. Która odpowiada na moje osobiste pytanie: .click( () => { } )i .click(function() { }) oba tworzą tę samą liczbę funkcji, gdy są używane w pętli, jak widać z liczby Guid w Plnkr.
abbaf33f

Odpowiedzi:

749

tl; dr: Nie! Funkcje strzałek oraz deklaracje / wyrażenia funkcji nie są równoważne i nie można ich zastępować na ślepo.
Jeśli funkcja chcesz zastąpić ma nie używać this, argumentsa nie jest wywołana new, to tak.


Jak często: to zależy . Funkcje strzałek mają inne zachowanie niż deklaracje / wyrażenia funkcji, więc najpierw spójrzmy na różnice:

1. Leksykalny thisiarguments

Funkcje strzałek nie mają własnych thisani argumentswiążących. Zamiast tego identyfikatory te są rozwiązywane w zakresie leksykalnym, jak każda inna zmienna. Oznacza to, że wewnątrz funkcji strzałki, thisi argumentsodnoszą się do wartości thisi argumentsw środowisku funkcja strzałka jest zdefiniowany w (czyli „na zewnątrz”, funkcja strzałka):

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

W przypadku wyrażenia funkcyjnego thisodnosi się do obiektu, który został utworzony wewnątrz createObject. W przypadku funkcji strzałki, thisodnosi się thisod createObjectsiebie.

To sprawia, że ​​funkcje strzałek są przydatne, jeśli chcesz uzyskać dostęp thisdo bieżącego środowiska:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

Zauważ, że oznacza to również, że nie można ustawić funkcji strzałek za thispomocą .bindlub .call.

Jeśli nie znasz się dobrze this, rozważ lekturę

2. Nie można wywoływać funkcji strzałek za pomocą new

ES2015 rozróżnia funkcje, które można wywoływać, i funkcje, które można konstruować . Jeśli funkcja jest konstruowalna, można ją wywołać za pomocą newnp new User(). Jeśli funkcja jest wywoływalna, można ją wywołać bez new(tzn. Normalne wywołanie funkcji).

Funkcje utworzone za pomocą deklaracji / wyrażeń funkcji można konstruować i wywoływać.
Funkcje strzałek (i metody) można wywoływać tylko. classkonstruktory są tylko do zbudowania.

Jeśli próbujesz wywołać funkcję, której nie można wywołać, lub zbudować funkcję, której nie można zbudować, pojawi się błąd czasu wykonywania.


Wiedząc o tym, możemy stwierdzić, co następuje.

Wymienny:

  • Funkcje, które nie używają thislub arguments.
  • Funkcje używane z .bind(this)

Nie wymienny:

  • Funkcje konstruktora
  • Funkcja / metody dodane do prototypu (ponieważ zwykle używają this)
  • Funkcje Variadic (jeśli używają arguments(patrz poniżej))

Przyjrzyjmy się temu na twoich przykładach:

Funkcja konstruktora

To nie zadziała, ponieważ nie można wywoływać funkcji strzałek new. Nadal używaj deklaracji / wyrażenia funkcji lub użyj class.

Metody prototypowe

Najprawdopodobniej nie, ponieważ thisdo uzyskania dostępu do instancji zwykle używają metod prototypowych . Jeśli nie używają this, możesz je wymienić. Jeśli jednak zależy ci przede wszystkim na zwięzłej składni, użyj classjej zwięzłej metody:

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

  getName() {
    return this.name;
  }
}

Metody obiektowe

Podobnie w przypadku metod w dosłowności obiektu. Jeśli metoda chce odwoływać się do samego obiektu this, nadal używaj wyrażeń funkcyjnych lub użyj nowej składni metody:

const obj = {
  getName() {
    // ...
  },
};

Callbacki

To zależy. Zdecydowanie powinieneś go wymienić, jeśli korzystasz z aliasu zewnętrznego thislub używasz .bind(this):

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

Ale: Jeśli kod wywołujący wywołanie zwrotne jawnie ustawia thisokreśloną wartość, jak to często ma miejsce w przypadku programów obsługi zdarzeń, szczególnie w jQuery, a wywołanie zwrotne używa this(lub arguments), nie można użyć funkcji strzałki!

Funkcje Variadic

Ponieważ funkcje strzałek nie mają własnych arguments, nie można po prostu zastąpić ich funkcją strzałki. Jednak ES2015 wprowadza alternatywę dla używania arguments: parametru rest .

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

Powiązane pytanie:

Dalsze zasoby:

Felix Kling
źródło
6
Być może warto wspomnieć, że leksyka thisrównież wpływa superi że nie mają .prototype.
loganfsmyth,
1
Warto również wspomnieć, że nie są one składniowo wymienne - funkcja strzałki ( AssignmentExpression) nie może być po prostu umieszczona wszędzie tam, gdzie może wyrazić to wyrażenie funkcji ( PrimaryExpression) i dość często wysyła ludzi (zwłaszcza, że ​​parsowano) błędy w głównych implementacjach JS).
JMM
@JMM: „dość często podchodzi do ludzi” czy możesz podać konkretny przykład? Przeskakując nad specyfikacją, wydaje się, że miejsca, w których można umieścić FE, ale nie AF, i tak spowodują błędy w czasie wykonywania ...
Felix Kling
Jasne, mam na myśli takie rzeczy, jak próba natychmiastowego wywołania funkcji strzałki, takiej jak wyrażenie funkcji ( () => {}()) lub zrobienie czegoś podobnego x || () => {}. Właśnie to mam na myśli: błędy uruchomieniowe (parsowanie). (I chociaż tak jest, dość często ludzie myślą, że błąd jest błędny.) Czy próbujesz tylko ukryć błędy logiczne, które pozostałyby niezauważone, ponieważ niekoniecznie są błędne podczas analizowania lub wykonywania? newjeden z nich to błąd czasu wykonywania, prawda?
JMM
Oto kilka linków pojawiających się na wolności: substack / node-browserify # 1499 , babel / babel-eslint # 245 (jest to strzałka asynchroniczna, ale myślę, że to ta sama podstawowa kwestia) i kilka problemów na Babel, który jest teraz trudny do znalezienia, ale oto jeden T2847 .
JMM
11

Funkcje strzałek => najlepsza jak dotąd funkcja ES6. Są niesamowicie potężnym dodatkiem do ES6, z którego ciągle korzystam.

Zaczekaj, nie możesz używać funkcji strzałek w całym kodzie, nie będzie działać we wszystkich przypadkach, na przykład gdy thisfunkcje strzałek nie są użyteczne. Bez wątpienia funkcja strzałki jest doskonałym dodatkiem, który zapewnia prostotę kodu.

Ale nie można użyć funkcji strzałki, gdy wymagany jest kontekst dynamiczny: definiowanie metod, tworzenie obiektów za pomocą konstruktorów, uzyskiwanie z tego celu celu podczas obsługi zdarzeń.

Funkcje strzałek NIE powinny być używane, ponieważ:

  1. Oni nie mają this

    Używa „zakresu leksykalnego”, aby ustalić, jaka thispowinna być wartość „ ”. W prostym słowie zakres leksykalny używa „ this” z wnętrza ciała funkcji.

  2. Oni nie mają arguments

    Funkcje strzałek nie mają argumentsobiektu. Ale tę samą funkcjonalność można uzyskać za pomocą parametrów odpoczynku.

    let sum = (...args) => args.reduce((x, y) => x + y, 0) sum(3, 3, 1) // output - 7 `

  3. Nie można ich używać z new

    Funkcje strzałek nie mogą być konstruktorami, ponieważ nie mają właściwości prototypu.

Kiedy używać funkcji strzałki, a kiedy nie:

  1. Nie używaj do dodawania funkcji jako właściwości w dosłowności obiektu, ponieważ nie możemy uzyskać do niej dostępu.
  2. Wyrażenia funkcyjne są najlepsze dla metod obiektowych. Strzałka funkcje są najlepsze dla wywołania zwrotne lub metod, takich jak map, reducelub forEach.
  3. Użyj deklaracji funkcji dla funkcji, które wywołujesz według nazwy (ponieważ są one podnoszone).
  4. Użyj funkcji strzałek do wywołań zwrotnych (ponieważ są one zwykle bardziej zwięzłe).
Ashutosh
źródło
2
2. Nie mają argumentów, przepraszam, nieprawda, można argumentować bez użycia ... operatora, może chcesz powiedzieć, że nie masz tablicy jako argumentu
Carmine Tambascia
@CarmineTambascia Przeczytaj o specjalnym argumentsobiekcie, który nie jest dostępny w funkcjach strzałek tutaj: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
vichle
0

Aby używać funkcji strzałek z function.prototype.call, utworzyłem funkcję pomocnika na prototypie obiektu:

  // Using
  // @func = function() {use this here} or This => {use This here}
  using(func) {
    return func.call(this, this);
  }

stosowanie

  var obj = {f:3, a:2}
  .using(This => This.f + This.a) // 5

Edytować

Nie POTRZEBUJESZ pomocnika. Mógłbyś:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
źródło