Jelaskan sintaks fungsi anonim yang dienkapsulasi

373

Ringkasan

Bisakah Anda menjelaskan alasan di balik sintaks untuk fungsi anonim yang dienkapsulasi dalam JavaScript? Mengapa ini bekerja: (function(){})();tetapi ini tidak function(){}();:?


Apa yang saya tahu

Dalam JavaScript, seseorang membuat fungsi bernama seperti ini:

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

Anda juga dapat membuat fungsi anonim dan menetapkannya ke variabel:

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

Anda dapat merangkum satu blok kode dengan membuat fungsi anonim, lalu membungkusnya dalam tanda kurung dan langsung mengeksekusinya:

(function(){
    alert(2 + 2);
})();

Ini berguna ketika membuat skrip termodulasi, untuk menghindari kekacauan lingkup saat ini, atau cakupan global, dengan variabel yang berpotensi bertentangan - seperti dalam kasus skrip Greasemonkey, plugin jQuery, dll.

Sekarang, saya mengerti mengapa ini berhasil. Tanda kurung melampirkan konten dan hanya memperlihatkan hasilnya (saya yakin ada cara yang lebih baik untuk menggambarkan itu), seperti dengan (2 + 2) === 4.


Apa yang tidak saya mengerti

Tapi saya tidak mengerti mengapa ini tidak bekerja sama baiknya:

function(){
    alert(2 + 2);
}();

Bisakah Anda menjelaskannya kepada saya?

Premasagar
sumber
39
Saya pikir semua notasi yang bervariasi ini dan cara mendefinisikan / mengatur / memanggil fungsi adalah bagian yang paling membingungkan pada awalnya bekerja dengan javascript. Orang cenderung tidak membicarakannya juga. Ini bukan poin yang ditekankan dalam panduan atau blog. Ini membuat saya terhenyak karena ini adalah sesuatu yang membingungkan bagi kebanyakan orang, dan orang-orang yang fasih dalam js pasti telah melewatinya juga. Ini seperti kenyataan tabu kosong yang tidak pernah dibicarakan.
ahnbizcad
1
Juga membaca tentang tujuan konstruk ini , atau cek ( teknis ) penjelasan (juga di sini ). Untuk penempatan tanda kurung, lihat pertanyaan ini tentang lokasi mereka .
Bergi
OT: Untuk orang-orang yang ingin tahu di mana fungsi-fungsi anonim yang digunakan banyak, silakan baca adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
Alireza Fattahi
Ini adalah kasus khas Ekspresi Fungsi Segera Dibatalkan (IIFE).
Aminu Kano

Jawaban:

410

Ini tidak berfungsi karena sedang diuraikan sebagai FunctionDeclaration, dan pengidentifikasi nama deklarasi fungsi adalah wajib .

Ketika Anda mengelilinginya dengan tanda kurung itu dievaluasi sebagai FunctionExpression, dan ekspresi fungsi dapat dinamai atau tidak.

Tata bahasanya FunctionDeclarationterlihat seperti ini:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Dan FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Karena Anda dapat melihat token Identifier(Identifier opt ) FunctionExpressionopsional, oleh karena itu kami dapat memiliki ekspresi fungsi tanpa nama yang ditentukan:

(function () {
    alert(2 + 2);
}());

Atau yang bernama ekspresi fungsi:

(function foo() {
    alert(2 + 2);
}());

Tanda kurung (secara resmi disebut Pengelompokan Operator ) hanya dapat mengelilingi ekspresi, dan ekspresi fungsi dievaluasi.

Dua produksi tata bahasa bisa ambigu, dan mereka dapat terlihat persis sama, misalnya:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Parser tahu apakah itu a FunctionDeclarationatau a FunctionExpression, tergantung pada konteks di mana ia muncul.

Dalam contoh di atas, yang kedua adalah ekspresi karena operator Koma juga dapat menangani hanya ekspresi.

Di sisi lain, FunctionDeclarations sebenarnya hanya dapat muncul dalam apa yang disebut " Program" kode, artinya kode di luar dalam lingkup global, dan di dalam FunctionBodyfungsi-fungsi lainnya.

Fungsi di dalam blok harus dihindari, karena dapat menyebabkan perilaku yang tidak terduga, misalnya:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

Kode di atas seharusnya benar-benar menghasilkan a SyntaxError, karena a Blockhanya dapat berisi pernyataan (dan Spesifikasi Naskah ECMAS tidak mendefinisikan pernyataan fungsi apa pun), tetapi sebagian besar implementasi bersifat toleran, dan hanya akan mengambil fungsi kedua, yang diperingatkan 'false!'.

Implementasi Mozilla -Rhino, SpiderMonkey, - memiliki perilaku yang berbeda. Tata bahasa mereka mengandung Pernyataan Fungsi non-standar , yang berarti bahwa fungsi tersebut akan dievaluasi pada saat run-time , bukan pada waktu parse, seperti yang terjadi pada FunctionDeclarations. Dalam implementasi tersebut kita akan mendefinisikan fungsi pertama.


