2013-07-26 46 views
8

tôi đã rảnh rang xung quanh với JavaScript, và thông báo một hành vi kỳ lạ (lạ đối với tôi ít nhất...)Javascript quản lý các cuộc gọi đệ quy như thế nào?

Vì vậy, tôi đã làm một SSCCE đây nó đi:

Tôi đã một div có tên là "myDiv"

function changeText(text){ 
    document.getElementById("myDiv").innerHTML=text; 
} 

function recursiveCall(counter){ 
    if(counter){ 
     setTimeout(function(){ 
      recursiveCall(--counter); 
      changeText(counter); 
     },750); 
    } 
} 

recursiveCall(10); 

Sống dụ: http://jsfiddle.net/T645X/

Vì vậy, tôi thay đổi các văn bản trên div, và những gì xảy ra là các văn bản đi 9-0, trong khi tôi nghĩ rằng nó đã được giả để đi từ 0 đến 9, kể từ cuộc gọi đệ quy changeText(counter); trước khi gọi phương thức thực sự thay đổi văn bản.

Trả lời

8

Hàm có thời gian chờ không đồng bộ.

setTimeout(function(){ 
    recursiveCall(--counter);// calls the next function, which will call the next 
          // and print in a timeout 
    changeText(counter); // print 
},750); 

Văn bản được thay đổi trước cuộc gọi đệ quy lượt truy cập thời gian chờ.

Nếu bạn muốn, bạn có thể chuyển cuộc gọi in từ bên ngoài thời gian chờ, mà sẽ dẫn đến hành vi mong đợi như vậy:

function recursiveCall(counter){ 
    if(counter){ 
     recursiveCall(--counter);    
     setTimeout(function(){ 
      changeText(counter); 
     },750); 
    } 
} 

(Mặc dù, lưu ý rằng đây việc in ấn là không đúng lúc ngoài ra, và chúng tôi đang dựa phần nào vào hành vi không xác định giả định nó sẽ in đầu tiên chỉ vì chúng tôi đặt hẹn giờ trước)

Nếu bạn vẫn muốn in vẫn bị chậm trễ, bạn có thể cho biết chức năng được thực hiện. Đệ quy vẫn sẽ được thực hiện bước đầu, nhưng mỗi cấp sẽ cho mức trên nó mà nó được thực hiện:

function recursiveCall(counter,done){ 
    if(counter){ 
     // note how recursion is done before the timeouts 
     recursiveCall(counter-1,function(){ //note the function 
      setTimeout(function(){   //When I'm done, change the text and let the 
       changeText(counter-1);  //next one know it's its turn. 
       done(); // notify the next in line. 
      },750); 
     }); 
    }else{ 
     done(); //If I'm the end condition, start working. 
    } 
} 

Here is a fiddle implementing this.

+0

+1 - Tôi đồng ý với điều này. Một ví dụ đơn giản mô tả điều này xảy ra từ 0-9 sẽ liên quan đến việc loại bỏ bản chất không đồng bộ của đệ quy. [demo] (http://jsfiddle.net/F9Nac/). Nó kết thúc rõ ràng ở mức 9, 0-8 có thể khó thấy khi chúng xảy ra ở cấp độ nano giây. –

+0

Bạn nói đúng, tôi đã làm bài kiểm tra tiếp theo để chứng minh điều đó: http://jsfiddle.net/AVyng/ (Tôi cần đợi 7 phút để chấp nhận câu trả lời này) – jsedano

+5

@anakata Tôi muốn bạn biết rằng lý do bạn có một câu trả lời quá nhanh (và một upvote) là bởi vì bạn đã diễn đạt câu hỏi của bạn rất tốt và nó chứa một ví dụ tái sản xuất đơn giản. Đạo cụ đọc sách hướng dẫn và vui mừng tôi có thể giúp :) –

2

Một điều cần hiểu là nó không phải đệ quy ngay từ đầu; nếu chức năng của bạn không có mệnh đề thoát thích hợp, điều này có thể tiếp tục mãi mãi mà không cần chạy vào một ngăn xếp bị thổi.

Lý do là bất kỳ hàm nào bạn chuyển đến setTimeout() đều chạy bên ngoài ngữ cảnh thực thi hiện tại; nói cách khác, mã "thoát ra" của hàm của bạn.

Nếu bạn muốn có một cuộc gọi đệ quy với 750ms ở giữa chúng, bạn có thể làm một cái gì đó như thế này:

function recursiveCall(counter, fn) 
{ 
    if (counter) { 
     recursiveCall(--counter, function() { 
      changeText(counter); 
      setTimeout(fn, 750); 
     }); 
    } else if (fn) { 
     fn(); // start chain backwards 
    } 
} 

Nó tạo ra một chuỗi các callbacks khi nó recurses và mệnh đề thoát đặt chuyển động toàn bộ dây chuyền, ngược :)

+0

Đó là một cuộc gọi lại ... phải không? – jsedano

+1

@anakata Bạn có nghĩa là đối số 'fn' là một cuộc gọi lại? Có, bạn có thể gọi nó là :) –

4

Nói đúng cách không có đệ quy nào tại đây.

Cuộc gọi đến setTimeout chỉ thêm một cuộc gọi lại vào danh sách các sự kiện hẹn giờ đã lên lịch.

Phần lớn thời gian, trình duyệt của bạn chỉ ngồi ở đó chờ sự kiện, trình duyệt xử lý các sự kiện đó (tức là chạy trình xử lý sự kiện của bạn) và sau đó quay lại chờ sự kiện.

Vì vậy, trong trường hợp này những gì bạn đang làm là:

recursiveCall(10) 
    timer event and callback added to the queue 
    function exits 

... waits 750 ms ... 

    timer event fires, callback pulled from the queue and invoked 
     -> recursiveCall(9) invoked 
     -> timer event and callback added to the queue 
     -> changeText(9) invoked 
    callback function exits 

... waits 750 ms ... 

    timer event fires, callback pulled from the queue and invoked 
     -> recursiveCall(8) invoked 
     -> timer event and callback added to the queue 
     -> changeText(8) invoked 
    callback function exits 

and so on... 

Tôi gọi đây là pseudo-đệ quy, bởi vì mặc dù nó có vẻ hơi giống như đệ quy cổ điển, mỗi lời gọi bắt đầu tại cùng một "stack frame" , tức là nếu bạn yêu cầu một dấu vết ngăn xếp thường sẽ chỉ là một (hoặc trong trường hợp của bạn đôi khi hai) dụ của recursiveCall có mặt tại một thời điểm.

+0

Tôi này bất cứ điều gì giống như đệ quy đuôi? (nhưng trong các chủ đề riêng biệt?) – jsedano

+3

* Nghiêm túc * nói đúng, đây là đệ quy hoàn toàn: hàm 'đệ quyCall' đề cập đến chính nó theo định nghĩa riêng của nó. Tôi đoán những gì bạn có nghĩa là, vì nó không bao giờ tự gọi, không có các cuộc gọi * đệ quy *. – ruakh

+0

@ruakh có, mặc dù nó đề cập đến chính nó, nó không bao giờ (trực tiếp) gọi chính nó. Đó là lý do tại sao tôi gọi là đệ quy giả. – Alnitak

Các vấn đề liên quan