javascript - Är 'Arrow Functions' och 'Functions' ekvivalenta/utbytbara?


Arrow-funktioner i ES2015 ger en mer koncis syntax.

  • Kan jag ersätta alla mina funktionsdeklarationer/uttryck med pilfunktioner nu?
  • Vad måste jag se upp för?

Exempel:

Konstruktörfunktion

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

// vs

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

Prototypmetoder

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

// vs

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

Objekt (bokstavliga) metoder

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

// vs

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

Återuppringningar

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

// vs

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

Variadiska funktioner

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

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

Svar #1

tl;dr: Nej! Arrow-funktioner och funktionsdeklarationer/-uttryck är inte ekvivalenta och kan inte ersättas blint.
Om funktionen du vill ersätta inte använder this, arguments och inte anropas med new, då ja.


Som så ofta: det beror på. Pilfunktioner har ett annat beteende än funktionsdeklarationer/uttryck, så låt oss först titta på skillnaderna:

1. Lexikalt this och arguments

Arrow-funktioner har inte sin egen this- eller arguments-bindning. Istället löses dessa identifierare i det lexikala omfånget som vilken annan variabel som helst. Det betyder att inuti en arrow-funktion refererar this och arguments till värdena av this och arguments i den miljö där arrow-funktionen är definierad (d.v.s. "utanför" arrow-funktionen):

// 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

I fallet med funktionsuttryck refererar this till objektet som skapades inuti createObject. I fallet med pilfunktion refererar this till this för createObject själv.

Detta gör pilfunktioner användbara om du behöver komma åt this i den aktuella miljön:

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

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

Observera att detta också innebär att det inte är möjligt att sätta en pilfunktion's this med .bind eller .call.

Om du inte är mycket bekant med this, överväg att läsa

  • MDN - det här
  • YDKJS - detta & objektprototyper

2. Pilfunktioner kan inte anropas med new

ES2015 skiljer mellan funktioner som är anropbara och funktioner som är konstruerbara. Om en funktion är konstruerbar kan den anropas med new, t.ex. new User(). Om en funktion är anropbar kan den anropas utan new (dvs. normalt funktionsanrop).

Funktioner som skapas genom funktionsdeklarationer/uttryck är både konstruerbara och anropbara.
Pilfunktioner (och metoder) är endast anropbara. class-konstruktörer är endast konstruerbara.

Om du försöker anropa en icke-anropbar funktion eller konstruera en icke-konstruerbar funktion kommer du att få ett körfel.


Med detta vetskap kan vi säga följande.

Utbytbar:

  • Funktioner som inte använder this eller arguments.
  • Funktioner som används med .bind(this)

Inte utbytbar:

  • Konstruktörsfunktioner
  • Funktioner/metoder tillagda på ett prototyp (eftersom de vanligtvis använder this)
  • Variadiska funktioner (om de använder arguments (se nedan))
  • Generatorfunktioner, som kräver function*-notationen

Låt oss ta en närmare titt på detta med hjälp av dina exempel:

Konstruktörsfunktion

Det här kommer inte att fungera eftersom pilfunktioner inte kan anropas med new. Fortsätt använda en funktionsdeklaration/uttryck eller använd class.

Prototypmetoder

Troligtvis inte, eftersom prototypmetoder vanligtvis använder this för att komma åt instansen. Om de inte använder this, då kan du ersätta det. Men om du främst bryr dig om kortfattad syntax, använd class med dess kortfattade metoddsyntax:

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

Objektmetoder

På samma sätt för metoder i ett objektsliteral. Om metoden vill referera till objektet självt via this, fortsätt använda funktionsuttryck, eller använd den nya metodsynaxen:

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

Återuppringningar

Det beror på. Du bör definitivt ersätta det om du aliaserar den yttre this eller använder .bind(this):

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

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

Men: Om koden som anropar callbacken uttryckligen sätter this till ett specifikt värde, vilket ofta är fallet med eventhanterare, särskilt med jQuery, och callbacken använder this (eller arguments), kan du inte använda en arrow-funktion!

Variadiska funktioner

Eftersom pilfunktioner inte har sina egna arguments, kan du inte helt enkelt ersätta dem med en pilfunktion. Däremot introducerar ES2015 ett alternativ till att använda arguments: restparametern.

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

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

Relaterad fråga:

  • När ska jag använda pilfunktioner i ECMAScript 6?
  • Har ES6-pilfunktioner sina egna arguments eller inte?
  • Vilka är skillnaderna (om några) mellan ES6-pilfunktioner och funktioner bundna med Function.prototype.bind?
  • Hur man använder pilfunktioner (offentliga klassfält) som klassmetoder?

Ytterligare resurser:

  • MDN - Pilarfunktioner
  • YDKJS - Pilfunktioner

Svar #2

Arrow-funktioner => bästa ES6-funktionen hittills. De är en otroligt kraftfull tillägg till ES6, som jag använder konstant.

Vänta, du kan inte använda pilfunktion överallt i din kod, det kommer inte att fungera i alla situationer som this där pilfunktioner inte kan användas. Utan tvekan är pilfunktionen ett fantastiskt tillskott som ger kodens enkelhet.

Men du kan inte använda en pilfunktion när ett dynamiskt sammanhang krävs: definiera metoder, skapa objekt med konstruktörer, få målet från this när du hanterar händelser.

Funktioner med pil ska INTE användas eftersom:

  1. De har inte this

    Det använder "lexikal skopning" för att lista ut vad värdet av "this" ska vara. Med enkla ord använder lexikal skopning "this" från insidan av funktionens kropp.

  2. De har inte argument

    Pilfunktioner har inget arguments-objekt. Men samma funktionalitet kan uppnås med hjälp av restparametrar.

let sum = (...args) => args.reduce((x, y) => x + y, 0);
sum(3, 3, 1) // output: 7
  1. De kan inte användas med new

    Arrow-funktioner kan inte vara konstruktörer eftersom de inte har en prototype-egenskap.

När man ska använda pilfunktion och när man inte ska:

  1. Använd inte att lägga till en funktion som en egenskap i ett objektliteral eftersom vi inte kan komma åt detta.
  2. Funktionsuttryck är bäst för objektmetoder. Pilfunktioner är bäst för callback-funktioner eller metoder som map, reduce eller forEach.
  3. Använd funktionsdeklarationer för funktioner som du skulle kalla vid namn (eftersom de är hoistade).
  4. Använd pilfunktioner för callbacks (eftersom de tenderar att vara mer kortfattade).

Relaterade artiklar