Frontend Study - 2/Javascript

자바스크립트 : 스코프와 클로저 (Scope & Closure) / for 반복문에서의 let & var

갓데미 2022. 6. 9. 18:22

1. Scope 스코프.

 

  • 사전적 의미로 범위, 영역이라는 의미가 있습니다.
  • 다른 프로그래밍 언어에서도 공통적으로 쓰이는 컨셉으로 참조할  있는 범위, 식별자(변수, 함수, 클래스이름) 유효한 범위 의미합니다. 선언된 위치에 따라 유효범위가 결정됩니다. 
function test() {
	let name = 'leo';
    	}
  • name이라는 변수의 '스코프'는 test 함수 '{'    '}' 사이.

 

  • 하위 스코프는 상위 스코프에 접근할  있지만 상위 스코프는 하위 스코프에 접근이 불가합니다. { } 블럭 안의 변수는 블럭 안에서만 유효하고 밖에서는 { } 블럭 안의 내용에 들어가지 못합니다.
let text = "hi";

function print() {
  console.log(text);
  let name = "leo";
}

print(); // hi
console.log(name); // name is not defined
  • print() 함수 내부의 text 인자는 외부의 text 변수를 받아왔지만,
  • 외부의 name인자는 print()함수 내부의 name 변수를 받아오지 못했습니다.

 

  • 스코프 안의 변수는 블럭안에서만 유효하고 블럭이 끝나면 *Garbage Collector 를 통해 메모리에서 제거됩니다 적절한 곳에서 변수를 선언한다면 메모리를 절약할 있게 됩니다. 
  • 글로벌 변수는 앱이 종료될 때까지 계속 메모리에 유지됩니다. 계속 필요하지 않은 변수를 글로벌 변수로 선언했다면 메모리 낭비가 되는 것입니다.  
  • 변수는 필요한 곳에서 선언해야 좋습니다.

*Garbage Collector

: C언어의 경우 개발자가 직접 메모리 관리를 해야 합니다. 얼만큼의 메모리크기를 설정, 할당 할 지 정하고 일일이 다 지워야 합니다.

하지만 자바스크립트에서는 지우는 작업을 자동으로 해주는 시스템이 있습니다.

Garbage Collector 는 백그라운드 프로세스로 아무도 참조하지 않는 데이터들을 적절한 타이밍에 삭제합니다. 그때 그때 바로 삭제 하는것이 아니라, 어플리케이션의 메모리를 살피며 바쁠 때는 가만히 있다가 한가할 때 삭제합니다. 

주기적인 삭제로 메모리는 절약 될 수 도 있지만, 결국 쓰레기를 모으고 삭제하는 과정에도 메모리가 소요되기 때문에 변수를 남발하는 것은 좋지 않습니다.  

 

 

2. Closure 클로저

 

  • 내부함수에서 외부함수에 있는 상태에 접근할 수 있는 권한을 주는 것.
  • 자신이 선언된 곳의 렉시컬 스코프에 대한 접근 권한

 

function init() {
      var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
      function displayName() { // displayName() 은 내부 함수이며, 클로저다.
        alert(name); // 부모 함수에서 선언된 변수를 사용한다.
      }
      displayName();
    }
    init();

 

- init()이라는 함수를 통해서 name 이라는 변수와, displayName 이라는 함수를 생성했습니다.

- displayName() 함수는 name이라는 변수가 없음에도 alert(name) -> 인자로 사용하였습니다.

- 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 부모 함수 init()에서 선언된 변수 name에 접근할 수 있었던 것 입니다.

- displayName()가 자신만의 name변수를 가지고 있었다면, name대신 this.name을 사용했을 것입니다.

 

-> 이것은 어휘적 범위 지정(lexical scoping)의 예시입니다. "lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능 한지 알기 위해 어디서 선언되었는지 고려한다는 것을 의미합니다.

 

 

