Mengapa menggunakan ekspresi fungsi bernama?

94

Kami memiliki dua cara berbeda untuk melakukan ekspresi fungsi dalam JavaScript:

Ekspresi fungsi bernama (NFE) :

var boo = function boo () {
  alert(1);
};

Ekspresi fungsi anonim :

var boo = function () {
  alert(1);
};

Dan keduanya bisa dipanggil dengan boo();. Saya benar-benar tidak dapat melihat mengapa / kapan saya harus menggunakan fungsi anonim dan kapan saya harus menggunakan Ekspresi Fungsi Bernama. Apa perbedaan diantara mereka?

Afshin Mehrabani
sumber

Jawaban:

86

Dalam kasus ekspresi fungsi anonim, fungsinya anonim  - secara harfiah, tidak memiliki nama. Variabel yang Anda tetapkan memiliki nama, tetapi fungsinya tidak. (Pembaruan: Itu benar melalui ES5. Mulai ES2015 [alias ES6], seringkali fungsi yang dibuat dengan ekspresi anonim mendapat nama sebenarnya [tetapi bukan pengenal otomatis], baca terus ...)

Nama berguna. Nama dapat dilihat di pelacakan tumpukan, tumpukan panggilan, daftar breakpoint, dll. Nama adalah Good Thing ™.

(Dulu Anda harus berhati-hati terhadap ekspresi fungsi bernama dalam versi IE [IE8 dan yang lebih lama], karena mereka secara keliru membuat dua objek fungsi yang benar-benar terpisah pada dua waktu yang sama sekali berbeda [selengkapnya di artikel blog saya Ambil ganda ]. Jika Anda perlu mendukung IE8 [!!], mungkin yang terbaik adalah tetap menggunakan ekspresi fungsi anonim atau deklarasi fungsi , tetapi hindari ekspresi fungsi bernama.)

Satu hal penting tentang ekspresi fungsi bernama adalah ia membuat pengenal dalam lingkup dengan nama itu untuk fungsi di dalam badan functon:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

Namun, pada ES2015, banyak ekspresi fungsi "anonim" membuat fungsi dengan nama, dan ini didahului oleh berbagai mesin JavaScript modern yang cukup pintar dalam menyimpulkan nama dari konteks. Di ES2015, ekspresi fungsi anonim Anda menghasilkan fungsi dengan nama boo. Namun, bahkan dengan semantik ES2015 +, pengenal otomatis tidak dibuat:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

Penetapan nama fungsi dilakukan dengan operasi abstrak SetFunctionName yang digunakan dalam berbagai operasi dalam spesifikasi.

Versi singkatnya pada dasarnya setiap kali ekspresi fungsi anonim muncul di sisi kanan sesuatu seperti penugasan atau inisialisasi, seperti:

var boo = function() { /*...*/ };

(atau bisa jadi letatau constlebih daripada var) , atau

var obj = {
    boo: function() { /*...*/ }
};

atau

doSomething({
    boo: function() { /*...*/ }
});

(dua yang terakhir itu benar-benar hal yang sama) , fungsi yang dihasilkan akan memiliki nama ( boo, dalam contoh).

Ada pengecualian penting dan disengaja: Menugaskan ke properti pada objek yang sudah ada:

obj.boo = function() { /*...*/ }; // <== Does not get a name

Ini karena masalah kebocoran informasi yang muncul ketika fitur baru sedang dalam proses penambahan; detail dalam jawaban saya untuk pertanyaan lain di sini .

