Apakah 'Fungsi Panah' dan 'Fungsi' setara / dapat ditukar?

520

Fungsi panah di ES2015 memberikan sintaksis yang lebih ringkas.

  • Bisakah saya mengganti semua deklarasi / ekspresi fungsi saya dengan fungsi panah sekarang?
  • Apa yang harus saya perhatikan?

Contoh:

Fungsi konstruktor

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

// vs

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

Metode prototipe

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

// vs

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

Metode objek (literal)

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

// vs

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

Telepon balik

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

// vs

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

Fungsi variadik

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

// vs
const sum = (...args) => {
  // ...
};
Felix Kling
sumber
5
Pertanyaan serupa tentang fungsi panah telah muncul semakin banyak dengan ES2015 menjadi lebih populer. Saya tidak merasa ada pertanyaan / jawaban kanonik yang bagus untuk masalah ini jadi saya buat yang ini. Jika menurut Anda sudah ada yang bagus, beri tahu saya dan saya akan menutup yang ini sebagai duplikat atau menghapusnya. Jangan ragu untuk meningkatkan contoh atau menambahkan yang baru.
Felix Kling
2
Bagaimana dengan JavaScript ecma6 mengubah fungsi normal menjadi fungsi panah ? Tentu saja, pertanyaan normal tidak akan pernah sebagus dan generik seperti yang ditulis secara khusus menjadi kanonik.
Bergi
Lihatlah contoh Plnkr ini . Variabel kenaikannya thissangat berbeda timesCalledhanya dengan 1 setiap kali tombol dipanggil. Yang menjawab pertanyaan pribadi saya: .click( () => { } )dan .click(function() { }) keduanya membuat jumlah fungsi yang sama ketika digunakan dalam satu lingkaran seperti yang Anda lihat dari jumlah Guid di Plnkr.
abbaf33f

Jawaban:

750

tl; dr: Tidak! Fungsi panah dan deklarasi fungsi / ekspresi tidak setara dan tidak dapat diganti secara membabi buta.
Jika fungsi yang ingin Anda ganti tidak digunakan this, argumentsdan tidak dipanggil dengan new, maka ya.


Seperti yang sering: itu tergantung . Fungsi panah memiliki perilaku yang berbeda dari deklarasi / ekspresi fungsi, jadi mari kita lihat perbedaannya terlebih dahulu:

1. Leksikal thisdanarguments

Fungsi panah tidak memiliki thisatau argumentsmengikat sendiri . Sebaliknya, pengidentifikasi tersebut diselesaikan dalam ruang lingkup leksikal seperti variabel lainnya. Itu berarti bahwa di dalam fungsi panah, thisdan argumentsmerujuk ke nilai-nilai thisdan argumentsdi lingkungan fungsi panah didefinisikan dalam (yaitu "di luar" fungsi panah):

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

Dalam kasus ekspresi fungsi, thismerujuk ke objek yang dibuat di dalam createObject. Dalam kasus panah fungsi, thismengacu thisdari createObjectitu sendiri.

Ini membuat fungsi panah berguna jika Anda perlu mengakses thislingkungan saat ini:

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

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

Perhatikan bahwa ini juga berarti bahwa tidak mungkin untuk mengatur fungsi panah thisdengan .bindatau .call.

Jika Anda tidak terlalu mengenal this, pertimbangkan untuk membaca

2. Fungsi panah tidak dapat dipanggil dengan new

ES2015 membedakan antara fungsi yang bisa dipanggil dan fungsi yang bisa dikonstruksikan . Jika suatu fungsi dapat dibangun, itu dapat dipanggil dengan new, yaitu new User(). Jika suatu fungsi dipanggil, itu dapat dipanggil tanpa new(yaitu panggilan fungsi normal).

Fungsi yang dibuat melalui deklarasi / ekspresi fungsi keduanya dapat dibangun dan dapat dipanggil.
Fungsi panah (dan metode) hanya dapat dipanggil. classkonstruktor hanya dapat dibangun.

Jika Anda mencoba memanggil fungsi yang tidak dapat dipanggil atau untuk membangun fungsi yang tidak dapat dibangun, Anda akan mendapatkan kesalahan runtime.


Mengetahui hal ini, kita dapat menyatakan yang berikut.

Tergantikan:

  • Fungsi yang tidak menggunakan thisatau arguments.
  • Fungsi yang digunakan dengan .bind(this)

Tidak tergantikan:

  • Fungsi konstruktor
  • Fungsi / metode ditambahkan ke prototipe (karena biasanya mereka gunakan this)
  • Fungsi variadik (jika digunakan arguments(lihat di bawah))

Mari kita lihat lebih dekat ini menggunakan contoh Anda:

Fungsi konstruktor

Ini tidak akan berfungsi karena fungsi panah tidak dapat dipanggil dengan new. Tetap menggunakan fungsi deklarasi / ekspresi atau gunakan class.

Metode prototipe

Kemungkinan besar tidak, karena metode prototipe biasanya digunakan thisuntuk mengakses instance. Jika tidak digunakan this, maka Anda dapat menggantinya. Namun, jika Anda terutama memperhatikan sintaksis ringkas, gunakan classdengan sintaksis metode ringkasnya:

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

  getName() {
    return this.name;
  }
}

Metode objek

