Apa tujuan membungkus seluruh file Javascript dalam fungsi anonim seperti "(function () {...}) ()"?

584

Saya telah membaca banyak Javascript belakangan ini dan saya telah memperhatikan bahwa seluruh file dibungkus seperti berikut dalam file .js yang akan diimpor.

(function() {
    ... 
    code
    ...
})();

Apa alasan untuk melakukan ini daripada satu set fungsi konstruktor yang sederhana?

Andrew Kou
sumber
6
Karena saya membayangkan ini akan digunakan oleh banyak orang, jangan lupa tutupnya;
dgh
5
Teknik ini disebut "IIFE", saya pikir. Ini adalah singkatan dari Ekspresi Fungsi yang Segera Diminta en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Jawaban:

786

Biasanya untuk namespace (lihat nanti) dan mengontrol visibilitas fungsi anggota dan / atau variabel. Anggap saja seperti definisi objek. Nama teknis untuk itu adalah Ekspresi Fungsi Segera Dibawa (IIFE). Plugin jQuery biasanya ditulis seperti ini.

Di Javascript, Anda bisa membuat fungsi sarang. Jadi, yang berikut ini legal:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

Sekarang Anda dapat menelepon outerFunction(), tetapi visiblity innerFunction()terbatas pada ruang lingkup outerFunction(), yang berarti pribadi outerFunction(). Ini pada dasarnya mengikuti prinsip yang sama dengan variabel dalam Javascript:

var globalVariable;

function someFunction() {
   var localVariable;
}

Sejalan dengan itu:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

Dalam skenario di atas, Anda dapat menelepon globalFunction()dari mana saja, tetapi Anda tidak dapat menelepon localFunction1atau localFunction2.

Apa yang Anda lakukan saat menulis (function() { ... })(), adalah membuat kode di dalam set kurung pertama sebagai fungsi literal (artinya seluruh "objek" sebenarnya adalah sebuah fungsi). Setelah itu, Anda menjalankan sendiri fungsi (final ()) yang baru saja Anda tetapkan. Jadi keuntungan utama dari ini seperti yang saya sebutkan sebelumnya, adalah Anda dapat memiliki metode / fungsi dan properti pribadi:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

Pada contoh pertama, Anda akan secara eksplisit memanggil globalFunctionnama untuk menjalankannya. Artinya, Anda hanya perlu melakukannya globalFunction()untuk menjalankannya. Tetapi dalam contoh di atas, Anda tidak hanya mendefinisikan suatu fungsi; Anda mendefinisikan dan menjalankannya dalam sekali jalan. Ini berarti bahwa ketika file JavaScript Anda dimuat, segera dieksekusi. Tentu saja, Anda bisa melakukan:

function globalFunction() {
    // code
}
globalFunction();

Perilaku ini sebagian besar akan sama kecuali untuk satu perbedaan signifikan: Anda menghindari mencemari ruang lingkup global ketika Anda menggunakan IIFE (sebagai akibatnya itu juga berarti bahwa Anda tidak dapat memanggil fungsi beberapa kali karena tidak memiliki nama, tetapi karena fungsi ini hanya dimaksudkan untuk dieksekusi setelah itu benar-benar bukan masalah).

Hal yang rapi dengan IIFE adalah bahwa Anda juga dapat mendefinisikan hal-hal di dalam dan hanya mengekspos bagian yang Anda inginkan ke dunia luar begitu (contoh namespacing sehingga Anda pada dasarnya dapat membuat perpustakaan / plugin Anda sendiri):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

Sekarang Anda dapat menelepon myPlugin.public_function1(), tetapi Anda tidak dapat mengakses private_function()! Sangat mirip dengan definisi kelas. Untuk memahami ini dengan lebih baik, saya merekomendasikan tautan berikut untuk bacaan lebih lanjut:

EDIT

Saya lupa menyebutkan. Di final itu (), Anda bisa melewati apa pun yang Anda inginkan di dalam. Misalnya, ketika Anda membuat plugin jQuery, Anda meneruskan jQueryatau $suka:

(function(jQ) { ... code ... })(jQuery) 

Jadi yang Anda lakukan di sini adalah mendefinisikan fungsi yang mengambil satu parameter (disebut jQ, variabel lokal, dan hanya diketahui oleh fungsi itu). Kemudian Anda menjalankan fungsi sendiri dan mengirimkan parameter (juga disebut jQuery, tetapi yang ini dari dunia luar dan referensi ke jQuery aktual itu sendiri). Tidak perlu mendesak untuk melakukan ini, tetapi ada beberapa keuntungan:

  • Anda dapat mendefinisikan ulang parameter global dan memberikannya nama yang masuk akal dalam lingkup lokal.
  • Ada sedikit keuntungan kinerja karena lebih cepat untuk mencari hal-hal di lingkup lokal daripada harus berjalan naik rantai lingkup ke lingkup global.
  • Ada manfaat untuk kompresi (minifikasi).

