Apa arti beberapa fungsi panah dalam javascript?


472

Saya telah membaca banyak reactkode dan saya melihat hal-hal seperti ini yang tidak saya mengerti:

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}

11
Hanya untuk bersenang-senang, Kyle Simpson memasukkan semua jalur keputusan untuk panah ke dalam bagan alur ini . Sumber: Komentarnya pada posting blog Mozilla Hacks berjudul ES6 In Depth: Arrow functions
gfullam

Karena ada jawaban yang bagus dan sekarang karunia. Bisakah Anda jelaskan apa yang tidak Anda pahami bahwa jawaban di bawah ini tidak mencakup.
Michael Warner

5
URL untuk bagan alur fungsi panah sekarang rusak karena ada edisi baru buku. URL yang berfungsi ada
Dhiraj Gupta

Jawaban:


833

Itu adalah fungsi kari

Pertama, periksa fungsi ini dengan dua parameter ...

const add = (x, y) => x + y
add(2, 3) //=> 5

Ini dia lagi dalam bentuk kari ...

const add = x => y => x + y

Berikut adalah 1 kode yang sama tanpa fungsi panah ...

const add = function (x) {
  return function (y) {
    return x + y
  }
}

Fokus pada return

Mungkin membantu memvisualisasikannya dengan cara lain. Kita tahu bahwa fungsi panah berfungsi seperti ini - mari kita perhatikan nilai pengembaliannya .

const f = someParam => returnValue

Jadi addfungsi kita mengembalikan fungsi - kita dapat menggunakan tanda kurung untuk menambahkan kejelasan. The tebal teks nilai kembali dari fungsi kitaadd

const add = x => (y => x + y)

Dengan kata lain addbeberapa angka mengembalikan fungsi

add(2) // returns (y => 2 + y)

Memanggil fungsi kari

Jadi untuk menggunakan fungsi kari kita, kita harus menyebutnya sedikit berbeda ...

add(2)(3)  // returns 5

Ini karena panggilan fungsi pertama (luar) mengembalikan fungsi kedua (dalam). Hanya setelah kita memanggil fungsi kedua barulah kita benar-benar mendapatkan hasilnya. Ini lebih jelas jika kita memisahkan panggilan pada dua baris ...

const add2 = add(2) // returns function(y) { return 2 + y }
add2(3)             // returns 5

Menerapkan pemahaman baru kami pada kode Anda

terkait: "Apa perbedaan antara pengikatan, aplikasi parsial, dan kari?"

Oke, sekarang kita mengerti cara kerjanya, mari kita lihat kode Anda

handleChange = field => e => {
  e.preventDefault()
  /// Do something here
}

Kami akan mulai dengan mewakilinya tanpa menggunakan fungsi panah ...

handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  };
};

Namun, karena fungsi panah mengikat secara leksikal this, itu sebenarnya akan terlihat lebih seperti ini ...

handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  }.bind(this)
}.bind(this)

Mungkin sekarang kita bisa melihat apa yang dilakukan ini dengan lebih jelas. The handleChangefungsi menciptakan fungsi untuk ditentukan field. Ini adalah teknik Bereaksi yang berguna karena Anda harus mengatur pendengar Anda sendiri pada setiap input untuk memperbarui status aplikasi Anda. Dengan menggunakan handleChangefungsi ini, kita dapat menghilangkan semua kode duplikat yang akan menghasilkan pengaturan changependengar untuk setiap bidang. Keren!

1 Di sini saya tidak perlu mengikat leksikal thiskarena addfungsi aslinya tidak menggunakan konteks apa pun, jadi tidak penting untuk melestarikannya dalam kasus ini.


Bahkan lebih banyak panah

Lebih dari dua fungsi panah dapat diurutkan, jika perlu -

const three = a => b => c =>
  a + b + c

const four = a => b => c => d =>
  a + b + c + d

three (1) (2) (3) // 6

four (1) (2) (3) (4) // 10

Fungsi kari mampu hal-hal mengejutkan. Di bawah ini kita melihat $didefinisikan sebagai fungsi kari dengan dua parameter, namun di situs panggilan, tampak seolah-olah kita dapat menyediakan sejumlah argumen. Kari adalah abstraksi dari arity -

