Tüm Javascript dosyalarını “(function () {…}) ()” gibi anonim işlevlere sarmanın amacı nedir?


584

Son zamanlarda Javascript bir sürü okudum ve tüm dosya ithal .js dosyalarında aşağıdaki gibi sarılmış olduğunu fark edilmiştir.

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

Basit bir yapıcı işlevi kümesi yerine bunu yapmanın nedeni nedir?


6
Bunun birçok insan tarafından kullanılacağını hayal ettiğim için lütfen kapanışını unutmayın;
dgh

5
Bu tekniğe "IIFE" deniyor. Hemen çağrılan işlev İfade Bu standları en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Yanıtlar:


786

Genellikle ad alanı (daha sonra bakın) ve üye işlevlerinin ve / veya değişkenlerin görünürlüğünü kontrol eder. Bunu bir nesne tanımı gibi düşünün. Bunun teknik adı Hemen Çağırılmış İşlev İfadesidir Çağırılmış İfadesidir (IIFE). jQuery eklentileri genellikle bu şekilde yazılır.

Javascript'te işlevleri iç içe yerleştirebilirsiniz. Dolayısıyla, aşağıdakiler yasaldır:

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

Şimdi arayabilirsiniz outerFunction(), ancak görünürlüğü innerFunction()kapsamı ile sınırlıdır outerFunction(), yani özeldir outerFunction(). Temel olarak Javascript'teki değişkenlerle aynı prensibi izler:

var globalVariable;

function someFunction() {
   var localVariable;
}

Buna:

