Kapsüllenmiş anonim işlev sözdizimini açıklar


372

özet

JavaScript'te kapsüllenmiş anonim işlevlerin sözdiziminin arkasındaki nedeni açıklayabilir misiniz? Neden bu işi yapar: (function(){})();ama bu değil: function(){}();?


Bildiklerim

JavaScript'te, şu şekilde adlandırılmış bir işlev oluşturulur:

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

Ayrıca anonim bir işlev oluşturabilir ve bir değişkene atayabilirsiniz:

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

Anonim bir işlev oluşturarak, ardından bunu parantez içine alarak ve hemen yürüterek bir kod bloğunu kapsülleyebilirsiniz:

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

Bu, modülerleştirilmiş komut dosyaları oluştururken, Greasemonkey komut dosyaları, jQuery eklentileri vb.

Şimdi, bunun neden işe yaradığını anlıyorum. Köşeli ayraçlar içeriği içine alır ve yalnızca sonucu ortaya çıkarır (bunu tanımlamanın daha iyi bir yolu olduğuna eminim) (2 + 2) === 4.


Ne anlamadım

Ama bunun neden aynı şekilde çalışmadığını anlamıyorum:

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

Bunu bana açıklayabilir misin?


39
Tüm bu çeşitli gösterimi ve işlevleri tanımlamak / ayarlamak / çağırmak yollarını başlangıçta javascript ile çalışmanın en kafa karıştırıcı kısmı olduğunu düşünüyorum. İnsanlar da onlar hakkında konuşma eğilimindedir. Rehberlerde veya bloglarda vurgulanmış bir nokta değil. Aklımı uçuruyor çünkü çoğu insan için kafa karıştırıcı bir şey ve js'de akıcı olan insanlar da bunun içinden geçmiş olmalı. Hiç konuşulmayan bu boş tabu gerçeği gibi.
ahnbizcad

1
Ayrıca bu yapının amacı hakkında bilgi edinin veya ( teknik ) açıklamayı kontrol edin (ayrıca burada ). Parantezin yerleştirilmesi için konumları hakkında bu soruya bakın .
Bergi

OT: Bu anonim işlevlerin çok fazla nerede kullanıldığını bilmek isteyenler için lütfen yeterli şekilde
okuyungood.com/JavaScript-Module-Pattern-In-Depth.html

Bu, Hemen Çağrılmış İşlev İfadeleri (IIFE) için tipik bir durumdur.
Aminu Kano

Yanıtlar:


410

Çalışmıyor çünkü a olarak ayrıştırılıyor FunctionDeclarationve işlev bildirimlerinin ad tanımlayıcısı zorunludur .

Parantez içine aldığınızda, a olarak değerlendirilir FunctionExpressionve işlev ifadeleri adlandırılıp adlandırılamaz.

Bir FunctionDeclarationgörünüşün grameri şöyle:

function Identifier ( FormalParameterListopt ) { FunctionBody }

Ve FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

Görebileceğiniz gibi Identifier(Tanımlayıcı seçeneği ) belirteci FunctionExpressionisteğe bağlıdır, bu nedenle tanımlanmış bir ad olmadan işlev ifadesine sahip olabiliriz:

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

Veya adlandırılmış işlev ifadesi:

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

Parantezler (resmi olarak Gruplama İşleci olarak adlandırılır ) yalnızca ifadeleri çevreleyebilir ve bir işlev ifadesi değerlendirilir.

İki dilbilgisi prodüksiyonu belirsiz olabilir ve örneğin tamamen aynı görünebilir:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

Ayrıştırıcı , göründüğü bağlama göre a FunctionDeclarationveya a olup olmadığını bilir .FunctionExpression

Yukarıdaki örnekte, ikincisi bir ifadedir, çünkü Virgül operatörü de yalnızca ifadeleri işleyebilir.

Öte yandan, FunctionDeclarations sadece " Program" kod adı verilen kodda, yani global kapsamın dışında ve FunctionBodydiğer fonksiyonların içinde kod anlamına gelebilir .

Blokların içindeki işlevlerden kaçınılmalıdır, çünkü öngörülemeyen bir davranışa yol açabilirler, örneğin:

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

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

Yukarıdaki kod aslında bir a üretmelidir SyntaxError, çünkü a Blockyalnızca ifadeler içerebilir (ve ECMAScript Spesifikasyonu herhangi bir işlev ifadesi tanımlamaz), ancak çoğu uygulama toleranslıdır ve uyarı veren ikinci işlevi alır 'false!'.