const $ = x => k =>
  $ (k (x))
  
const add = x => y =>
  x + y

const mult = x => y =>
  x * y
  
$ (1)           // 1
  (add (2))     // + 2 = 3
  (mult (6))    // * 6 = 18
  (console.log) // 18
  
$ (7)            // 7
  (add (1))      // + 1 = 8
  (mult (8))     // * 8 = 64
  (mult (2))     // * 2 = 128
  (mult (2))     // * 2 = 256
  (console.log)  // 256

Aplikasi sebagian

Aplikasi parsial adalah konsep terkait. Hal ini memungkinkan kita untuk menerapkan sebagian fungsi, mirip dengan kari, kecuali fungsi tidak harus didefinisikan dalam bentuk kari -

const partial = (f, ...a) => (...b) =>
  f (...a, ...b)

const add3 = (x, y, z) =>
  x + y + z

partial (add3) (1, 2, 3)   // 6

partial (add3, 1) (2, 3)   // 6

partial (add3, 1, 2) (3)   // 6

partial (add3, 1, 2, 3) () // 6

partial (add3, 1, 1, 1, 1) (1, 1, 1, 1, 1) // 3

Inilah demo partialyang dapat Anda mainkan di browser Anda sendiri -

const partial = (f, ...a) => (...b) =>
  f (...a, ...b)
  
const preventDefault = (f, event) =>
  ( event .preventDefault ()
  , f (event)
  )
  
const logKeypress = event =>
  console .log (event.which)
  
document
  .querySelector ('input[name=foo]')
  .addEventListener ('keydown', partial (preventDefault, logKeypress))
<input name="foo" placeholder="type here to see ascii codes" size="50">


2
Ini luar biasa! Seberapa sering seseorang benar-benar menetapkan '$'? Atau apakah ini alias untuk reaksi ini? Maafkan ketidaktahuan saya pada yang terakhir, hanya ingin tahu karena saya tidak melihat simbol mendapatkan tugas terlalu sering dalam bahasa lain.
Caperneoignis

7
@Caperneoignis $digunakan untuk demo konsep, tetapi Anda bisa memberi nama apa pun yang Anda inginkan. Secara kebetulan tetapi sama sekali tidak terkait, $ telah digunakan di perpustakaan populer seperti jQuery, di mana $semacam semacam titik masuk global ke seluruh fungsi perpustakaan. Saya pikir itu sudah digunakan pada orang lain juga. Lain yang akan Anda lihat adalah _, dipopulerkan di perpustakaan seperti garis bawah dan lodash. Tidak ada satu simbol pun yang lebih bermakna dari yang lainnya; Anda menetapkan arti untuk program Anda . Ini hanya berlaku JavaScript: D
Terima kasih

1
frijoli suci, jawaban yang bagus. berharap op akan menerima
mtyson

2
@Blake Anda bisa mendapatkan pemahaman yang lebih baik $dengan melihat cara penggunaannya. Jika Anda bertanya tentang implementasi itu sendiri, $adalah fungsi yang menerima nilai xdan mengembalikan fungsi baru k => .... Melihat tubuh fungsi yang dikembalikan, kita melihat k (x)sehingga kita tahu kpasti juga sebuah fungsi, dan apa pun hasilnya k (x)akan dimasukkan kembali $ (...), yang kita tahu mengembalikan yang lain k => ..., dan seterusnya ... jika Anda masih macet, beri tahu saya.
Terima kasih

2
sementara jawaban ini menjelaskan cara kerjanya dan pola apa yang ada dengan teknik ini. Saya merasa tidak ada yang spesifik mengapa ini sebenarnya solusi yang lebih baik dalam skenario apa pun. Dalam situasi apa, abc(1,2,3)kurang dari ideal daripada abc(1)(2)(3). Lebih sulit untuk berpikir tentang logika kode dan sulit untuk membaca fungsi abc dan lebih sulit untuk membaca pemanggilan fungsi. Sebelum Anda hanya perlu tahu apa yang dilakukan abc, sekarang Anda tidak yakin apa fungsi yang tidak disebutkan namanya yang dilakukan abc, dan dua kali pada saat itu.
Muhammad Umer