Sebelumnya saya menjelaskan bagaimana fungsi-fungsi ini berjalan secara otomatis pada saat startup, tetapi jika mereka berjalan secara otomatis, siapa yang memberikan argumen? Teknik ini mengasumsikan bahwa semua parameter yang Anda butuhkan sudah didefinisikan sebagai variabel global. Jadi jika jQuery belum didefinisikan sebagai variabel global, contoh ini tidak akan berfungsi. Seperti yang Anda duga, satu hal yang dilakukan jquery.js saat inisialisasi adalah mendefinisikan variabel global 'jQuery', serta variabel global '$' yang lebih terkenal, yang memungkinkan kode ini berfungsi setelah jQuery dimasukkan.

Vivin Paliath
sumber
14
Sangat keren, saya mengerti namespace dengan baik, tetapi saya telah melihat banyak contoh terakhir dari Anda dan tidak tahu apa yang orang coba capai. Ini benar-benar jelas.
Andrew Kou
34
Pos yang luar biasa. Terima kasih banyak.
Darren
4
Saya pikir menambahkan tanda koma yang memimpin dan mengekor ';' akan membuat contoh lengkap - ;(function(jQ) { ... code ... })(jQuery);Dengan cara ini jika seseorang meninggalkan tanda titik koma di skripnya, itu tidak akan menghancurkan Anda, terutama jika Anda berencana untuk memperkecil dan menyatukan skrip Anda dengan yang lain.
Taras Alenin
3
posting bagus, saya suka penekanan pada variabel pribadi. Saya juga suka pembukaan pada modul-pola / penutupan (public_function1 & public_function2) & bagaimana Anda lulus variabel, meskipun sedikit keluar dari ruang lingkup itu adalah pengantar yang bagus. Saya juga menambahkan jawaban, yang ini fokus pada apa yang saya kira adalah akar dari sintaks & perbedaan antara pernyataan fungsi vs ekspresi fungsi & apa yang saya pikir "hanya konvensi" vs "satu-satunya cara untuk mencapai hasil ini".
Adrien Be
4
Posting yang bagus, saya pikir mungkin lebih pada bagaimana meneruskan variabel ke fungsi self executing bermanfaat. Konteks dalam fungsi yang dijalankan sendiri adalah bersih - tidak ada data. Anda dapat meneruskan dalam konteks dengan melakukan ini (function (context) { ..... })(this)yang kemudian memungkinkan Anda untuk melampirkan apa pun yang Anda suka ke konteks induk sehingga memaparkannya.
Callum Linington
79

Pendeknya

Ringkasan

Dalam bentuknya yang paling sederhana, teknik ini bertujuan untuk membungkus kode di dalam lingkup fungsi .

Ini membantu mengurangi kemungkinan:

  • berbenturan dengan aplikasi / perpustakaan lain
  • ruang lingkup unggul (kemungkinan terbesar) yang berpolusi

Itu tidak mendeteksi ketika dokumen sudah siap - itu bukan semacam document.onloadatau tidakwindow.onload

Umumnya dikenal sebagai Immediately Invoked Function Expression (IIFE)atau Self Executing Anonymous Function.

Kode Dijelaskan

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Dalam contoh di atas, variabel apa pun yang didefinisikan dalam fungsi (yaitu dideklarasikan menggunakan var) akan menjadi "pribadi" dan dapat diakses dalam lingkup fungsi HANYA (seperti yang dikatakan Vivin Paliath). Dengan kata lain, variabel-variabel ini tidak terlihat / terjangkau di luar fungsi. Lihat demo langsung .

Javascript memiliki fungsi pelingkupan. "Parameter dan variabel yang didefinisikan dalam suatu fungsi tidak terlihat di luar fungsi, dan bahwa variabel yang didefinisikan di mana saja dalam suatu fungsi terlihat di mana-mana dalam fungsi." (dari "Javascript: The Good Parts").


Keterangan lebih lanjut

Kode Alternatif

Pada akhirnya, kode yang diposting sebelumnya juga dapat dilakukan sebagai berikut:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Lihat demo langsung .


Akar

Iterasi 1

Suatu hari, seseorang mungkin berpikir "pasti ada cara untuk menghindari penamaan 'myMainFunction', karena semua yang kita inginkan adalah menjalankannya dengan segera."