Mozilla uygulamaları -Rhino, SpiderMonkey - farklı bir davranışa sahiptir. Gramerleri standart olmayan bir İşlev İfadesi içerir, yani işlevin s ile olduğu gibi ayrıştırma zamanında değil, çalışma zamanında değerlendirileceği anlamına gelir FunctionDeclaration. Bu uygulamalarda ilk işlevi tanımlayacağız.


İşlevler farklı şekillerde bildirilebilir , aşağıdakileri karşılaştırın :

1- Değişkene atanan İşlev yapıcısı ile tanımlanan bir işlev çarpılır :

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

2- multiply adlı bir fonksiyonun fonksiyon beyanı :

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

3- Değişkene atanan fonksiyon ifadesi çarpılır :

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

4- adlandırılmış bir fonksiyonu ifade FUNC_NAME değişken tahsis çarpma :

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

2
CMS'nin cevabı doğrudur. İşlev bildirimlerinin ve ifadelerinin mükemmel bir açıklaması için kangax'ın bu makalesine bakın .
Tim Down

Bu harika bir cevap. Kaynak metnin nasıl ayrıştırıldığı- ve BNF'nin yapısı ile yakından bağlantılı gibi görünmektedir. Örnek 3'te, bir eşittir işaretini izlediğinden, bunun bir işlev ifadesi olduğunu söylemeliyim; Bunun amacının ne olacağını merak ediyorum - sadece adlandırılmış bir işlev bildirimi olarak yorumlanıyor, ama isimsiz mi? Bir değişkene atamazsanız, adlandırmazsanız veya çağırmazsanız bu hangi amaca hizmet eder?
Breton

1
Aha. Çok kullanışlı. Teşekkürler, CMS. Bağlantı
kurduğunuz

1
+1, kapanış köşeli ayraç fonksiyon ifadesinde yanlış konumda olmasına rağmen :-)
NickFitz

1
@GovindRai, No. İşlev bildirimleri derleme zamanında işlenir ve yinelenen işlev bildirimi önceki bildirimi geçersiz kılar. Çalışma zamanında işlev bildirimi zaten kullanılabilir ve bu durumda kullanılabilir olan "false" uyarısıdır. Daha fazla bilgi için bilmiyorum
Saurabh Misra

50

Bu eski bir soru ve cevap olsa da, bugüne kadar birçok geliştiriciyi bir döngü için attığı bir konuyu tartışıyor. Görüşme yaptığım ve bir işlev bildirimi ile bir işlev ifadesi arasındaki farkı söyleyemeyen ve hemen çağrılan bir işlev ifadesinin ne olduğuna dair hiçbir fikri olmayan JavaScript geliştirici adaylarının sayısını sayamıyorum.

Bununla birlikte, Premasagar'ın kod snippet'ine bir isim tanımlayıcı vermiş olsa bile çalışmayacağını belirtmek isterim.

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

Bunun işe yaramamasının nedeni, JavaScript motorunun bunu bir işlev bildirimi olarak yorumlaması ve ardından hiçbir ifade içermeyen tamamen ilgisiz bir gruplama operatörü olarak yorumlaması ve gruplama işleçlerinin bir ifade içermesi gerektiğidir . JavaScript'e göre, yukarıdaki kod snippet'i aşağıdakine eşdeğerdir.

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

();

Bazı insanlar için bazı yararlı olabilir işaret etmek istiyorum başka bir şey, bir işlev ifadesi için sağladığınız herhangi bir ad tanımlayıcısı işlev tanımının kendisi dışında kod bağlamında hemen hemen işe yaramaz olmasıdır.

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

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

Tabii ki, kod hata ayıklama söz konusu olduğunda işlev tanımlayıcılarınızla ad tanımlayıcıları kullanmak her zaman yararlıdır, ancak bu tamamen başka bir şeydir ... :-)


17

Harika cevaplar zaten gönderiliyor. Ancak işlev bildirimlerinin boş bir tamamlama kaydı döndürdüğünü belirtmek isterim:

14.1.20 - Çalışma Zamanı Semantiği: Değerlendirme

İşlev function Tanımlama : BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. Dönüş Normal Tamamlama (boş).

Bu gerçeği gözlemlemek kolay değildir, çünkü döndürülen değeri almaya çalışmanın çoğu yolu işlev bildirimini bir işlev ifadesine dönüştürür. Ancak şunu evalgösterir:

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

Boş bir tamamlama kaydı çağırmak mantıklı değildir. Bu yüzden function f(){}()çalışamıyorum. Aslında JS motoru bunu çağırmaya bile çalışmaz, parantezler başka bir ifadenin parçası olarak kabul edilir.