function makeAdder(x) {
      var y = 1;
      return function(z) {
        y = 100;
        return x + y + z;
      };
    }

    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    //클로저에 x와 y의 환경이 저장됨

    console.log(add5(2));  // 107 (x:5 + y:100 + z:2)
    console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
    //함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
  • makeAdder함수는 특정한 값을 인자로 가질 수 있는 함수들을 리턴합니다.
  • 위의 예제에서 add5, add10 두 개의 새로운 함수들을 만들기 위해 makeAdder함수 공장을 사용했습니다. 
  • add5와 add10은 둘 다 클로저입니다. 이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장합니다.
  • 함수 실행 시 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한 것을 볼 수 있습니다. 이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미합니다.
  • 함수가 리턴이 될 때 해당 함수 뿐만 아니라, 함수를 감싸는 외부 렉시컬 환경까지 같이 클로저로 리턴이 되기 때문입니다.
  • 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하게 됩니다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합으로, 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성됩니다.

 

+ for 반복문에서의 let & var

 

for 반복문 (초기식)에서 let으로 선언한 변수 i 는 for 스코프에 속합니다. for 바깥에서 사용할 수 없습니다.

for (let i = 0; i < 3; i++) {
  console.log(i); // 출력: 0 1 2
}
console.log(i); // 오류: ReferenceError

 

 

for 반복문 (초기식)에서 var로 선언한 변수는 for 스코프가 아닌, 전역 스코프에 속합니다. 그러므로 for 바깥에서도 사용할 수 있습니다.

또한 바깥에서 출력한 값이 3이 나왔다는 사실을 통해서

i 변수 안에는 for 문이 주어진 반복을 완료한 후에 남은, 즉 for문을 반복 불가하게 만든 값이 남는 다는 것을 알 수 있습니다.

for (var i = 0; i < 3; i++) {
  console.log(i); // 출력: 0 1 2
}
console.log(i); // 출력: 3

 

for안의 function에서 var를 사용하면 안 되는 이유.

function loop() {
  const array = [];
  //var , let의 차이점!
  for (var i = 0; i < 5; i++) {
    array.push(function () {
      console.log(i);
    });
  }
  return array;
}

const array = loop();
array.forEach((func) => {
  func();
});

 

0 1 2 3 4 가 출력될 것 처럼 보이지만 5 5 5 5 5 가 출력됩니다.

for 안의 var를 let으로 변경한다면 0 1 2 3 4 가 출력됩니다.

 

 

case 1 ) var i

함수는 함수가 정의될 때의 변수 자체만 기억하고 있을 뿐 실제 변수로부터 당장 값을 꺼내오지는 않습니다.

var i = 0, var i = 1, var i =2, var i = 3, var i =4 까지 실행하며

console.log(i), console.log(i), console.log(i), console.log(i), console.log(i) 가 생기게 되고,

var i = 5 가 되면서 반복이 멈추게 됩니다. 

 

 i가 var로 선언되었고, 전역변수로 남게 되었기 때문에 i 는 하나만 존재하게 됩니다. for 안의 function은 클로저 이므로 안에 포함되어 있는 i의 참조값을 같이 데려가서 값을 반환됩니다. 리턴하는 시점의 i를 찾아서 데려갑니다. 값은 5에서 멈춰 있기 때문에, 모든 i는 5가 되게 됩니다.  

 

 

case 2) let i

 for 문의 초기식 for (i = 0; i < 3; i++)  주황색 부분에 존재하는 let의 경우, 특수한 작용이 일어납니다. 이 작용은 for 루프가 돌 때마다 새로운 i를 만들고, 여기에 이전 i의 값을 대입합니다. let으로 선언된 i는 예외적으로 push function 안의 i 에서 각각의 값 별로 참조가 가능해 저마다 다른 i를 가리키게 됩니다.

 

let의 이 특수한 작용은 오직 for 문의 초기식 안에서만 일어납니다. 다음 코드는 i가 하나만 존재하므로 var와 똑같이 55555 출력됩니다.

var functions = [];

let i = 0; // for 문 바깥에서 let 사용
for (; i < 3; i++) { // 초기식을 비워 둠
  functions.push(function () {
    console.log(i);
  });
}

functions[0](); // 출력: 3
functions[1](); // 출력: 3
functions[2](); // 출력: 3