언뜻보기엔 쉬워 보일 수 있지만, 바로 대답하기 어려울 수 있다.
두 키워드의 차이점은 코드를 보며 알아 보자.

function doStuff() {
  // a,b 변수 모두 선언된 함수 내부에서만 사용 가능하다.
  let a = 5;
  var b = 5;
  
  console.log(a + b); // 10
}

doStuff(); // 10;
console.log(a); // ReferenceError
console.log(b); // ReferenceError

function doMoreStuff() {
  if (2 + 2 === 4) {
    // var 로 선언된 변수는 선언된 함수 어디에서나 사용 가능하다.
    var a = 5;
    // 그러나 let 으로 선언된 변수는 선언된 block(if 문)에서만 사용할 수 있다.
    let b = 5;
  }
  console.log(a); // 5
  console.log(b); // ReferenceError
}

doMoreStuff();
// 5
// ReferenceError

for (let i = 0; i < 5; i++) {
  // i 변수는 매 루프마다 재생산된다.
  // 그리고 setTimeout 함수는 항상 재생산된 i 변수를 접근 한다.
  setTimeout(() => console.log(i), 100);
}
/*
0
1
2
3
4
*/

for (var j = 0; j < 5; j++) {
  // thus, it is not recreated and setTimeout gets the same reference to j
  // j 변수는 전역스코프로 정의된다.
  // 그러므로, j 변수는 재생성되지 않고 settimeout 함수는 동일한 전역변수 j 를 참조하게된다.
  setTimeout(() => console.log(j), 100);
}
/*
5
5
5
5
5
*/

둘의 차이점의 핵심은 var 는 함수 스코프 ( fucntion-scope ) 이고 let 은 블록스코프 ( block-scope ) 이다.
이에 따라 함수의 선언위치에 따른 접근 가능 범위가 달라지게 되고, 루프나 함수 내부에서 다르게 동작한다.

여기서 추가로 드는 의문은 왜 루프의 콜백함수에서 let 은 왜 var랑 다르게 동작하는가이다.

이해를 위해 대략적으로 설명하자면,
let 변수가 for 루프문에서 선언되면 해당 루프 전용 변수를 별도로 만들어서 사용한다라고 이해하면 편하다.
( 물론 이해를 위해 설명한 부분이므로 실제 작동 방식과는 다르다.. )