2022.03.21(월)
Garbage Collector
Scope
hoisting
Closure
자바스크립트는 Garbage Collector를 제공한다. low level 언어와 다르게 자바스크립트는 메모리를 직접 건드릴 수 있는 방법이 없다. 따라서 Gargabe Collector를 통해 자바스크립트가 자동으로 메모리 관리를 해준다.
자바스크립트의 Garbage Collector에 대해 배우면서 Mark Sweep 알고리즘에 대해서 알고 싶어서 조금 더 공부했다.
내부 알고리즘을 정리하면,
자료를 찾다 보니 다른 알고리즘과 더 심화적인 내용들이 있었지만 우선 자바스크립트에서 메모리 관리를 위해 사용하는 Garbage Collector의 개념과 기본 알고리즘에 대해서 배웠다.
참고 : 자바스크립트의 메모리관리
참고 : 자바스크립트 가비지 컬렉션
var는 함수 스코프 let, const는 블록 스코프
var와 let,const는 스코프가 다르다. 스코프의 차이는 hoisting과도 연관이 있다.
const age = 30;
if(age > 19){
var text = "adult";
}
console.log(text);
if 문 내부의 변수 text를 외부에서 사용 가능하다. 하지만 let과 const로 선언 한다면 사용할 수 없다. 블록 스코프이기 때문에 if 문 내부에서만 사용 가능하기 때문.
function add(num1, num2) {
var result = num1 + num2;
}
add(2, 3);
console.log(2, 3);
add 함수 내부 var 변수는 함수 스코프를 갖기 때문에 함수 밖에서는 사용할 수 없다. let, const로 선언한다 해도 블록 스코프(여기서는 함수)를 벗어나는 외부에서 사용할 수 없다.
이게 뭐야... 왜 되는거지.
hoisting을 알기 위해서 ES6에서 var 사용을 권장하지 않는 이유와 let 과 const를 사용해야 하는 이유를 알게 되었다.
호이스팅(Hoisting) 이란, 변수의 선언이 코드의 맨 위로 이동해서 먼저 할당 전에 실행되는 것을 의미한다.
hoisting 발생 예제
console.log(name); // undefined
var name = "Minki";
이 코드가 에러 없이 실행된 이유는 hositing에 의해서 코드가 아래와 같이 변경되었기 때문이다.
var name;
console.log(name);
name = "Minki";
여기서 undefined가 출력된 이유는 변수의 선언 var name은 상단으로 hoisting 되지만 할당 (="minki")은 hositing 되지 않기 때문이다.
그렇다면 let, const는 호이스팅이 안되나?
📍정답은 NO!!
let, const로 선언한 변수도 호이스팅 대상이지만, var와 달리 호이스팅 시 undefined로 변수를 초기화하지는 않는다. 따라서 변수의 초기화를 수행하기 전에 읽는 코드가 먼저 나타나면 예외가 발생한다.
변수 스코프 맨 위에서 변수의 초기화 완료 시점까지의 변수는 시간상 사각지대(TDZ, Temporal Dead Zone)에 들어간 변수라고 표현한다. TDZ 영역에 들어 있는 변수들은 선언될 때까지 사용할 수 없다.
function hoistingTest() {
console.log(bar); // undefined
console.log(len); // ReferenceError
var bar = 1;
let len = 2;
}
위 코드에서 var로 선언한 변수 bar는 호이스팅 되어 스코프 최상단으로 이동한다. 할당은 이동하지 않았기 때문에 undefined가 출력된다. let의 경우 선언된 변수 len은 호이스팅 되어 스코프 최상단으로 이동하지만 할당하는 코드(len = 2)에 도달하기 전까지는 TDZ임으로 레퍼런스 에러가 발생한다.
var는 한번 선언된 변수를 다시 선언할 수 있다. ▶️ 에러 만들기 너무 좋다.
참고 : 시간상 사각지대
자바스크립트는 어휘적 환경(Lexical Enviroment)를 갖는다.
오늘 배운 내용중 가장 이해하기 어려웠던 부분이다. 어휘적 환경이 어떤 의미인지 그로 인해서 어떤 효과가 발생하는지 이해가 가질 않아서 좀 더 찾아봤다.
Closure란 함수와 렉티컬 환경의 조합이다. 함수가 생성될 당시의 외부 변수를 기억하고 생성 이후에도 계속 접근이 가능하다.
더 이해가 안간다.
첫 번째 예시
let one;
one = 1;
function addOne(num) {
console.log(one + num);
}
addOne(5);
이 코드를 실행하면 Lexical 환경에 one, addOne 함수가 추가된다. 그리고 addOne(5) 함수를 실행함과 동시에 새로운 Lexical 환경이 생성되고 이곳에는 함수가 전달 받은 매개변수와 함수 내부의 지역변수들이 추가된다.
함수가 호출되는 동안 함수에서 만들어진 내부 지역 Lexical 환경과 외부에서 받은 전역 Lexical 환경 두 개를 갖는다. 내부(지역) Lexical 환경은 외부(전역) Lexical 환경에 대한 참조를 갖는다.
코드에서 변수를 찾을 때, 내부 Lexical 환경에서 찾고 그 다음 외부 Lexical 환경에서 찾는다. addOne 함수의 경우 내부 Lexical 환경에서 매개변수로 전달 받은 num 변수를 찾고 one은 외부 Lexical 환경에서 찾는다. 따라서 함수 외부에 있는 one과 내부에 있는 num을 더해 콘솔로 출력한다.
두 번째 예시
function makeAdderFunction(x){
return function(y){
return x+y;
}
}
const add3 = makeAdderFunction(3);
console.log(add3(2)); // 5
const add10 = makeAdderFunction(10);
console.log(add10(5)); // 15
console.log(add3(1)) // 4
이 코드의 전역 Lexical 환경에서는 makeAdderFunction과 add3이 추가되어 있다. makeAdderFunction(3) 코드가 실행되면 makeAdderFunction Lexical 환경이 생기고 매개변수로 전달 받은 x(3)이 추가된다. console.log(add3(2)) 함수가 실행되면 익명함수 Lexical 환경이 생기고 매개변수로 전달 받은 y(2)가 추가 된다. function(y)는 외부 Lexical 환경을 참조해가며 5를 리턴한다.
정리하면,익명함수 function(y)는 y를 갖고 있고 상위 함수인 makeAdderFunction의 x에 접근 가능하다. 외부 함수의 실행이 끝나서 소멸된 이후에도 내부함수가 외부함수의 변수에 접근할 수 있다.
세 번째 예시
function makeCounter() {
let num = 0;
return function() {
return num++;
}
}
let counter = makeCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
counter = makeCounter() 코드가 실행된 다음 counter에는 리턴 받은 익명함수가 저장된다. (makeCounter는 소멸되었다. 따라서 let num 또한 소멸된것 처럼 보인다.)
counter 함수를 실행하면 num의 값이 계속해서 증가하는데 num의 값을 변경시키는 방법은 없다. 이런 점을 사용해서 은닉화가 가능하다는 것이다.
참고 : 클로저
Garbage Collector, Scope, hoisting, Closure 등 자바스크립트 기본 문법을 정리해 보았다. Closure는 특히 해결방법으로 설명해주신 즉시 실행함수를 사용하여 루프마다 클로저를 만드는 방법이나 let을 사용해서 해결하는 부분은 더 공부해야겠다...