공부를 하면서 헷갈렸거나 확실히 머릿속에서 정립되지 않은 부분들을 따로 공부해 정리해봤다.
이 책에 내용들이 잘 정리되어있고 이해하기도 쉬워서 참고했다! 언어에 대한 부분은 물론, 네트워크나 다른 부분에 대해서도 함께 이해하기 좋은 책이었다. 도서관에서 발견한 책인데 역시 도서관 최고!
프로그래머스에서 알고리즘 문제를 풀다가 const
선언자로 변수를 할당했음에도 객체나, 배열의 경우 값이 추가되거나 삭제되는 걸 발견했다. 이상했다.
const
선언자로 값이 할당된 변수는 값을 재할당하려고 하면 당연히 에러가 발생해야하는 것 아냐?
하지만 조금만 생각해보면 당연히 되는 일이었다. 결국 값을 할당하는 건 =
연산자의 일이기 때문에 배열의 push()
메소드나 pop()
같은 메소드는 값을 재할당하는 게 아니기 때문이다. 객체의 경우에도 동일했다.
const object = {};
// 아래 두 경우는 object 내부 값을 변경하지만, object 자체를 재할당하는 건 아니다.
object.name = 'declarement';
object.declarer = 'const';
// 아래의 경우는 object 값을 재할당하므로 에러가 발생한다.
object = {
name: 'declarement',
declarer: 'const
}
이상하다고 생각할 수 있지만 엄연히 두 경우는 다른 경우이므로 2번째 경우에만 에러가 발생했다.
이에 대한 연장선 상에서 스코프와 호이스팅에 대해서도 궁금해졌다. 이전까지 스코프의 종류에는 두 가지가 있다고 생각했다. 전역 스코프와 렉시컬 스코프. 두 가지가 있는 건 맞았다. 그러나 "전역"과 "렉시컬"은 같은 범위 내에서 상반되는 개념이 아니었다. 잘못 이해하고 있었던 거다.
사실상 전역 스코프라는 건 존재하지 않았다. 그렇게 생각하게 된 이유는 단 하나, var
선언자 때문이었다.
그렇다면 어떻게 스코프가 구별하면 될까? 바로 "함수 스코프"와 "블록 스코프"로 구별한다.
함수 스코프는 말 그대로 함수를 선언하면 함수 내에 있는 모든 변수가 함수 스코프에 포함된다. 즉, 어떻게 보면 전역 스코프라고 생각했던 내용은 함수 스코프에 포함될 수 있는 내용이다. var
선언자로 선언된 함수는 함수 스코프를 따르기 때문에 만약 조건문 안에 있다고 하더라도, 조건문 외부에서 호출이 가능하기 때문이다.
function example() {
console.log(variant); // undefined
if(variant !== null) {
var variant = 0;
}
console.log(variant); // 0
}
이런 식으로 말이다. variant
는 분명 조건문 안에서 선언되었음에도 호이스팅되어 함수 스코프로 끌어올려진다. "전역" 또한 익명 즉시실행함수로 볼 수 있으니까 대략적으로 함수 스코프를 전역 스코프라고 멋대로 이해해버린 거다.
var
선언자로 선언된 변수라 해서 무조건 전역적으로 호이스팅되는 건 아니다. ❗함수 스코프❗ 내에서만 호이스팅되며 전역 변수일 경우에만 전역적으로 호이스팅이 된다.
var
선언자가 변수를 호이스팅시키는 이유는 선언과 초기화가 동시에 이뤄지기 때문이다. 함수 레벨 최상단으로 변수를 끌어올려 선언 후 값이 할당되지 않은 채 초기화된다. 그래서 예시처럼 undefined
로 값이 할당된 것처럼 만드는 오류가 발생한다.
만약 위 코드에서 variant
가 var
선언자가 아닌 let
, const
로 선언됐다면 첫번째 console.log에서 undefined가 찍히는 요상한 결과가 나오지는 않았을 것이다.
let
, const
두 선언자는 블록 스코프를 준수하기 때문이다. 블록 스코프는 변수의 유효 범위를 블록(중괄호) 단위로 제한하기 때문에 제멋대로 호이스팅되는 함수 스코프의 문제를 해결한다.
이 두 선언자는 선언과 초기화가 분리되어있다. var
선언자와 동일하게 선언은 스코프 최상단으로 끌어올려져 실행되지만, 초기화는 내가 원하는 위치에서 할 수 있다. 그래서 초기화 이전에 접근할 경우 Reference Error가 발생하고, 이 구간을 TDZ(Temporal Dead Zone)이라고 부른다.
렉시컬 스코프는 함수, 블록 스코프와는 다른 개념으로 언어 차원의 개념이다. 프로그래밍 언어의 스코프는 대부분 동적, 렉시컬 두 가지 스코프 방식으로 동작하는데 동적 스코프는 런타임 도중에 함수 호출 시 결정되고, 렉시컬 스코프는 변수와 함수 위치에 기반해 결정된다. 자바스크립트는 이 렉시컬 스코프를 따르는 언어로 함수 스코프와 블록 스코프 역시 렉시컬 스코프의 규칙에 따라 스코프의 경계가 결정된다.
함수 스코프와 블록 스코프는 스코프의 단위이며, 렉시컬 스코프는 이 스코프들의 범위를 결정하는 규칙이다.