JavaScript'te başka bir işlev içinde bir işlev tanımlayın


83
function foo(a) {
    if (/* Some condition */) {
        // perform task 1
        // perform task 3
    }
    else {
        // perform task 2
        // perform task 3
    }
}

Yapısı yukarıdakine benzer bir fonksiyona sahibim. Görev 3'ü bir fonksiyona soyutlamak istiyorum bar(), ancak bu fonksiyonun erişimini sadece kapsamı dahilinde sınırlamak istiyorum foo(a).

İstediğimi elde etmek için aşağıdakilere geçmek doğru mu?

function foo(a) {
    function bar() {
        // Perform task 3
    }

    if (/* Some condition */) {
        // Perform task 1
        bar();
    }
    else {
        // Perform task 2
        bar();
    }
}

Yukarıdakiler doğruysa, bar()her foo(a)çağrıldığında yeniden tanımlanıyor mu? (Burada CPU kaynağının israfı konusunda endişeleniyorum.)


1
Kendinize değip değmediğini test edin: jsperf.com Bunun task3'e bağlı olduğunu hayal ediyorum.
tomByrer

1
@tomByer - aracı önermek için +1
tamakisquare

Yanıtlar:


123

Evet, sahip olduğunuz şey doğrudur. Bazı notlar:

  • barher işlev çağrısında oluşturulur foo, ancak:
    • Modern tarayıcılarda bu çok hızlı bir süreçtir. (Bazı motorlar, kodu onun için yalnızca bir kez derleyebilir ve ardından bu kodu her seferinde farklı bir bağlamla yeniden kullanabilir; Google'ın V8 motoru [Chrome'da ve başka yerlerde] bunu çoğu durumda yapar.)
    • Ve ne yaptığına bağlı olarak bar, bazı motorlar, işlev çağrısını tamamen ortadan kaldırarak onu "satır içi" yapabileceklerini belirleyebilir. V8 bunu yapıyor ve eminim bunu yapan tek motor o değil. Doğal olarak bunu ancak kodun davranışını değiştirmezse yapabilirler.
  • barHer seferinde yaratmanın performans etkisi, varsa, JavaScript motorları arasında büyük farklılıklar gösterecektir. Eğer barönemsiz, bu belirlenemeyen gelen oldukça küçük değişecektir. fooArka arkaya binlerce kez aramıyorsan (örneğin, bir mousemoveişleyiciden), bunun için endişelenmem. Öyle olsanız bile, sadece daha yavaş motorlarda bir sorun görürsem endişelenirim. İşte DOM işlemlerini içeren bir test örneği , bir etki olduğunu, ancak önemsiz bir etkinin olduğunu (muhtemelen DOM şeyleriyle yıkanmış) öneriyor. İşte saf hesaplama yapan bir test durumu var hangi gösterileri çok daha yüksek darbe, ama açıkçası bile, biz bir fark söz ediyoruz mikro şey üzerinde bile% 92 artış alır çünkü saniye mikroSaniyeler hala çok çok hızlı. Gerçek dünyadaki bir etki görene kadar / görmedikçe, endişelenecek bir şey değil.
  • baryalnızca işlevin içinden erişilebilir olacak ve işleve yapılan bu çağrı için tüm değişkenlere ve argümanlara erişime sahip olacaktır. Bu, bunu çok kullanışlı bir model yapar.
  • Bir işlev bildirimi kullandığınız için , bildirimi nereye koyduğunuz önemli değildir (üst, alt veya orta - işlevin en üst düzeyinde olduğu sürece, akış kontrol ifadesinin içinde değil) bir sözdizimi hatası), adım adım kodun ilk satırı çalıştırılmadan önce tanımlanır.

Cevabınız için teşekkürler. Yani bunun önemsiz bir performans artışı olduğunu mu söylüyorsunuz? ( barher çağrıda bir kopyasının oluşturulduğu göz önüne alındığında foo)
tamakisquare