57

Memahami sintaks fungsi panah yang tersedia akan memberi Anda pemahaman tentang perilaku apa yang mereka perkenalkan ketika 'dirantai' seperti dalam contoh yang Anda berikan.

Ketika fungsi panah ditulis tanpa tanda kurung, dengan atau tanpa beberapa parameter, ekspresi yang membentuk tubuh fungsi secara implisit dikembalikan. Dalam contoh Anda, ekspresi itu adalah fungsi panah lain.

No arrow funcs              Implicitly return `e=>{…}`    Explicitly return `e=>{…}` 
---------------------------------------------------------------------------------
function (field) {         |  field => e => {            |  field => {
  return function (e) {    |                             |    return e => {
      e.preventDefault()   |    e.preventDefault()       |      e.preventDefault()
  }                        |                             |    }
}                          |  }                          |  }

Keuntungan lain dari menulis fungsi anonim menggunakan sintaks panah adalah bahwa mereka terikat secara leksikal ke ruang lingkup di mana mereka didefinisikan. Dari 'Fungsi panah' di MDN :

Sebuah ekspresi panah fungsi memiliki sintaks yang lebih pendek dibandingkan dengan ekspresi fungsi dan leksikal mengikat ini nilai. Fungsi panah selalu anonim .

Ini khususnya terkait dalam contoh Anda mengingat diambil dari aplikasi. Seperti yang ditunjukkan oleh @naomik, di Bereaksi Anda sering mengakses fungsi anggota komponen menggunakan this. Sebagai contoh:

Unbound                     Explicitly bound            Implicitly bound 
------------------------------------------------------------------------------
function (field) {         |  function (field) {       |  field => e => {
  return function (e) {    |    return function (e) {  |    
      this.setState(...)   |      this.setState(...)   |    this.setState(...)
  }                        |    }.bind(this)           |    
}                          |  }.bind(this)             |  }

53

Tip umum, jika Anda bingung dengan salah satu sintaks JS baru dan bagaimana itu akan dikompilasi, Anda dapat memeriksa babel . Misalnya menyalin kode Anda dalam babel dan memilih preset es2015 akan memberikan output seperti ini

handleChange = function handleChange(field) {
 return function (e) {
 e.preventDefault();
  // Do something here
   };
 };

Babel


42

Pikirkan seperti ini, setiap kali Anda melihat panah, Anda menggantinya dengan function.
function parametersdidefinisikan sebelum panah.
Jadi, dalam contoh Anda:

field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}

dan kemudian bersama-sama:

function (field) { 
    return function (e) { 
        e.preventDefault(); 
    };
}

Dari dokumen :

// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
   // equivalent to:  => { return expression; }

// Parentheses are optional when there's only one argument:
singleParam => { statements }
singleParam => expression

6
Jangan lupa menyebutkan ikatan leksikal this.
Terima kasih

30

Singkat dan sederhana 🎈

Ini adalah fungsi yang mengembalikan fungsi lain yang ditulis dengan cara singkat.

const handleChange = field => e => {
  e.preventDefault()
  // Do something here
}

// is equal to 
function handleChange(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
  }
}

Mengapa orang melakukannya

Pernahkah Anda berhadapan ketika Anda perlu menulis fungsi yang dapat dikustomisasi? Atau Anda harus menulis fungsi panggilan balik yang memiliki parameter (argumen) tetap, tetapi Anda perlu meneruskan lebih banyak variabel ke fungsi tetapi menghindari variabel global? Jika jawaban Anda " ya " maka itu adalah cara melakukannya.

Misalnya kita memiliki buttonpanggilan balik onClick. Dan kita perlu beralih idke fungsi, tetapi onClickhanya menerima satu parameter event, kita tidak bisa melewatkan parameter tambahan seperti ini:

const handleClick = (event, id) {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

Itu tidak akan bekerja!

Oleh karena itu kami membuat fungsi yang akan mengembalikan fungsi lain dengan ruang lingkup variabel sendiri tanpa variabel global, karena variabel global jahat 😈.

Di bawah fungsi handleClick(props.id)}akan dipanggil dan mengembalikan fungsi dan itu akan ada iddalam ruang lingkupnya! Tidak peduli berapa kali akan ditekan id tidak akan mempengaruhi atau mengubah satu sama lain, mereka benar-benar terisolasi.

const handleClick = id => event {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

const Confirm = props => (
  <div>
    <h1>Are you sure to delete?</h1>
    <button onClick={handleClick(props.id)}>
      Delete
    </button>
  </div
)

2

Contoh dalam pertanyaan Anda adalah curried functionyang menggunakan arrow functiondan memiliki implicit returnargumen pertama.

Fungsi panah secara lexic mengikat ini yaitu mereka tidak memiliki thisargumen sendiri tetapi mengambil thisnilai dari lingkup melampirkan

Setara dengan kode di atas adalah

const handleChange = (field) {
  return function(e) {
     e.preventDefault();
     /// Do something here
  }.bind(this);
}.bind(this);

Satu hal lagi yang perlu diperhatikan tentang contoh Anda adalah mendefinisikan handleChangesebagai konst atau fungsi. Mungkin Anda menggunakannya sebagai bagian dari metode kelas dan menggunakan aclass fields syntax

jadi alih-alih mengikat fungsi luar secara langsung, Anda akan mengikatnya di konstruktor kelas

class Something{
    constructor(props) {
       super(props);
       this.handleChange = this.handleChange.bind(this);
    }
    handleChange(field) {
        return function(e) {
           e.preventDefault();
           // do something
        }
    }
}

Hal lain yang perlu diperhatikan dalam contoh adalah perbedaan antara pengembalian implisit dan eksplisit.

const abc = (field) => field * 2;

Di atas adalah contoh pengembalian implisit yaitu. dibutuhkan bidang nilai sebagai argumen dan mengembalikan hasil field*2yang secara eksplisit menentukan fungsi untuk kembali

Untuk pengembalian eksplisit Anda akan secara eksplisit memberi tahu metode untuk mengembalikan nilai

const abc = () => { return field*2; }

Hal lain yang perlu diperhatikan tentang fungsi panah adalah bahwa mereka tidak memiliki fungsi panah sendiri argumentstetapi mewarisi itu dari ruang lingkup orang tua.

Misalnya jika Anda hanya mendefinisikan fungsi panah seperti

const handleChange = () => {
   console.log(arguments) // would give an error on running since arguments in undefined
}

Sebagai fungsi panah alternatif, berikan parameter lainnya yang dapat Anda gunakan

const handleChange = (...args) => {
   console.log(args);
}

1

Mungkin tidak sepenuhnya terkait, tetapi karena pertanyaan yang disebutkan bereaksi menggunakan case (dan saya terus menabrak utas SO ini): Ada satu aspek penting dari fungsi panah ganda yang tidak disebutkan secara eksplisit di sini. Hanya panah 'pertama' (fungsi) yang dinamai (dan karenanya 'dapat dibedakan' oleh run-time), semua panah berikut ini bersifat anonim dan dari sudut pandang Bereaksi dihitung sebagai objek 'baru' pada setiap render.

Dengan demikian fungsi panah ganda akan menyebabkan PureComponent untuk merender setiap saat.

Contoh

Anda memiliki komponen induk dengan penangan perubahan sebagai:

handleChange = task => event => { ... operations which uses both task and event... };

dan dengan render seperti:

{ tasks.map(task => <MyTask handleChange={this.handleChange(task)}/> }

handleChange kemudian digunakan pada input atau klik. Dan ini semua bekerja dan terlihat sangat bagus. TETAPI itu berarti bahwa setiap perubahan yang akan menyebabkan orang tua rerender (seperti perubahan status yang sama sekali tidak terkait) juga akan merender SEMUA MyTask Anda juga meskipun itu adalah komponen PureComponents.

Ini dapat diatasi dengan banyak cara seperti mengoper panah 'terluar' dan objek yang akan Anda beri makan atau menulis fungsi custom shouldUpdate atau kembali ke dasar-dasar seperti menulis fungsi bernama (dan mengikat ini secara manual ...)

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.