Bir JavaScript işlevini yinelemeli olarak çağırma


90

Bunun gibi bir değişkende özyinelemeli bir işlev oluşturabilirim:

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

Bununla functionHolder(3);çıktı olur 3 2 1 0. Diyelim ki aşağıdakileri yaptım:

var copyFunction = functionHolder;

copyFunction(3);3 2 1 0yukarıdaki gibi çıktı . Daha sonra functionHolderaşağıdaki gibi değiştirirsem :

functionHolder = function(whatever) {
    output("Stop counting!");

Sonra beklendiği gibi functionHolder(3);verirdi Stop counting!.

copyFunction(3);şimdi (kendisinin işaret ettiği) işlevi değil, 3 Stop counting!ifade ettiği şekilde verir functionHolder. Bu, bazı durumlarda arzu edilebilir, ancak işlevi, onu tutan değişkenden ziyade kendisini çağıracak şekilde yazmanın bir yolu var mı?

Yani, tüm bu adımlardan geçmek , aradığımızda hala verirse , sadece hattı değiştirmek mümkün müdür ? Denedim ama bu bana hata veriyor .functionHolder(counter-1);3 2 1 0copyFunction(3);this(counter-1);this is not a function


1
NB Bir işlevin içinde bu, işlevin kendisini değil, işlevin yürütülme bağlamını ifade eder. Sizin durumunuzda bu muhtemelen genel pencere nesnesine işaret ediyordu.
antoine

Yanıtlar:


146

Adlandırılmış İşlev İfadelerini Kullanma:

Bir işlev ifadesine aslında özel olan ve yalnızca işlevin içinden görülebilen bir ad verebilirsiniz :

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

İşte myselfolan fonksiyonun görünen tek iç kendisi.

İşlevi özyinelemeli olarak çağırmak için bu özel adı kullanabilirsiniz.

Bkz 13. Function DefinitionECMAScript 5 spec:

Bir FunctionExpression'daki Tanımlayıcı, işlevin kendini yinelemeli olarak çağırmasına izin vermek için FunctionExpression'ın FunctionBody'sinden referans alınabilir. Ancak, bir FunctionDeclaration'dan farklı olarak, bir FunctionExpression'daki Tanımlayıcıya başvurulamaz ve FunctionExpression kapsamındaki kapsamı etkilemez.

Lütfen 8. sürümüne kadar olan Internet Explorer'ın, ad aslında çevreleyen değişken ortamında göründüğünden ve gerçek işlevin bir kopyasına atıfta bulunduğundan doğru davranmadığını unutmayın (aşağıdaki patrick dw'nin açıklamasına bakın).

Arguments.callee'yi kullanma:

Alternatif arguments.calleeolarak, mevcut işleve başvurmak için kullanabilirsiniz :

var factorial = function (n) {
    if (n <= 1) {
        return 1;
    }
    return n * arguments.callee(n-1);
}

ECMAScript'in 5. baskısı arguments.callee () öğesinin katı modda kullanılmasını yasaklar , ancak:

( MDN'den ): Normal kodda arguments.callee, çevreleyen işlevi ifade eder. Bu kullanım durumu zayıftır: sadece çevreleyen işlevi adlandırın! Ayrıca arguments.callee, satır içi işlevler gibi optimizasyonları büyük ölçüde engeller, çünkü arguments.callee'ye erişilirse satır içi olmayan işleve bir başvuru sağlanması mümkün kılınmalıdır. Katı mod işlevleri için arguments.callee, ayarlandığında veya alındığında atan, silinemez bir özelliktir.


4
+1 IE8'de biraz hatalı ve daha düşük olmasına rağmen myself, çevreleyen değişken ortamında gerçekte görülebilir ve gerçek işlevin bir kopyasına atıfta bulunur myself. Dış referansı nullyine de ayarlayabilmelisiniz .
user113716

Cevap için teşekkürler! Her ikisi de yardımcı oldu ve sorunu 2 farklı şekilde çözdü. Sonunda hangisini kabul edeceğime rastgele karar verdim: P
Samthere

sadece benim anlamam için. Her dönüşte fonksiyonu çarpmanın arkasındaki sebep nedir? return n * myself(n-1);?
chitzui

işlev neden böyle çalışır? jsfiddle.net/jvL5euho/18 4 kez döngü yaptıktan sonra.
Prashant Tapase

Bazı başvuru argümanlarına göre. Callee katı modda çalışmayacaktır.
Krunal Limbad

10

arguments.callee [MDN] kullanarak işleve erişebilirsiniz :

if (counter>0) {
    arguments.callee(counter-1);
}

Ancak bu, katı modda kırılacaktır.


6
Bunun kullanımdan kaldırıldığına inanıyorum (ve katı modda izin verilmiyor)
Arnaud Le Blanc

@Felix: Evet, "katı mod" bir verecek TypeError, ancak resmi olarak arguments.callee (veya herhangi bir katı mod ihlalinin) "katı mod" dışında kullanımdan kaldırıldığını belirten bir şey bulamadım .
user113716

Cevap için teşekkürler! Her ikisi de yardımcı oldu ve sorunu 2 farklı şekilde çözdü. Sonunda hangisini kabul edeceğime rastgele karar verdim: P
Samthere

6

Y-birleştiriciyi kullanabilirsiniz: ( Wikipedia )

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

Ve bunu şu şekilde kullanabilirsiniz:

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});

5

Bunun eski bir soru olduğunu biliyorum, ancak adlandırılmış işlev ifadelerini kullanmaktan kaçınmak isterseniz kullanılabilecek bir çözüm daha sunabileceğimi düşündüm. (Onlardan kaçınmanız veya kaçınmanız gerektiğini söylememek, sadece başka bir çözüm sunmak)

  var fn = (function() {
    var innerFn = function(counter) {
      console.log(counter);

      if(counter > 0) {
        innerFn(counter-1);
      }
    };

    return innerFn;
  })();

  console.log("running fn");
  fn(3);

  var copyFn = fn;

  console.log("running copyFn");
  copyFn(3);

  fn = function() { console.log("done"); };

  console.log("fn after reassignment");
  fn(3);

  console.log("copyFn after reassignment of fn");
  copyFn(3);

3

İşte çok basit bir örnek:

var counter = 0;

function getSlug(tokens) {
    var slug = '';

    if (!!tokens.length) {
        slug = tokens.shift();
        slug = slug.toLowerCase();
        slug += getSlug(tokens);

        counter += 1;
        console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
    }

    return slug;
}

var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
console.log('THE SLUG IS: %s', mySlug);

Değerin counterne olduğuna göre "geriye doğru" sayıldığına dikkat edin slug. Bunun nedeni, işlev günlüğe kaydetmeden önce yinelediği için bu değerleri günlüğe kaydettiğimiz konumdur - bu nedenle, günlüğe kaydetme gerçekleşmeden önce çağrı yığınına daha derin ve daha derin yuva yapmaya devam ediyoruz .

Özyineleme, son çağrı yığını öğesini karşıladığında, işlev çağrılarının " dışını " tramplenler , halbuki, 'nin ilk artışı counter, son iç içe geçmiş çağrının içinde gerçekleşir.

Bunun Soran'ın kodunda bir "düzeltme" olmadığını biliyorum, ancak başlığa bakıldığında, özyinelemeyi daha iyi anlamak için genel olarak Özyinelemeyi örnekleyeceğimi düşündüm .

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.