TJ Crowder
sumber
1
Perlu dicatat setidaknya ada dua tempat di mana penggunaan NFE masih memberikan keuntungan konkret: pertama, untuk fungsi yang dimaksudkan untuk digunakan sebagai konstruktor melalui newoperator (memberikan semua nama fungsi tersebut membuat .constructorproperti lebih berguna selama debugging untuk mencari tahu apa sih beberapa objek adalah turunan dari), dan untuk literal fungsi diteruskan langsung ke fungsi tanpa terlebih dahulu ditugaskan ke properti atau variabel (misalnya setTimeout(function () {/*do stuff*/});). Bahkan Chrome menampilkan ini seolah- (anonymous function)olah Anda membantunya dengan menamainya.
Mark Amery
4
@ MarkAmery: "apakah ini masih benar? Saya ... mencoba CTRL-F untuk aturan ini dan tidak dapat menemukannya" Oh ya. :-) Itu bertebaran di seluruh spesifikasi daripada berada di satu tempat yang mendefinisikan seperangkat aturan, cukup cari "setFunctionName". Saya telah menambahkan sebagian kecil tautan di atas, tetapi saat ini muncul di ~ 29 tempat berbeda. Saya hanya akan sedikit terkejut jika setTimeoutcontoh Anda tidak mengambil nama dari argumen resmi yang dinyatakan untuk setTimeout, jika ada. :-) Tapi ya, NFE pasti berguna jika Anda tahu Anda tidak akan berurusan dengan browser lama yang membuat hash dari mereka.
TJ Crowder
24

Fungsi penamaan berguna jika mereka perlu mereferensikan dirinya sendiri (misalnya untuk panggilan rekursif). Memang, jika Anda meneruskan ekspresi fungsi literal sebagai argumen langsung ke fungsi lain, ekspresi fungsi tersebut tidak dapat merujuk dirinya sendiri secara langsung dalam mode ketat ES5 kecuali jika dinamai.

Misalnya, perhatikan kode ini:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

Tidak mungkin untuk menulis kode ini dengan bersih seperti ini jika ekspresi fungsi yang diteruskan ke setTimeoutanonim; kita perlu menetapkannya ke variabel sebelum setTimeoutpanggilan. Dengan cara ini, dengan ekspresi fungsi bernama, sedikit lebih pendek dan lebih rapi.

Secara historis mungkin untuk menulis kode seperti ini bahkan menggunakan ekspresi fungsi anonim, dengan mengeksploitasi arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... tetapi arguments.calleesudah tidak digunakan lagi, dan langsung dilarang dalam mode ketat ES5. Oleh karena itu MDN menyarankan:

Hindari menggunakan arguments.callee()dengan memberi nama ekspresi fungsi atau menggunakan deklarasi fungsi di mana suatu fungsi harus memanggil dirinya sendiri.

(penekanan milikku)

Mark Amery
sumber
3

Jika suatu fungsi ditetapkan sebagai Ekspresi Fungsi, fungsi tersebut dapat diberi nama.

Ini hanya akan tersedia di dalam fungsi (kecuali IE8-).

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

Nama ini ditujukan untuk pemanggilan fungsi rekursif yang andal, bahkan jika itu ditulis ke variabel lain.

Selain itu, nama NFE (Named Function Expression) DAPAT ditimpa dengan Object.defineProperty(...)metode sebagai berikut:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

Catatan: bahwa dengan Deklarasi Fungsi ini tidak bisa dilakukan. Nama fungsi internal "khusus" ini ditetapkan hanya dalam sintaks Ekspresi Fungsi.

Roma
sumber
2

Anda harus selalu menggunakan ekspresi fungsi bernama , itu sebabnya:

  1. Anda dapat menggunakan nama fungsi itu saat Anda membutuhkan rekursi.

  2. Fungsi anonim tidak membantu saat debugging karena Anda tidak dapat melihat nama fungsi yang menyebabkan masalah.

  3. Jika Anda tidak menyebutkan suatu fungsi, nanti akan lebih sulit untuk memahami apa yang dilakukannya. Memberinya nama membuatnya lebih mudah untuk dipahami.

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

Di sini, misalnya, karena bilah nama digunakan dalam ekspresi fungsi, itu tidak dideklarasikan di lingkup luar. Dengan ekspresi fungsi bernama, nama ekspresi fungsi diapit dalam cakupannya sendiri.

Antero Ukkonen
sumber
1

Lebih baik menggunakan ekspresi fungsi bernama, jika Anda ingin dapat merujuk fungsi yang dimaksud tanpa harus bergantung pada fitur yang sudah tidak digunakan lagi seperti arguments.callee.

Sudhir Bastakoti
sumber
3
Itu lebih merupakan komentar daripada jawaban. Mungkin elaborasi akan bermanfaat
vsync