Jika Anda kembali ke dasar-dasarnya, Anda mengetahui bahwa:

  • expression: sesuatu yang mengevaluasi suatu nilai. yaitu3+11/x
  • statement: baris kode melakukan sesuatu TETAPI itu tidak mengevaluasi nilai. yaituif(){}

Demikian pula, ekspresi fungsi mengevaluasi suatu nilai. Dan satu konsekuensi (saya kira?) Adalah mereka dapat langsung dipanggil:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

Jadi contoh kita yang lebih kompleks menjadi:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Lihat demo langsung .

Iterasi 2

Langkah selanjutnya adalah pikiran "mengapa var myMainFunction =kita harus menggunakan itu saja !?"

Jawabannya sederhana: coba hapus ini, seperti di bawah ini:

 function(){ console.log('mamamia!'); }();

Lihat demo langsung .

Ini tidak akan berfungsi karena "deklarasi fungsi tidak dapat dimasuki" .

Kuncinya adalah dengan menghapus var myMainFunction =kita mengubah ekspresi fungsi menjadi pernyataan fungsi . Lihat tautan di "Sumberdaya" untuk detail lebih lanjut tentang ini.

Pertanyaan selanjutnya adalah "mengapa saya tidak bisa menyimpannya sebagai ekspresi fungsi dengan sesuatu selain var myMainFunction =?

Jawabannya adalah "Anda bisa", dan sebenarnya ada banyak cara Anda bisa melakukan ini: menambahkan a +, a !, a -, atau mungkin membungkus sepasang tanda kurung (seperti yang sekarang dilakukan oleh konvensi), dan lebih banyak lagi saya percaya. Sebagai contoh:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

atau

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

atau

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

Jadi, setelah modifikasi yang relevan ditambahkan ke apa yang dulunya "Kode Alternatif" kami, kami kembali ke kode yang sama persis seperti yang digunakan dalam contoh "Kode Dijelaskan"

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

Baca lebih lanjut tentang Expressions vs Statements:


Lingkup Demistifikasi

Satu hal yang mungkin bertanya-tanya adalah "apa yang terjadi ketika Anda TIDAK mendefinisikan variabel 'benar' di dalam fungsi - yaitu melakukan tugas sederhana sebagai gantinya?"

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

Lihat demo langsung .

Pada dasarnya, jika suatu variabel yang tidak dideklarasikan dalam lingkupnya saat ini diberi nilai, maka "lihat rantai lingkup terjadi sampai ia menemukan variabel atau mengenai lingkup global (pada titik mana ia akan membuatnya)".

Ketika di lingkungan browser (vs lingkungan server seperti nodejs) lingkup global ditentukan oleh windowobjek. Karenanya kita bisa melakukannya window.myOtherFunction().

Kiat "Praktik yang baik" pada topik ini adalah untuk selalu digunakan varsaat menentukan apa pun : apakah itu angka, objek, atau fungsi, & bahkan saat dalam lingkup global. Ini membuat kode lebih sederhana.

catatan:

  • javascript tidak memiliki block scope(Pembaruan: ruang lingkup variabel lokal ditambahkan dalam ES6 .)
  • javascript hanya memiliki function scope& global scope( windowcakupan di lingkungan browser)

Baca lebih lanjut tentang Javascript Scopes:


Sumber daya


Langkah selanjutnya

Setelah Anda mendapatkan IIFEkonsep ini , itu mengarah ke module pattern, yang biasanya dilakukan dengan memanfaatkan pola IIFE ini. Selamat bersenang-senang :)

Adrien Be
sumber
Sangat membantu. Terima kasih banyak!
Christoffer Helgelin Hald
Bagus, saya lebih suka versi demo :)
Fabrizio Bertoglio
Penjelasan yang sangat bagus. Terima kasih!
Vikram Khemlani
26

Javascript di browser hanya benar-benar memiliki beberapa cakupan efektif: lingkup fungsi dan ruang lingkup global.

Jika variabel tidak dalam lingkup fungsi, itu dalam lingkup global. Dan variabel global umumnya buruk, jadi ini adalah konstruksi untuk menjaga variabel perpustakaan untuk dirinya sendiri.