Fungsi dapat dideklarasikan dengan cara yang berbeda, bandingkan yang berikut :

1- Fungsi yang didefinisikan dengan konstruktor Fungsi yang ditugaskan ke variabel multiply :

var multiply = new Function("x", "y", "return x * y;");

2- Deklarasi fungsi dari suatu fungsi bernama multiply :

function multiply(x, y) {
    return x * y;
}

3- Ekspresi fungsi yang ditugaskan ke variabel multiply :

var multiply = function (x, y) {
    return x * y;
};

4- Nama fungsi func_name ekspresi , ditugaskan untuk variabel multiply :

var multiply = function func_name(x, y) {
    return x * y;
};
CMS
sumber
2
Jawaban CMS benar. Untuk penjelasan mendalam yang sangat baik tentang deklarasi dan ekspresi fungsi, lihat artikel ini oleh kangax .
Tim Down,
Ini jawaban yang bagus. Itu tampaknya terkait erat dengan bagaimana teks sumber diuraikan - dan struktur BNF. dalam contoh Anda 3, haruskah saya mengatakan bahwa itu adalah ekspresi fungsi karena mengikuti tanda sama dengan, sedangkan bentuk yang merupakan deklarasi fungsi / pernyataan ketika muncul pada baris dengan sendirinya? Saya ingin tahu apa tujuan dari itu - apakah itu hanya ditafsirkan sebagai deklarasi fungsi bernama, tetapi tanpa nama? Tujuan apa yang berfungsi jika Anda tidak menugaskannya ke variabel, menamainya, atau memanggilnya?
Breton
1
Aha. Sangat berguna. Terima kasih, CMS. Bagian dari dokumen Mozilla yang Anda
tautkan
1
1, meskipun Anda memiliki braket penutupan di posisi yang salah dalam ekspresi fungsi :-)
NickFitz
1
@GovindRai, Tidak. Deklarasi fungsi ditangani pada waktu kompilasi dan deklarasi fungsi duplikat menimpa deklarasi sebelumnya. Saat runtime, deklarasi fungsi sudah tersedia dan dalam hal ini, salah satu yang tersedia adalah yang memberi peringatan "false". Untuk info lebih lanjut, baca Anda tidak tahu JS
Saurabh Misra
50

Meskipun ini adalah pertanyaan dan jawaban lama, ini membahas topik yang hingga hari ini banyak dilemparkan pengembang untuk loop. Saya tidak bisa menghitung jumlah kandidat pengembang JavaScript yang saya wawancarai yang tidak bisa memberi tahu saya perbedaan antara deklarasi fungsi dan ekspresi fungsi dan yang tidak tahu apa itu ekspresi fungsi yang segera dipanggil.

Saya ingin menyebutkan, satu hal yang sangat penting yaitu potongan kode Premasagar tidak akan berfungsi bahkan jika dia memberinya pengenal nama.

function someName() {
    alert(2 + 2);
}();

Alasan ini tidak berhasil adalah bahwa mesin JavaScript mengartikan ini sebagai deklarasi fungsi diikuti oleh operator pengelompokan yang sama sekali tidak terkait yang tidak mengandung ekspresi, dan operator pengelompokan harus berisi ekspresi. Menurut JavaScript, potongan kode di atas setara dengan yang berikut.

function someName() {
    alert(2 + 2);
}

();

Hal lain yang ingin saya tunjukkan yang mungkin bermanfaat bagi sebagian orang adalah bahwa pengenal nama apa pun yang Anda berikan untuk ekspresi fungsi cukup berguna dalam konteks kode kecuali dari dalam definisi fungsi itu sendiri.

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

Tentu saja, menggunakan pengidentifikasi nama dengan definisi fungsi Anda selalu membantu ketika datang ke kode debugging, tapi itu sesuatu yang sama sekali berbeda ... :-)

natlee75
sumber
17

Jawaban yang bagus sudah diposting. Tapi saya ingin mencatat bahwa deklarasi fungsi mengembalikan catatan penyelesaian kosong:

14.1.20 - Semantics Runtime: Evaluasi

FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Kembalikan Penyelesaian Normal (kosong).

Fakta ini tidak mudah untuk diamati, karena sebagian besar cara mencoba untuk mendapatkan nilai yang dikembalikan akan mengkonversi deklarasi fungsi ke ekspresi fungsi. Namun, evalperlihatkan:

var r = eval("function f(){}");
console.log(r); // undefined

Memanggil catatan penyelesaian kosong tidak masuk akal. Itu sebabnya function f(){}()tidak bisa bekerja. Bahkan mesin JS bahkan tidak berusaha menyebutnya, tanda kurung dianggap sebagai bagian dari pernyataan lain.

Tetapi jika Anda membungkus fungsi dalam tanda kurung, itu menjadi ekspresi fungsi:

var r = eval("(function f(){})");
console.log(r); // function f(){}