Ancak işlevi parantez içine alırsanız, işlev ifadesi olur:

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

İşlev ifadeleri bir işlev nesnesi döndürür. Ve bu yüzden onu arayabilirsiniz: (function f(){})().


Yazık bu cevap göz ardı edilir. Kabul edilen cevap kadar kapsamlı olmasa da, bazı çok faydalı ekstra bilgiler sağlar ve daha fazla oyu hak eder
Avrohom Yisroel

10

Javascript'te buna Hemen Çağrılmış İşlev İfadesi (IIFE) denir .

Bir işlev ifadesi yapmak için yapmanız gerekenler:

  1. () kullanarak içine alın

  2. önüne bir boş operatör koymak

  3. bir değişkene atayabilir.

Aksi takdirde, işlev tanımı olarak ele alınır ve daha sonra aşağıdaki şekilde arayamaz / çağıramazsınız:

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

Yukarıdakiler size hata verecektir. Çünkü sadece bir işlev ifadesini hemen çağırabilirsiniz.

Bu birkaç yolla elde edilebilir: Yol 1:

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

Yol 2:

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

Yol 3:

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

yol 4:

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

Yukarıdakilerin hepsi hemen işlev ifadesini çağırır.


3

Başka bir küçük sözüm daha var. Kodunuz küçük bir değişiklikle çalışır:

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

Ben daha yaygın sürüm yerine yukarıdaki sözdizimini kullanın:

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

çünkü girintiyi vim'deki javascript dosyaları için düzgün çalışmayı başaramadım. Görünüşe göre vim, açık parantez içindeki kıvırcık parantezleri sevmiyor.


Peki, bu sözdizimi, çalıştırılan sonucu bir değişkene atadığınızda neden bağımsız değil de çalışır?
paislee

1
@paislee - JavaScript motoru, functionanahtar kelimeyle başlayan geçerli herhangi bir JavaScript ifadesini işlev bildirimi() olarak yorumladığı için, bu durumda, izleyen , JavaScript sözdizimi kurallarına göre yalnızca bir JavaScript ifadesi içerebilen ve içermesi gereken bir gruplama operatörü olarak yorumlanır .
natlee75

1
@bosonix - Tercih ettiğiniz sözdizimi iyi çalışır, ancak başvuruda bulunduğunuz "daha geniş yayılmış sürüm" veya ()gruplama operatörünün (Douglas Crockford'un şiddetle tavsiye ettiği sürüm) içinde bulunduğu varyantı kullanmak iyi bir fikirdir : IIFE'leri bir değişkene atamadan kullanmak için yaygındır ve tutarlı bir şekilde kullanmıyorsanız bu sarma parantezlerini dahil etmeyi unutmak kolaydır.
natlee75

0

Belki daha kısa cevap şu olurdu:

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

a, fonksiyon hazır tanımlayan bir (anonim) işlevini. Bir ifade olarak yorumlanan ek () bir çift, üst düzeyden beklenmez, sadece değişmez değerlerdir.

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

Bir içindedir ifade deyimi çağırır isimsiz işlev.


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

Yukarıda sözdizimi geçerlidir, çünkü parantez içinde iletilen her şey işlev ifadesi olarak kabul edilir.

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

Yukarıdaki geçerli bir sözdizimi değil. Java komut dosyası sözdizimi ayrıştırıcısı işlev anahtar sözcüğünden sonra işlev adını aradığı için bir hata bulamadığı için hata atar.


2
Cevabınız yanlış olmasa da, kabul edilen cevap zaten bütün bunları borçlu olarak kapsamaktadır.
Arnold

0

Ayrıca şu şekilde kullanabilirsiniz:

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

veya

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

!- negation op, fn tanımını fn ifadesine dönüştürür, bu nedenle hemen ile çağırabilirsiniz (). 0,fn defVeya ile aynıvoid fn def


-1

Bunlar gibi parametreler ile kullanılabilir

var x = 3; 
var y = 4;

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

7 olarak sonuçlanır


-1

Bu ekstra parantez, genel ad alanı ile kodu içeren anonim işlev arasında ek anonim işlevler oluşturur. Ve Javascript işlevlerinde diğer işlevler içinde bildirilen işlevler yalnızca bunları içeren üst işlevin ad alanına erişebilir. Global kapsam ve gerçek kod kapsamı arasında fazladan bir nesne (anonim işlev) bulunduğundan, muhafaza edilmez.

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.