2
@ahmoo: JavaScript performansıyla yanıt neredeyse her zaman: Duruma göre değişir. :-) Hangi motorun çalıştıracağına ve ne sıklıkla arayacağınıza bağlıdır foo. fooArka arkaya binlerce kez aramıyorsan (örneğin, bir mousemoveişleyicide değil ), o zaman bu konuda hiç endişelenmem. Ve bazı motorların (örneğin, V8) kodu yine de satır içi yapacağını ve bunu yapmanın dışarıdan algılanabilecek bir şekilde olup bitenleri değiştirmemesi koşuluyla işlev çağrısını tamamen ortadan kaldıracağını unutmayın.
TJ Crowder

@TJCrowder: Robrich'in cevabına yorum yapabilir misiniz? bu çözüm bar()her aramada yeniden yaratılmasını engelliyor mu? Ayrıca, foo.prototype.barişlevi tanımlamak için kullanmak herhangi bir yardımcı olur mu?
rkw

4
@rkw: Robrich'in cevabının yaptığı gibi, işlevi bir kez oluşturmak, her aramada işlev oluşturma maliyetinden kaçınmanın yararlı bir yoludur. barÇağrı için değişkenlere ve argümanlara erişim sahibi olma gerçeğini kaybedersiniz foo(üzerinde işlemesini istediğiniz herhangi bir şey varsa, onu iletmeniz gerekir), bu da işleri biraz karmaşıklaştırabilir, ancak performans açısından kritik bir durumda gerçek bir sorun gördüyseniz, sorunu çözüp çözmediğini görmek için bu şekilde yeniden düzenleme yapabilirsiniz. Hayır, kullanmak foo.prototypegerçekten yardımcı olmaz (bir kere barartık özel olmayacak).
TJ Crowder

@ahmoo: Bir test durumu eklendi. İlginç bir şekilde, Guffa'nın test durumundan farklı sonuçlar alıyorum, bence işlevi biraz fazla basit. Ama yine de performansın bir sorun olması gerektiğini düşünmüyorum.
TJ Crowder

15

Kapanışlar bunun içindir.

var foo = (function () {
  function bar() {
    // perform task 3
  };

  function innerfoo (a) { 
    if (/* some cond */ ) {
      // perform task 1
      bar();
    }
    else {
      // perform task 2
      bar();
    }
  }
  return innerfoo;
})();

Innerfoo (bir kapanış), çubuğa bir başvuru tutar ve yalnızca bir kapatmayı oluşturmak için çağrılan anonim bir işlevden yalnızca innerfoo'ya bir başvuru döndürülür.

Bar'a dışarıdan bu şekilde ulaşılamaz.


1
İlginç. Javascript'e sınırlı maruz kaldım, bu yüzden kapanış benim için yeni bir şey. Bununla birlikte, kapanışı incelemek için benim için bir başlangıç ​​noktası belirlediniz. Teşekkürler.
tamakisquare

Kapanışı değişken / işlev kapsamlarıyla başa çıkmak için ne sıklıkla kullanıyorsunuz? Örneğin, aynı 3 değişkene erişmeniz gereken 2 işleviniz varsa, 3 değişkeni 2 işlevle birlikte bir kapanışta açıklar ve sonra 2 işlevi döndürür müydünüz?
doubleOrt

8
var foo = (function () {
    var bar = function () {
        // perform task 3
    }
    return function (a) {

        if (/*some condition*/) {
            // perform task 1
            bar();
        }
        else {
            // perform task 2
            bar();
        }
    };
}());

Kapanış, bar()içerilenin kapsamını korur, yeni işlevi kendi kendine çalışan anonim işlevden döndürmek, daha görünür kapsamı ayarlar foo(). Anonim kendi kendini çalıştıran işlev tam olarak bir kez çalıştırılır, bu nedenle yalnızca bir bar()örnek vardır ve her çalıştırıldığında foo()onu kullanır.


İlginç. O halde yakından bakmalıyım. Teşekkürler.
tamakisquare