Gareth
sumber
1
Tetapi bukankah fungsi konstruktor itu sendiri menyediakan ruang untuk variabelnya sendiri?
Andrew Kou
1
Ya, setiap fungsi yang didefinisikan dalam pustaka ini dapat menentukan variabel lokalnya sendiri, tetapi ini memungkinkan variabel untuk dibagikan di antara fungsi-fungsi tanpa mereka bocor di luar perpustakaan
Gareth
@ Gareth, jadi ini memungkinkan untuk variabel "global" dalam lingkup (;
Francisco Presencia
2
@FranciscoPresencia "global dalam lingkup" bukan ungkapan yang berguna, karena pada dasarnya itulah arti "ruang lingkup". Inti dari ruang lingkup "global" adalah bahwa secara khusus ruang lingkup yang semua aksesnya miliki ke ruang lingkup.
Gareth
19

Itu disebut penutupan. Ini pada dasarnya menyegel kode di dalam fungsi sehingga perpustakaan lain tidak mengganggu itu. Ini mirip dengan membuat namespace dalam bahasa yang dikompilasi.

Contoh. Misalkan saya menulis:

(function() {

    var x = 2;

    // do stuff with x

})();

Sekarang perpustakaan lain tidak dapat mengakses variabel yang xsaya buat untuk digunakan di perpustakaan saya.

Joel
sumber
7
Hati-hati dengan terminologi Anda. Namespacing menyiratkan bahwa variabel dapat diakses dari luar dengan mengatasi namespace (biasanya dengan menggunakan awalan). Meskipun hal ini dimungkinkan dalam Javascript, bukan itu yang ditunjukkan di sini
Gareth
Saya setuju itu tidak persis seperti namespace, namun Anda dapat memberikan fungsi serupa dengan mengembalikan sebuah objek dengan sifat yang ingin Anda mempublikasikan: (function(){ ... return { publicProp1: 'blah' }; })();. Jelas tidak sejajar sempurna dengan namespace, tetapi mungkin membantu untuk memikirkannya seperti itu.
Joel
dalam contoh Anda x masih merupakan variabel pribadi ... Meskipun Anda membungkusnya dalam IIFE. silakan dan coba akses x di luar fungsi, Anda tidak bisa ..
RayLoveless
Poin Anda tidak valid. Bahkan dalam fungsi berikut perpustakaan lain tidak dapat mengakses x. function () {var x = 2}
RayLoveless
@ RayLoveless Saya setuju. Saya tidak menentang pernyataan itu. Sebenarnya, saya membuat pernyataan yang sama dengan kalimat terakhir dari jawaban ini.
Joel
8

Anda dapat menggunakan penutupan fungsi sebagai data dalam ekspresi yang lebih besar juga, seperti dalam metode ini menentukan dukungan browser untuk beberapa objek html5.

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }
kennebec
sumber
Apa yang dilakukan !! melakukan?
1,21 gigawatt
!! mengkonversi nilai menjadi representasi boolean (benar / salah).
Liam
7

Selain menjaga variabel lokal, satu penggunaan yang sangat berguna adalah ketika menulis perpustakaan menggunakan variabel global, Anda bisa memberinya nama variabel yang lebih pendek untuk digunakan dalam perpustakaan. Ini sering digunakan dalam menulis plugin jQuery, karena jQuery memungkinkan Anda untuk menonaktifkan $ variabel yang menunjuk ke jQuery, menggunakan jQuery.noConflict (). Jika dinonaktifkan, kode Anda masih dapat menggunakan $ dan tidak rusak jika Anda hanya melakukannya:

(function($) { ...code...})(jQuery);
Coronus
sumber
3
  1. Untuk menghindari bentrok dengan metode / pustaka lain di jendela yang sama,
  2. Hindari ruang lingkup Global, jadikan ruang lingkup lokal,
  3. Untuk mempercepat proses debug (lingkup lokal),
  4. JavaScript hanya memiliki lingkup fungsi, sehingga akan membantu dalam kompilasi kode juga.
Vivek Mehta
sumber
1

Kita juga harus menggunakan 'gunakan ketat' dalam fungsi lingkup untuk memastikan bahwa kode harus dieksekusi dalam "mode ketat". Kode contoh ditunjukkan di bawah ini

(function() {
    'use strict';

    //Your code from here
})();
Neha Jain
sumber
Kenapa kita harus menggunakan yang ketat?
nbro
Lihat artikel ini: stackoverflow.com/questions/1335851/…
Neha Jain
Tidak benar-benar menjawab pertanyaan itu!
Pritam Banerjee
Pritam, ini praktik penggunaan yang baik. Silakan lakukan penelitian yang tepat sebelum memberikan jawaban apa pun
Neha Jain
1
'use strict' menyimpan programmer yang buruk dari diri mereka sendiri. Dan karena mayoritas programmer adalah programmer yang buruk, ini membantu mencegah mereka melakukan hal-hal yang seharusnya tidak boleh mereka lakukan dan berakhir dengan kekacauan kode yang cepat.
MattE