function globalFunction() {

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

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

Yukarıdaki senaryoda, globalFunction()herhangi bir yerden arama yapabilirsiniz , ancak localFunction1veya öğesini arayamazsınız localFunction2.

Yazarken yaptığınız şey (function() { ... })(), ilk parantez kümesindeki kodu bir işlev değişmezi yapmaktır (yani tüm "nesne" aslında bir işlevdir). Bundan sonra, ()az önce tanımladığınız işlevi (son ) kendiniz çağırıyorsunuz . Daha önce de belirttiğim gibi, bunun en büyük avantajı, özel yöntemlere / işlevlere ve özelliklere sahip olabilmenizdir:

(function() {
   var private_var;

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

İlk örnekte, globalFunctionçalıştırmak için açıkça ada göre çağırırsınız . Yani, sadece globalFunction()onu çalıştırmak için yapardın . Ancak yukarıdaki örnekte, sadece bir işlevi tanımlamakla kalmıyorsunuz; Eğer tanımlarken ve tek seferde onu çağırarak. Bu, JavaScript dosyanız yüklendiğinde hemen yürütüldüğü anlamına gelir. Elbette şunları yapabilirsiniz:

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

Önemli bir fark dışında davranış büyük ölçüde aynı olacaktır: IIFE kullandığınızda küresel kapsamı kirletmekten kaçınıyorsunuz (sonuç olarak, bir adı olmadığı için işlevi birden çok kez çağıramayacağınız anlamına gelir, ancak bu işlev yalnızca gerçekten bir sorun olmadığında yürütülmelidir).

IIFE'ler ile temiz olan şey, içerideki şeyleri de tanımlayabilmeniz ve sadece istediğiniz dünyayı dış dünyaya gösterebilmenizdir (temel olarak kendi kitaplığınızı / eklentinizi oluşturabilmeniz için bir ad alanı örneği):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

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

Şimdi arayabilirsiniz myPlugin.public_function1(), ancak erişemezsiniz private_function()! Sınıf tanımına oldukça benzer. Bunu daha iyi anlamak için, daha fazla okuma için aşağıdaki bağlantıları tavsiye ederim:

DÜZENLE

Bahsetmeyi unuttum. Bu finalde (), istediğiniz her şeyi aktarabilirsiniz. Örneğin, jQuery eklentileri oluşturduğunuzda, bunu iletir jQueryveya $beğenirsiniz:

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

Burada yaptığınız şey, bir parametreyi alan bir işlev tanımlamaktır ( jQyerel bir değişken olarak adlandırılır ve yalnızca bu işlev tarafından bilinir ). Daha sonra işlevi kendiniz çağırırsınız ve bir parametre geçirirsiniz (aynı zamanda denir jQuery, ancak bu dış dünyadan ve gerçek jQuery'nin kendisine bir referanstır). Bunu yapmak için acil bir ihtiyaç yoktur, ancak bazı avantajları vardır:

  • Genel bir parametreyi yeniden tanımlayabilir ve bu parametreye yerel kapsamda anlamlı bir ad verebilirsiniz.
  • Kapsam zincirini küresel kapsama doğru yürümek zorunda kalmak yerine, yerel kapsamdaki şeyleri aramak daha hızlı olduğu için hafif bir performans avantajı vardır.
  • Sıkıştırma için faydalar vardır (küçültme).

Daha önce bu işlevlerin başlangıçta otomatik olarak nasıl çalıştığını açıkladım, ancak otomatik olarak çalışırlarsa argümanları kim iletiyor? Bu teknik, ihtiyacınız olan tüm parametrelerin zaten global değişkenler olarak tanımlandığını varsayar. Dolayısıyla jQuery zaten global bir değişken olarak tanımlanmamışsa bu örnek çalışmaz. Tahmin edebileceğiniz gibi, jquery.js'nin başlatılması sırasında yaptığı şeylerden biri, 'jQuery' global değişkeni ve jQuery dahil edildikten sonra bu kodun çalışmasına izin veren daha ünlü '$' global değişkeni tanımlamaktır.


14
Çok güzel, ad alanını iyi anlıyorum, ancak son örneğinizin çoğunu gördüm ve insanların neyi başarmaya çalıştıklarını anlayamadım. Bu gerçekten işleri temizler.
Andrew Kou

34
Müthiş gönderi. Çok teşekkürler.
Darren

4
Bence önde gelen ve sondaki noktalı virgül ';' örneği tamamlardı - ;(function(jQ) { ... code ... })(jQuery);Bu şekilde, biri komut dosyasında noktalı virgül bıraktıysa, özellikle kodunuzu küçültmeyi ve başkalarıyla birleştirmeyi planlıyorsanız, sizinkini kırmaz.
Taras Alenin

3
güzel yazı, özel değişkenlere vurgu yapmayı seviyorum. Ben de modül-desen / kapanışları (public_function1 & public_function2) ve biraz nasıl kapsam dışına çıkmak güzel bir giriş olsa bile, değişkenleri geçmek gibi açılış gibi. Ben de bir cevap ekledi, bu bir sözdizimi kökleri ve fonksiyon ifadesi vs işlev ifadesi arasındaki farklar ve ne düşünüyorum "sadece bir kongre" vs "bu sonuca ulaşmak için tek yol" olduğunu odaklanan.
Adrien Be

4
Harika bir yazı, değişkenlerin kendi kendini yürütme işlevine nasıl geçebileceğinin daha yararlı olabileceğini düşünüyorum. Kendi kendini yürütme işlevindeki bağlam temiz - veri yok. Bunu yaparak bağlamı aktarabilirsiniz, bu (function (context) { ..... })(this)daha sonra ana içeriğe istediğiniz bir şeyi eklemenizi ve böylece açığa çıkarmanızı sağlar.
Callum Linington

79

Kısacası

özet

En basit haliyle, bu teknik kodu bir fonksiyon kapsamı içine sarmayı amaçlamaktadır .

Şansını azaltmaya yardımcı olur:

  • diğer uygulamalarla / kütüphanelerle çakışma
  • üstün (küresel olasılıkla) kapsamı kirleten

O değil belge hazır olduğunda algılamak - bu çeşit değildir document.onload, ne dewindow.onload

Genellikle bir Immediately Invoked Function Expression (IIFE)veya olarak bilinir Self Executing Anonymous Function.

Kod Açıklaması

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

Yukarıdaki örnekte, işlevde tanımlanan herhangi bir değişken (yani kullanılarak bildirildi var) "özel" olacak ve SADECE (Vivin Paliath'in belirttiği gibi) işlev kapsamı içinde erişilebilir olacaktır. Başka bir deyişle, bu değişkenler fonksiyonun dışında görünmez / ulaşılamaz. Canlı demoya bakın .

Javascript fonksiyon kapsam belirleme özelliğine sahiptir. Msgstr "Bir fonksiyonda tanımlanan parametreler ve değişkenler fonksiyonun dışında görünmez ve fonksiyon içinde herhangi bir yerde tanımlanan bir değişken fonksiyonun her yerinde görülebilir." ("Javascript: İyi Parçalar" dan).


Daha fazla detay

Alternatif Kod

Sonunda, daha önce yayınlanan kod aşağıdaki gibi de yapılabilir:

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

Canlı demoya bakın .


Kökleri

Yineleme 1

Bir gün biri muhtemelen "myMainFunction" isminden kaçınmanın bir yolu olmalı, çünkü istediğimiz tek şey onu hemen uygulamaktır. "

Temel bilgilere geri dönerseniz, şunu öğrenirsiniz:

  • expression: bir değeri değerlendiren bir şey. yani3+11/x
  • statement: Satır bir şey yapıyor kodunun (s) AMA yok değil bir değere değerlendirir. yaniif(){}

Benzer şekilde, fonksiyon ifadeleri bir değer olarak değerlendirilir. Ve bir sonuç (sanırım?) Derhal çağrılabilirler:

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

Böylece daha karmaşık örneğimiz şöyle olur:

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

Canlı demoya bakın .

Yineleme 2

Bir sonraki adım "neden var myMainFunction =kullanmasak bile!"

Cevap basit: aşağıdaki gibi bunu kaldırmayı deneyin:

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

Canlı demoya bakın .

Çünkü işe yaramaz "fonksiyonu bildirimleri Invokable değiliz" .

Hüner kaldırarak olmasıdır var myMainFunction =biz dönüştürülmüş işlev ifadesi bir içine işlev bildiriminde . Bununla ilgili daha fazla ayrıntı için "Kaynaklar" daki bağlantılara bakın.

Sonraki soru şudur: "Bunu neden başka bir şeyle bir işlev ifadesi olarak tutamıyorum var myMainFunction =?

Cevap "yapabilirsin" ve aslında bunu yapmanın pek çok yolu var: a +, a !, a -veya belki de bir çift parantez (şimdi konvansiyon tarafından yapıldığı gibi) eklenmesi ve daha fazlasına inanıyorum. Örnek olarak:

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

veya

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

veya

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

Bu nedenle, bir zamanlar "Alternatif Kodumuza" ilgili değişiklik eklendikten sonra, "Kod Açıklaması" örneğinde kullanılanla aynı koda dönüyoruz.

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

Hakkında daha fazla bilgi Expressions vs Statements:


Kapsamları Belirleme

Merak edebileceğiniz bir şey, "fonksiyonun içinde 'düzgün' değişkeni TANIMLAMADIĞINIZDA ne olur - yani bunun yerine basit bir atama yapın?"

(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

Canlı demoya bakın .

Temel olarak, geçerli kapsamı içinde bildirilmeyen bir değişkene bir değer atanırsa, "değişkeni bulana veya global kapsama (hangi noktada oluşturacağı) ulaşıncaya kadar kapsam zincirine bakın".

Bir tarayıcı ortamında (düğümler gibi bir sunucu ortamına karşı) global kapsam windownesne tarafından tanımlanır . Böylece yapabiliriz window.myOtherFunction().

Bu konuyla ilgili "İyi uygulamalar" ipucum, her varzaman bir şey tanımlarken kullanmaktır : ister sayı, ister nesne veya işlev, ister global kapsamda olsa bile. Bu, kodu çok daha basit hale getirir.

Not:

  • javascript yok değil var block scope(: eklenen blok kapsamı yerel değişkenler Güncelleme ES6 .)
  • javascript'te yalnızca function scope& vardır global scope(( windowtarayıcı ortamında kapsam)

Hakkında daha fazla bilgi Javascript Scopes:


kaynaklar


Sonraki adımlar

Bu IIFEkavramı elde ettikten sonra module pattern, genellikle bu IIFE modelini kullanarak yapılır. İyi eğlenceler :)


Çok yararlı. Çok teşekkürler!
Christoffer Helgelin Hald

Güzel, demo sürümünü tercih ederim :)
Fabrizio Bertoglio

Böyle harika bir açıklama. Teşekkür ederim!
Vikram Khemlani

26

Bir tarayıcıdaki Javascript'in gerçekten birkaç etkili kapsamı vardır: işlev kapsamı ve küresel kapsam.

Bir değişken işlev kapsamında değilse, global kapsamdadır. Ve global değişkenler genellikle kötüdür, bu yüzden bu kütüphane değişkenlerini kendine saklayan bir yapıdır.


1
Ancak yapıcı işlevinin kendisi kendi değişkenleri için kapsam sağlamıyor mu?
Andrew Kou

1
Evet, bu kütüphanede tanımlanan her fonksiyon kendi yerel değişkenlerini tanımlayabilir, ancak bu, değişkenlerin kütüphane dışına sızmadan fonksiyonlar arasında paylaşılmasına izin verir
Gareth

@Gareth, böylece bu bir kapsam içinde "global" değişkenlere izin verir (;
Francisco Presencia

2
@FranciscoPresencia "bir kapsam dahilinde küresel" yararlı bir ifade değildir, çünkü bu temelde "kapsam" ın anlamı budur. "Küresel" kapsamın bütün mesele, diğer tüm kapsamların erişebildiği kapsamdır.
Gareth

19

Buna kapanış denir. Temelde, fonksiyonun içindeki kodu mühürler, böylece diğer kütüphaneler buna müdahale etmez. Derlenmiş dillerde ad alanı oluşturmaya benzer.

Misal. Diyelim ki yazıyorum:

(function() {

    var x = 2;

    // do stuff with x

})();

Şimdi diğer kütüphaneler kütüphanemde xkullanmak için oluşturduğum değişkene erişemiyor .


7
Terminolojinize dikkat edin. Ad aralığı, değişkenlere ad alanına hitap ederek (genellikle bir önek kullanarak) dışarıdan erişilebildiğini gösterir. Bu Javascript'te mümkün olsa da, burada gösterilen şey bu değildir
Gareth

Bunun tam olarak değil bir ad gibi, ancak, size duyurmak istediğiniz özelliklere sahip bir nesne dönen benzer işlevleri sağlayabilir katılıyorum: (function(){ ... return { publicProp1: 'blah' }; })();. Açıkçası isimlendirmeye mükemmel bir şekilde paralel değil, ama bu şekilde düşünmeye yardımcı olabilir.
Joel

Örneğinizde x hala özel bir değişkendir ... Bir IIFE içine sarmanıza rağmen. devam edin ve x fonksiyonun dışında erişmeye çalışın, yapamazsınız ..
RayLoveless

Puanınız geçerli değil. Aşağıdaki işlevde bile diğer kütüphaneler x'e erişemez. function () {var x = 2}
RayLoveless

@ RayLoveless katılıyorum. Bu iddiayla çelişmiyorum. Aslında, bu cevabın son cümlesiyle aynı iddiada bulundum.
Joel

8

Bazı html5 nesneleri için tarayıcı desteğini belirleme yönteminde olduğu gibi, işlev kapanışlarını da daha büyük ifadelerde veri olarak kullanabilirsiniz .

   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;
     })()
    }

Ne yapar !! yapmak?
1.21 gigawatts

!! bir değeri boole (doğru / yanlış) gösterimine dönüştürür.
Liam

7

Değişkenleri yerel tutmanın yanı sıra, çok kullanışlı bir kullanım, global bir değişken kullanarak bir kitaplık yazarken, kitaplıkta kullanmak için daha kısa bir değişken adı verebilirsiniz. JQuery jQuery.noConflict () kullanarak jQuery'ye işaret eden $ değişkenini devre dışı bırakmanıza izin verdiğinden, genellikle jQuery eklentileri yazarken kullanılır. Devre dışı bırakılmışsa, kodunuz yine de $ kullanabilir ve yalnızca şunları yaparsanız kesilemez:

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

3
  1. Aynı penceredeki diğer yöntemlerle / kütüphanelerle çakışmayı önlemek için,
  2. Global kapsamdan kaçının, yerel kapsam yapın,
  3. Hata ayıklamayı daha hızlı hale getirmek için (yerel kapsam),
  4. JavaScript yalnızca işlev kapsamına sahiptir, bu nedenle kodların derlenmesine de yardımcı olacaktır.

1

Ayrıca, kodun "katı modda" yürütülmesi için kapsam işlevinde 'katı kullan' kullanmalıyız. Aşağıda gösterilen örnek kod

(function() {
    'use strict';

    //Your code from here
})();

Neden katı kullanmalıyız?
nbro


Soruya gerçekten cevap vermiyor!
Pritam Banerjee

Pritam, iyi bir uygulama kullanımı. Herhangi bir cevaba oy vermeden önce lütfen uygun araştırmayı yapın
Neha Jain

1
'katı kullan' kötü programcıları kendilerinden kurtarır. Ve programcıların çoğu kötü programcılar olduğundan, kesinlikle yapmamaları gereken şeyleri yapmalarını ve hızla batan bir kod karmaşasına neden olmalarını önlemeye yardımcı olur.
Mart'ta MattE
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.