Ekspresi fungsi mengembalikan objek fungsi. Dan karena itu Anda dapat menyebutnya: (function f(){})().

Oriol
sumber
Malu jawaban ini diabaikan. Meskipun tidak selengkap jawaban yang diterima, itu memberikan beberapa informasi tambahan yang sangat berguna dan pantas mendapat lebih banyak suara
Avrohom Yisroel
10

Dalam javascript, ini disebut Immediately-Invoked Function Expression (IIFE) .

Untuk menjadikannya ekspresi fungsi, Anda harus:

  1. lampirkan menggunakan ()

  2. letakkan operator batal sebelum itu

  3. tetapkan ke variabel.

Kalau tidak, itu akan diperlakukan sebagai definisi fungsi dan kemudian Anda tidak akan dapat memanggil / memanggilnya secara bersamaan dengan cara berikut:

 function (arg1) { console.log(arg1) }(); 

Di atas akan memberi Anda kesalahan. Karena Anda hanya dapat menjalankan ekspresi fungsi dengan segera.

Ini dapat dicapai dengan beberapa cara: Cara 1:

(function(arg1, arg2){
//some code
})(var1, var2);

Cara 2:

(function(arg1, arg2){
//some code
}(var1, var2));

Cara 3:

void function(arg1, arg2){
//some code
}(var1, var2);

cara 4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

Semua di atas akan segera memanggil ekspresi fungsi.

asmmahmud
sumber
3

Saya hanya punya sedikit komentar. Kode Anda akan berfungsi dengan perubahan kecil:

var x = function(){
    alert(2 + 2);
}();

Saya menggunakan sintaks di atas dan bukan versi yang lebih luas:

var module = (function(){
    alert(2 + 2);
})();

karena saya tidak berhasil mendapatkan indentasi agar berfungsi dengan benar untuk file javascript di vim. Tampaknya vim tidak suka kurung kurawal di dalam kurung buka.

Andrei Bozantan
sumber
Jadi mengapa sintaks ini berfungsi ketika Anda menetapkan hasil yang dieksekusi ke variabel, tetapi tidak mandiri?
paislee
1
@paislee - Karena mesin JavaScript mengartikan pernyataan JavaScript apa pun yang valid yang dimulai dengan functionkata kunci sebagai deklarasi fungsi dalam hal ini trailing ()ditafsirkan sebagai operator pengelompokan yang, menurut aturan sintaksis JavaScript, hanya dapat dan harus berisi ekspresi JavaScript.
natlee75
1
@bosonix - Sintaks pilihan Anda berfungsi dengan baik, tetapi ide yang baik untuk menggunakan "versi penyebaran yang lebih luas" yang Anda referensikan atau varian tempat ()terlampir dalam operator pengelompokan (yang sangat direkomendasikan oleh Douglas Crockford) untuk konsistensi: itu umum untuk menggunakan IIFE tanpa menugaskan mereka ke variabel, dan mudah untuk lupa menyertakan kurung kurung itu jika Anda tidak menggunakannya secara konsisten.
natlee75
0

Mungkin jawaban yang lebih pendek adalah itu

function() { alert( 2 + 2 ); }

adalah fungsi literal yang mendefinisikan fungsi (anonim). Pasangan () tambahan, yang ditafsirkan sebagai ekspresi, tidak diharapkan pada tingkat atas, hanya literal.

(function() { alert( 2 + 2 ); })();

ada dalam pernyataan ekspresi yang memanggil fungsi anonim.

theking2
sumber
0
(function(){
     alert(2 + 2);
 })();

Di atas adalah sintaks yang valid karena apa pun yang disampaikan di dalam tanda kurung dianggap sebagai ekspresi fungsi.

function(){
    alert(2 + 2);
}();

Di atas bukan sintaks yang valid. Karena java script syntax parser mencari nama fungsi setelah kata kunci fungsi karena tidak menemukan apa-apa itu membuat kesalahan.

Vithy
sumber
2
Meskipun jawaban Anda tidak salah, jawaban yang diterima sudah mencakup semua ini di dept.
Hingga Arnold
0

Anda juga dapat menggunakannya seperti:

! function() { console.log('yeah') }()

atau

!! function() { console.log('yeah') }()

!- negasi op mengonversi definisi fn ke ekspresi fn, oleh karena itu, Anda dapat langsung memohonnya (). Sama seperti menggunakan 0,fn defatauvoid fn def

Csaba K.
sumber
-1

Mereka dapat digunakan dengan parameter-argumen seperti

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

akan menghasilkan 7

Jude
sumber
-1

Tanda kurung ekstra tersebut menciptakan fungsi anonim tambahan antara namespace global dan fungsi anonim yang berisi kode. Dan dalam fungsi-fungsi Javascript yang dinyatakan di dalam fungsi-fungsi lain hanya dapat mengakses namespace dari fungsi induk yang berisi mereka. Karena ada objek tambahan (fungsi anonim) antara ruang lingkup global dan pelingkupan kode aktual tidak dipertahankan.

Jarkko Hietala
sumber