เรียกใช้ฟังก์ชันจาวาสคริปต์ซ้ำ


91

ฉันสามารถสร้างฟังก์ชันวนซ้ำในตัวแปรดังนี้:

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

ด้วยวิธีนี้จะส่งออกfunctionHolder(3); 3 2 1 0สมมติว่าฉันทำสิ่งต่อไปนี้:

var copyFunction = functionHolder;

copyFunction(3);จะแสดงผลลัพธ์3 2 1 0ตามด้านบน ถ้าฉันเปลี่ยนแล้วfunctionHolderดังนี้:

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

จากนั้นfunctionHolder(3);จะให้Stop counting!ตามที่คาดไว้

copyFunction(3);ตอนนี้ให้3 Stop counting!ตามที่อ้างถึงfunctionHolderไม่ใช่ฟังก์ชัน (ที่ตัวเองชี้ไป) สิ่งนี้อาจเป็นที่พึงปรารถนาในบางสถานการณ์ แต่มีวิธีเขียนฟังก์ชันเพื่อให้เรียกตัวเองมากกว่าตัวแปรที่เก็บไว้หรือไม่?

นั่นคือเป็นไปได้ไหมที่จะเปลี่ยนเฉพาะสายfunctionHolder(counter-1);เพื่อให้ทำตามขั้นตอนทั้งหมดนี้ยังคงให้3 2 1 0เมื่อเราโทรcopyFunction(3);? ฉันพยายามแต่ที่ทำให้ผมเกิดข้อผิดพลาดthis(counter-1);this is not a function


1
NB ภายในฟังก์ชันหมายถึงบริบทของการดำเนินการของฟังก์ชันไม่ใช่ฟังก์ชันนั้นเอง ในกรณีของคุณนี่อาจชี้ไปที่วัตถุหน้าต่างส่วนกลาง
antoine

คำตอบ:


146

การใช้นิพจน์ฟังก์ชันที่มีชื่อ:

คุณสามารถตั้งชื่อนิพจน์ฟังก์ชันที่เป็นส่วนตัวจริงและสามารถมองเห็นได้จากภายในฟังก์ชัน ifself เท่านั้น:

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

นี่myselfคือมองเห็นได้ภายในเพียงการทำงานของตัวเอง

คุณสามารถใช้ชื่อส่วนตัวนี้เพื่อเรียกใช้ฟังก์ชันซ้ำได้

ดู13. Function Definitionข้อกำหนด ECMAScript 5:

ตัวระบุใน FunctionExpression สามารถอ้างอิงได้จากภายใน FunctionBody ของ FunctionExpression เพื่อให้ฟังก์ชันเรียกตัวเองแบบวนซ้ำ อย่างไรก็ตามไม่เหมือนใน FunctionDeclaration ตัวระบุใน FunctionExpression ไม่สามารถอ้างอิงได้และไม่มีผลต่อขอบเขตที่ล้อมรอบ FunctionExpression

โปรดทราบว่า Internet Explorer ถึงเวอร์ชัน 8 ทำงานไม่ถูกต้องเนื่องจากชื่อสามารถมองเห็นได้จริงในสภาพแวดล้อมตัวแปรที่แนบมาและอ้างอิงถึงฟังก์ชันที่ซ้ำกัน (ดูความคิดเห็นของpatrick dwด้านล่าง)

ใช้ arguments.callee:

หรือคุณสามารถใช้arguments.calleeเพื่ออ้างถึงฟังก์ชันปัจจุบัน:

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

ECMAScript ฉบับที่ 5 ห้ามไม่ให้ใช้อาร์กิวเมนต์ callee () ในโหมดเข้มงวดอย่างไรก็ตาม:

(จากMDN ): ในอาร์กิวเมนต์รหัสปกติ callee หมายถึงฟังก์ชันปิดล้อม กรณีการใช้งานนี้อ่อนแอเพียงแค่ตั้งชื่อฟังก์ชันปิดล้อม! ยิ่งไปกว่านั้น arguments.callee ยังขัดขวางการปรับให้เหมาะสมอย่างมากเช่นฟังก์ชัน inlining เนื่องจากต้องทำให้เป็นไปได้ที่จะให้การอ้างอิงถึงฟังก์ชันที่ไม่อยู่ในบรรทัดหากมีการเข้าถึง arguments.callee arguments.callee สำหรับฟังก์ชันโหมดเข้มงวดเป็นคุณสมบัติที่ไม่สามารถลบได้ซึ่งจะพ่นเมื่อตั้งค่าหรือเรียกค้น