Kapanışı değişken / işlev kapsamlarıyla başa çıkmak için ne sıklıkla kullanıyorsunuz? Örneğin, aynı 3 değişkene erişmeniz gereken 2 işleviniz varsa, 3 değişkeni 2 işlevle birlikte bir kapanışta açıklar ve sonra 2 işlevi döndürür müydünüz?
doubleOrt

Kapakları ne sıklıkla kullanırım? Her zaman. 2 fonksiyonun ihtiyaç duyduğu 3 değişkene sahip olsaydım: 1. (en iyi) 3 değişkeni her iki fonksiyona da aktarın - fonksiyonlar bir kez ve yalnızca bir kez tanımlanabilir. 2. (iyi) değişkenlerin her ikisinin de kapsamı dışında olduğu 2 işlevi oluşturun. Bu bir kapanış. (Temelde cevap burada.) Ne yazık ki, işlevler değişkenlerin her yeni kullanımı için yeniden tanımlanıyor. 3. (kötü) işlevleri kullanmayın, her iki şeyi de yapan büyük ve uzun bir yönteme sahip olun.
robrich

5

Evet, bu iyi çalışıyor.

Dış işleve her girdiğinizde iç işlev yeniden oluşturulmaz, ancak yeniden atanır.

Bu kodu test ederseniz:

function test() {

    function demo() { alert('1'); }

    demo();
    demo = function() { alert('2'); };
    demo();

}

test();
test();

o gösterecek 1, 2, 1, 2, değil 1, 2, 2, 2.


Cevabınız için teşekkürler. demo()Her seferinde yeniden atama test()bir performans sorunu mu olmalı? Karmaşıklığına bağlı demo()mı?
tamakisquare

1
Bir performans testi yaptım: jsperf.com/inner-function-vs-global-function Sonuç, genellikle bunun bir performans sorunu olmadığıdır (çünkü işlevlere koyduğunuz herhangi bir kodun çalıştırılması işlevi oluşturmaktan çok daha uzun sürecektir. kendisi), ancak bu ekstra performans avantajına ihtiyacınız varsa, farklı tarayıcılar için farklı kod yazmanız gerekir.
Guffa

Testi oluşturmak için zaman harcadığınız ve performans konusundaki puanlarınızı paylaştığınız için teşekkürler. Çok minnettarım.
tamakisquare

Büyük bir güvenle "iç işlev her seferinde yeniden yaratılmaz" dediniz. Spesifikasyona göre ; bir motorun optimize edip etmediği motora bağlıdır. ( Pek çoğunun isteyeceğini umuyorum .) Sizin ve benim test durumunuzun çok değişken sonuçlara sahip olduğunu görmek beni ilgilendiriyor : jsperf.com/cost-of-creating-inner-function Yine de, performansın bir sorun olduğunu düşünmüyorum.
TJ Crowder

@TJCrowder: Evet, bu bir uygulama ayrıntısı, ancak modern Javascript motorları kodu derlerken, her atandığında işlevi yeniden derlemeyecektir. Performans testlerinin sonucunun farklı olmasının nedeni, farklı şeyleri test etmeleridir. Testim global fonksiyonları yerel fonksiyonlarla karşılaştırırken, testiniz yerel bir fonksiyonu satır içi kodla karşılaştırır. Kodu satır içi yapmak elbette işlevi çağırmaktan daha hızlı olacaktır, bu yaygın bir optimizasyon tekniğidir.
Guffa

0

Yuvalanmış ve iç içe olmayan ve işlev ifadelerini işlev bildirimlerine karşı test etmek için bir jsperf oluşturdum ve yuvalanmış test durumlarının iç içe olmayanlara göre 20 kat daha hızlı performans gösterdiğini görünce şaşırdım. (Ya zıt ya da ihmal edilebilir farkları tahmin ettim).

https://jsperf.com/nested-functions-vs-not-nested-2/1

Bu, Chrome 76, macOS'ta.

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.