Demikian pula untuk metode dalam objek literal. Jika metode ingin merujuk objek itu sendiri melalui this, tetap menggunakan ekspresi fungsi, atau gunakan sintaks metode baru:

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

Telepon balik

Tergantung. Anda harus menggantinya jika Anda menggunakan aliasing luar thisatau menggunakan .bind(this):

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

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

Tetapi: Jika kode yang memanggil panggilan balik secara eksplisit ditetapkan thiske nilai tertentu, seperti yang sering terjadi dengan penangan acara, terutama dengan jQuery, dan panggilan balik menggunakan this(atau arguments), Anda tidak dapat menggunakan fungsi panah!

Fungsi variadik

Karena fungsi panah tidak memiliki fungsi panah sendiri arguments, Anda tidak dapat dengan mudah menggantinya dengan fungsi panah. Namun, ES2015 memperkenalkan alternatif untuk menggunakan arguments: parameter sisanya .

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

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

Pertanyaan terkait:

Sumber lebih lanjut:

Felix Kling
sumber
6
Mungkin perlu disebutkan bahwa leksikal thisjuga mempengaruhi superdan bahwa mereka tidak punya .prototype.
loganfsmyth
1
Akan lebih baik untuk menyebutkan bahwa mereka tidak secara sintaksis dapat dipertukarkan - fungsi panah ( AssignmentExpression) tidak bisa hanya dijatuhkan di mana-mana ekspresi fungsi ( PrimaryExpression) dapat dan itu membuat orang naik cukup sering (terutama karena telah diuraikan kesalahan dalam implementasi JS utama).
JMM
@ JMM: "sering membuat orang tersandung " bisakah Anda memberikan contoh nyata? Melacak spesifikasi, tampaknya tempat-tempat di mana Anda dapat menempatkan FE tetapi bukan AF akan menghasilkan kesalahan runtime ...
Felix Kling
Tentu, maksud saya hal-hal seperti mencoba untuk segera memanggil fungsi panah seperti ekspresi fungsi ( () => {}()) atau melakukan sesuatu seperti x || () => {}. Itulah yang saya maksud: kesalahan runtime (parse). (Dan meskipun itu masalahnya, cukup sering orang berpikir kesalahan itu salah.) Apakah Anda hanya mencoba untuk menutupi kesalahan logika yang tidak akan diperhatikan karena mereka tidak selalu kesalahan ketika diuraikan atau dieksekusi? newSalah satunya adalah kesalahan runtime bukan?
JMM
Berikut ini beberapa tautan yang muncul di alam bebas: subtack / node-browserify # 1499 , babel / babel-eslint # 245 (ini adalah panah async, tapi saya pikir ini masalah dasar yang sama), dan banyak masalah tentang Babel yang sulit ditemukan sekarang, tapi inilah satu T2847 .
JMM
11

Fungsi panah => fitur ES6 terbaik sejauh ini. Mereka adalah tambahan yang sangat kuat untuk ES6, yang saya gunakan terus-menerus.

Tunggu, Anda tidak dapat menggunakan fungsi panah di mana pun dalam kode Anda, ini tidak akan berfungsi dalam semua kasus seperti di thismana fungsi panah tidak dapat digunakan. Tanpa ragu, fungsi panah adalah tambahan yang bagus karena membawa kesederhanaan kode.

Tetapi Anda tidak dapat menggunakan fungsi panah ketika konteks dinamis diperlukan: mendefinisikan metode, membuat objek dengan konstruktor, mendapatkan target dari ini saat menangani acara.

Fungsi panah TIDAK boleh digunakan karena:

  1. Mereka tidak memiliki this

    Ini menggunakan "pelingkupan leksikal" untuk mencari tahu apa nilai " this" seharusnya. Dengan kata sederhana pelingkupan leksikal menggunakan " this" dari dalam tubuh fungsi.

  2. Mereka tidak memiliki arguments

    Fungsi panah tidak memiliki argumentsobjek. Tetapi fungsi yang sama dapat dicapai dengan menggunakan parameter lainnya.

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

  3. Mereka tidak dapat digunakan dengan new

    Fungsi panah tidak dapat menjadi konstruktor karena tidak memiliki properti prototipe.

Kapan menggunakan fungsi panah dan kapan tidak:

  1. Jangan gunakan untuk menambahkan fungsi sebagai properti dalam objek literal karena kita tidak dapat mengakses ini.
  2. Ekspresi fungsi adalah yang terbaik untuk metode objek. Panah fungsi yang terbaik untuk callback atau metode seperti map, reduce, atau forEach.
  3. Gunakan deklarasi fungsi untuk fungsi yang akan Anda panggil dengan nama (karena mereka diangkat).
  4. Gunakan fungsi panah untuk panggilan balik (karena mereka cenderung terser).
Ashutosh
sumber
2
2. Mereka tidak memiliki argumen, saya minta maaf tidak benar, kita dapat berdebat tanpa menggunakan ... operator, mungkin Anda ingin mengatakan bahwa Anda tidak memiliki argumen sebagai array
Carmine Tambascia
@CarmineTambascia Baca tentang argumentsobjek khusus yang tidak tersedia dalam fungsi panah di sini: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
vichle
0

Untuk menggunakan fungsi panah function.prototype.call, saya membuat fungsi pembantu pada prototipe objek:

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

pemakaian

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

Edit

Anda TIDAK PERLU pembantu. Anda bisa melakukannya:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
toddmo
sumber