Scope trong JavaScript



  • Em có 2 đoạn code như sau:

    function countdown() {
      console.log("Countdown:");
      for (var i = 5; i >= 0; i--) {
        setTimeout(function() {
          console.log(i);
        }, (i) * 1000);
      }
    }
    countdown();
    

    kết quả sau đoạn code này là: -1 -1 -1 -1 -1

    Nhưng đoạn code tương tự nhưng thay var i thành let i

    function countdown() {
      console.log("Countdown:");
      for (let i = 5; i >= 0; i--) {
        setTimeout(function() {
          console.log(i);
        }, (i) * 1000);
      }
    }
    countdown();
    

    Kết quả của đoạn code này là 0 1 2 3 4 5

    Có ai có thể giải thích giúp em lý do vì sao không ạ? Đặc biết là đoạn code thứ 2 ạ. Em mới tìm hiểu nên còn kém. Mong mọi người giúp đỡ



  • Thì bạn google "var vs let javascript" rồi đọc xem



  • Thực sự là e đã đọc rồi mà k thể hiểu nổi. Var vs let thì e hiểu rồi, nhưng ví dụ kia còn liên quan cả tới async, callback nên e còn có chỗ khó nghĩ, vậy nên rất mong ai biết giải thích chi tiết giúp em với ạ!


  • Global Moderator

    @trungthao bạn cố gắng hiểu đơn giản thôi. let thì chỉ có ý nghĩa trong 1 scope được bao đóng bởi cặp ngoặc nhọn { }. Như vậy vòng lặp for của bạn sẽ xuất hiện 5 scope với 5 giá trị của biến i, context của scope sẽ được giữ nguyên khi hàm setTimeout được thực thi sau 1000ms.



  • @trungthao letvar khác nhau về phạm vi.
    Khai biến với var cho ta một biến có giá trị trong phạm vi khối function chứa nó (nearest function block). Khai biến với let cho ta một biến có phạm vi trong khối block bao quanh nó (nearest enclosing block) hay tức là chỉ trong {} chứa nó.

    function foo() {
    	var x = 1;
    	if (1) {
    		// x in foo
    		var x = 100;
    	}
    	console.log(x); // x = 100
    	var callback = function() {
    		var x = 1000;
    		// x in callback
    	}
    	callback();
    	console.log(x); // x = 100 not 1000
    }
    
    
    function foo() {
    	let x = 1;
    	if (1) {
    		// x in if statement block
    		//console.log(x); // Error: x is not defined...
    		let x = 100;
    	}
    	console.log(x) // x = 1
    	function callback(){
    		x = 10000;
    	};
    	callback();
    	console.log(x); // x = 10000
    }
    

    Bên cạnh đó, var sẽ tạo properties trong phạm vi function chứa nó hay this.x được khởi tạo, còn let thì không.
    Quay lại vidu của bạn:

    for (var i = 5; i >= 0; i--) {
        setTimeout(function callback() {
          // i ở đây là i của function callback của setTimeout, có giá trị bằng giá trị cuối cùng của vòng lặp, tức i = -1
          console.log(i); // -1 -1 -1 -1 -1
        }, (i) * 1000); // i ở đây là của vòng function countdown nên giá trị = 5 4 3 2 1
      }
    

    Với ví dụ dùng let

     for (let i = 5; i >= 0; i--) {
        setTimeout(function() {
          console.log(i); // i ở đây có giá trị tương đương trong block của vòng for, giá trị = 5 4 3 2 1
        }, (i) * 1000); 
      }
    
    


  • @hidemanvn cho mình hỏi tại sao i chỉ khai báo 1 lần. Mà sao nó có thể tồn tại ở cả 5 scope với giá trị khác nhau vậy? Có nhiều biến i dc tạo ra à



  • Câu hỏi này khá thú vị đấy. Mình post thêm đoạn code này để chúng ta cũng ngâm cứu. Phạm vi của var và let chắc là mọi người cũng hiểu cơ bản rồi. Có điều gì đó đặc biệt trong vòng lặp for.

    (function countdown() {
        console.log("Countdown:");
        let i = 5; // Đưa let ra ngoài vòng lặp for
        for (; i >= 0; i--) {
            setTimeout(function() {
                console.log(i);
            }, (i) * 1000);
        }
    })();
    // kết quả -1 -1 -1 -1 -1 -1
    

    Có lẽ khi đưa let i vào for thì nó giống như thế này:

    (function countdown() {
        let i = 5;
        console.log("Countdown:");
        for (; i >= 0; i--) {
            let i2 = i; // tạo ra bản sao của biến i trong khối lệnh
            setTimeout(function() {
                console.log(i2);
            }, (i2) * 1000);
        }
    })();
    // kết quả 0 1 2 3 4 5
    
    • . ^
    0


  • @ST chỉ có một biến i. Vì bind giá trị i ở function callback của setTimeout khác nhau nên sẽ cho ra 5 giá trị khác nhau. Bạn có thể thay đổi giá trị biến i trong callback để thấy sự khác biệt



  • @hidemanvn Callback được gọi khi for đã chạy xong, thay đổi như thế nào để biết được nó có 1 biến đây. Mình đã dùng debug để watch biến đó. Trong vòng for i thay đổi (đương nhiên). Lúc bắt đầu gọi callback sau khi for chay xong i lại thay đổi lần nữa (cái này ngạc nhiên)

    vậy là có bao nhiêu biến y, bảo biến let thuộc {} mà mình có nhiều cái {} mà



  • Khi bạn khai báo biến bằng var, nó sẽ chịu ảnh hưởng của hiệu ứng hoisting trong JavaScript (bạn google thêm). Trong quá trình thông dịch, JavaScript engine sẽ xử lý như sau:

    Code của bạn:

      for (var i = 5; i >= 0; i--) {
         // setTimeout ...
      }
    

    Code sau khi thông dịch bởi JS engine:

    var i;
      for (i = 5; i >= 0; i--) {
         // setTimeout ...
      }
    

    Cho nên hidemanvn nói chỉ có 1 biến i là đúng.

    Khai báo bằng let sẽ không bị ảnh hưởng của hoist. Biến chỉ tồn tại trong block. Trong vòng lặp, mỗi lần lặp sẽ tạo ra 1 biến i khác nhau, không bị tham chiếu đến i trước đó.



  • @Huong-Nguyen Javascript LUÔN là pass-by-value nha bạn, với biến tới một object thì nó lại reference-to-the-object.

    function foo(a,array,object) {
    	a = 2;
    	array[0] = "green";
    	object="jane doe";
    }
    
    var x = 1, y = ["red","blue"], z = {name:"john doe"};
    var _foo = foo(x,y,z);
    console.log(x,y,z); // 1 [ 'green', 'blue' ] { name: 'john doe' }
    

    Ý mình bảo bạn thay đổi i để xem i ở callback của setTimeout khác có bị thay đổi không ?



  • @hidemanvn Mình hiểu ý bạn, nhưng mà nó k thay đổi nên mình k thể nghĩ chỉ có 1 được.
    Với cả k có pass cái biến nào vào anonymous function cả, mình dùng trực tiếp biến i của for.
    Nếu pass như thế này thì làm gì còn lỗi mà tranh luận. Cảm ơn bạn vì quan tâm đến thắc mắc của mình.

    for (var i = 0; i < 5; i++) {
      setTimeout((i) => {
        console.log(i)
      }, 0, i)
    }
    

    @ndaidong "Trong vòng lặp, mỗi lần lặp sẽ tạo ra 1 biến i khác nhau, không bị tham chiếu đến i trước đó." Mình hiểu câu này của bạn là có nhiều biến i được tạo ra, và nó k bị ảnh hưởng khi các biến trong scope khác thay đổi. Cảm ơn bạn vì keywork hoist.



  • @Huong-Nguyen bạn quên closure scoped rồi. Kể cả bạn có pass nó vào như thế kia nó vẫn không phải của i của for đâu, nó là i của function callback đó.
    i mình nói là của vòng for
    Bàn vậy thôi, chứ không cần quan tâm quá nhiều đến có bao biến tạo ra với Javascript.



  • This post is deleted!

Log in to reply