4
+1 แม้ว่ามันจะเป็นรถเล็ก ๆ น้อย ๆ ใน IE8 และลดในการที่myselfเป็นจริงมองเห็นได้ในตัวแปรสภาพแวดล้อมล้อมรอบและมันอ้างอิงที่ซ้ำกันของจริงmyselfฟังก์ชั่น คุณควรตั้งค่าการอ้างอิงภายนอกnullได้
user113716

ขอบคุณสำหรับคำตอบ! ทั้งสองอย่างมีประโยชน์และแก้ปัญหาได้ 2 วิธี ในที่สุดฉันก็สุ่มตัดสินใจว่าจะรับแบบ
ไหน

เพียงเพื่อให้ฉันเข้าใจ อะไรคือเหตุผลเบื้องหลังการคูณฟังก์ชันในผลตอบแทนแต่ละครั้ง return n * myself(n-1);เหรอ?
chitzui

ทำไมฟังก์ชันถึงทำงานเช่นนี้ jsfiddle.net/jvL5euho/18หลังจากถ้าวนซ้ำ 4 ครั้ง
Prashant Tapase

ตามข้อโต้แย้งอ้างอิงบางประการ Callee จะไม่ทำงานในโหมดเข้มงวด
Krunal Limbad

10

คุณสามารถเข้าถึงฟังก์ชันได้โดยใช้arguments.callee [MDN] :

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

อย่างไรก็ตามสิ่งนี้จะแตกในโหมดเข้มงวด


6
ฉันเชื่อว่าสิ่งนี้เลิกใช้แล้ว (และไม่อนุญาตให้ใช้ในโหมดเข้มงวด)
Arnaud Le Blanc

@Felix: ใช่ "โหมดเข้มงวด" จะให้TypeErrorแต่ฉันไม่พบสิ่งใดที่ระบุอย่างเป็นทางการว่าarguments.callee (หรือการละเมิดโหมดที่เข้มงวดใด ๆ )เลิกใช้งานนอก "โหมดเข้มงวด"
user113716

ขอบคุณสำหรับคำตอบ! ทั้งสองอย่างมีประโยชน์และแก้ปัญหาได้ 2 วิธี ในที่สุดฉันก็สุ่มตัดสินใจว่าจะรับแบบ
ไหน

6

คุณสามารถใช้ Y-combinator: ( 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)));

และคุณสามารถใช้มันได้ดังนี้:

// 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

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันคิดว่าฉันจะนำเสนออีกหนึ่งวิธีแก้ปัญหาที่สามารถใช้ได้หากคุณต้องการหลีกเลี่ยงการใช้นิพจน์ฟังก์ชันที่มีชื่อ (ไม่ได้บอกว่าคุณควรหรือไม่ควรหลีกเลี่ยงเพียงแค่นำเสนอวิธีแก้ปัญหาอื่น)

  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

นี่คือตัวอย่างง่ายๆอย่างหนึ่ง:

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

สังเกตว่าการcounterนับ "ถอยหลัง" ในส่วนที่เกี่ยวกับslugมูลค่าคืออะไร นี่เป็นเพราะตำแหน่งที่เราบันทึกค่าเหล่านี้เนื่องจากฟังก์ชันเกิดขึ้นซ้ำก่อนที่จะบันทึกดังนั้นเราจึงทำการซ้อนกันให้ลึกและลึกลงไปในcall-stack ก่อนที่จะทำการบันทึก

เมื่อการเรียกซ้ำตรงตามรายการเรียกซ้อนสุดท้ายมันจะแทรมโพลีน "ออก" ของการเรียกฟังก์ชันในขณะที่การเพิ่มครั้งแรกcounterจะเกิดขึ้นภายในการเรียกซ้อนสุดท้าย

ฉันรู้ว่านี่ไม่ใช่การ "แก้ไข" ในรหัสของผู้ถาม แต่ได้รับชื่อที่ฉันคิดว่าฉันคิดว่าฉันเป็นตัวอย่างของการเรียกซ้ำโดยทั่วไปเพื่อความเข้าใจที่ดีขึ้นเกี่ยวกับการเรียกซ้ำโดยสิ้นเชิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.