01/10/2018, 14:54

Hàm setTimeout() trong js

Theo e hiểu thì kết quả sẽ chạy ra là từ 0 đến 4 và mỗi lần chạy in ra kết quả thì sau 1s mới in,
Tại sao kết quả lại ra là 5 lần 5 ạ ? Em có đọc trên mạng về callback function mà vẫn chưa hiểu,
Anh chị nào thông não chi tiết hộ em với ạ,
for (var i = 0; i < 5; i++) {
setTimeout(function(){
console.log('Yo! ', i);
}, 1000);
}

Nguyến Tùng Lâm viết 17:07 ngày 01/10/2018

var _loop_1 = function (i) {
setTimeout(function () {
console.log('Yo! ', i);
}, 1000);
};
for (var i = 0; i < 5; i++) {
_loop_1(i);
}

Đoàn Trọng Hiếu viết 17:02 ngày 01/10/2018

Cái này gọi là Closure nhé bạn

Sử dụng closure bên trong 1 vòng lặp

Closure là 1 chủ đề thường thấy trong các buổi phỏng vấn JavaScript, nó giúp người phỏng vấn đánh giá bạn thành thục ngôn ngữ đến đâu, bạn biết implement closure hay không.

Về cơ bản, closure là 1 hàm nội truy cập đến các biến bên ngoài phạm vi của nó. Closure có thể được sử dụng để implement privacy và tạo ra các function factory. Một câu hỏi phỏng vấn thường thấy về việc sử dụng closure sẽ có kiểu thế này:

Viết 1 function lặp qua 1 danh sách các số dạng integer và in ra index của mỗi giá trị sau thời gian chờ 3s.

Dưới đây là cách tôi hay thấy khi mọi người giải quyết bài toán này (thực ra là sai):

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 3000);
}

Nếu bạn chạy đoạn code trên, output bạn nhận được sẽ luôn là 4, mặc dù ta mong rằng output sẽ phải là 0, 1, 2, 3 sau mỗi 3s.

Tại sao lại như thế? Để hiểu rõ lý do, đương nhiên bạn cần nắm vững kiến thức về closure của JavaScript, bởi vì người phỏng vấn đang kiểm tra bạn về nó cơ mà!

Lý do là bởi vì hàm setTimeout sẽ tạo ra 1 function (closure) có thể truy cập phạm vi bên ngoài nó, vòng loop sẽ chứa index i. Sau 3s, hàm được thực thi và nó sẽ log ra giá trị của i, là giá trị cuối cùng của vòng lặp (4).

Có 1 số cách để viết hàm đúng. Dưới đây tôi sẽ chỉ nêu ra 2 cách:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  // pass in the variable i so that each function 
  // has access to the correct index
  setTimeout(function(i_local) {
    return function() {
      console.log('The index of this number is: ' + i_local);
    }
  }(i), 3000);
}

const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
  // using the ES6 let syntax, it creates a new binding
  // every single time the function is called
  // read more here: http://exploringjs.com/es6/ch_variables.html#sec_let-const-loop-heads
  setTimeout(function() {
    console.log('The index of this number is: ' + i);
  }, 3000);
}

Ref: https://www.topitworks.com/blogs/phong-van-javascript/

Văn Dương viết 17:01 ngày 01/10/2018

Bởi vì hàm setTimeout nó sẽ thực hiện một hàm mà được chỉ định sau t ms tính từ lúc gọi.
Bạn for 5 lần thì i cuối cùng sẽ có giá trị = 5 tuy nhiên hàm for chạy rất nhanh và nó sẽ thực hiện xong trước tất cả các hàm mà bạn set timeout chưa kịp chạy. Đến khi các hàm chỉ định bởi setTime chạy thì hàm for đã thực hiện xong rồi và tất nhiên lúc này chỉ thu được i =5.

Florastamine viết 17:02 ngày 01/10/2018

Đọc cuốn YDKJS để hiểu rõ về internals của JS, chính xác hơn là ở đây.

tl;dr: Bởi vì for(var i = ..., ivar nên scope của i sẽ là toàn bộ function nơi vòng lặp được chạy (hoặc global scope nếu ở ngoài). Mỗi lần gọi setTimeout() thì callback bên trong sẽ reference tới cùng biến i đó => khi vòng lặp kết thúc và callback được chạy thì tất cả sẽ đều in ra 5.

Để fix thì chỉ cần bắt callback trỏ tới 1 bản i duy nhất của nó, khỏi phải xài chùa i nữa.

for(var i = 0; i < 5; ++i) {
    (function(x) {
        setTimeout(function () { console.log(x); }, 1000);
    })(i);
}
ĐTĐTVN viết 17:05 ngày 01/10/2018

cám ơn các bác, để em tìm hiểu thêm về closure

Quang Le viết 16:57 ngày 01/10/2018

Ko phải closure mà là event loop mới giải đáp được câu hỏi của em.

Xem cái này nhé

ĐTĐTVN viết 17:01 ngày 01/10/2018

oh, ok anh. Em còn mơ hồ về cái này quá, tình hình là phải còn cày js dài dai

Quang Le viết 16:58 ngày 01/10/2018

Đây là mô phỏng JS runtime environment

Đây là code của em anh viết lại dạng đơn giản hơn:

for (var i = 0; i < 2; i++) {
setTimeout(function(){ console.log(i);}, 1000);}

Các bước thực thi sẽ như sau:

  1. JS engine gặp lệnh vòng lặp for nó gán biến i = 0
  2. Trong vòng lặp nó chạy setTimeout.
  3. setTimeout được thực hiện bởi Web API như trên hình và đặt 1s để chạy hàm function()…
  4. Tuy nhiên JS engine ko đợi setTimeout, mà nó tiếp tục chạy phần chương trình ngoài setTimeout và ở đây phần tiếp theo của chương trình là chạy vòng lặp lần thứ hai.
  5. Lần chạy thứ hai của vòng lặp for, JS engine gán biến i = 1, rồi lặp lại các bước từ 2 đến 4
  6. Sau vòng lặp thứ hai, i lúc này là 1 và chương trình coi như kết thúc
  7. Thế nhưng sau khoảng 1 giây, setTimeout hết thời gian chờ, nó quẳng function(){ console.log(i);}, 1000) vào Callback queue để đẩy lên Call Stack thực hiện.
  8. function()… in ra console giá trị của i, nhưng ̣i lúc này đã bằng 1 như ở bước 6.
  9. Tương tư như thế với setTimeout thứ 2

Mô tả ở trên có thể gọi là cơ chế hoạt động của JS Event Loop

ĐTĐTVN viết 16:54 ngày 01/10/2018

anh nhiệt tình quá. cái e cứ thắc mắc là tại sao i = 0 < 5 sao nó không in ra 0 sau 1 giây mà phải chạy đến tận 5 mới in. giờ thì giác ngộ rồi . cảm ơn anh lần nữa

Bài liên quan
0