- 프로토타입
- 스코프 ⏪
- 호이스팅 ⏪
- 클로저
- 모듈
함수
와 블록 단위
의 스코프로 나뉘며, 함수나 블록의 선언 위치에 따라 중첩 스코프
를 정의호이스팅
의 동작 방식과 모듈 스코프
정의와 사용법 정리가장 흔하게 사용되는 함수 스코프부터 알아보자.
function foo() { // foo() 함수 스코프가 생성됨
var a = 1; // foo() 함수 스코프에 포함되는 변수 a
function bar(b) { // foo() 함수 스코프에 포함되는 함수 bar()
console.log(a, b); // 1, 2
}
bar(2);
}
foo();
function foo() { // foo() 함수 스코프가 생성됨
if (true) {
var a = 1; // var 키워드로 생성된 변수 a
}
console.log(a); // 1
}
foo();
var
키워드로 선언한 변수 a
는 함수 스코프를 따르기에 조건문 블록을 무시하고 함수 몸체 안에 접근할 수 있다.let
, const
로 선언된 변수는 블록 스코프를 따른다. → 함수 스코프 문제를 해결할 수 있다.function foo() {
if (true) {
const a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
}
foo();
const
키워드로 선언된 a
변수는 해당 블록 안에서만 유효하므로 블록을 벗어나서는 접근할 수 없다. var
와 let
, const
는 스코프를 통해 확실히 구분할 수 있다. 책에서 계속해서 강조하는 부분은 변수는 블록 스코프를 갖도록 선언하는 것이 직관적이며 버그를 줄일 수 있다.
자바스크립트만 그런 것이 아니라 대부분의 프로그래밍 언어가 동적 스코프와 렉시컬 스코프 두 가지 방식으로 동작하는데, 동적 스코프는 함수의 호출에 의해 결정되며 렉시컬 스코프는 변수나 함수를 어디에 작성하였는가에 따라 결정된다. 대부분의 현대 프로그래밍 언어들은 렉시컬 스코프 규칙을 따르며 자바스크립트 또한 마찬가지이다.
this 바인딩
과스코프
를 착각하지 말자. 자바스크립트는 코드가 작성된 문맥에 따라 정적으로 결정되는 렉시컬 스코프를 따르지만, this 바인딩은 함수를 호출하는 방법에 따라 동적으로 달라진다는 것!
function foo() { // 1. 전역 스코프
var a = 1; // 2. 함수 스코프 foo()
function bar(b) { // 3. 함수 스코프 bar(b)
console.log(a, b); // 1, 2
}
bar(2);
}
foo();
위 예제에 중첩되어 있는 3개의 스코프를 알아보자.
📌 console.log(a, b)
코드가 변수 a를 검색하는 과정
- console.log() 메소드에서 참조된 변수 a를 찾기 위해 bar() 함수 스코프부터 검색을 시작
- bar() 함수의 스코프에서는 변수 a를 찾을 수 없으므로 가장 가까운 상위 스코프 foo() 함수 스코프로 올라가 검색
- foo() 함수 스코프에서 변수 a를 찾아 사용하며 검색 중단
📌 주의할 점
렉시컬 스코프
의 규칙에 따라!⇒ 이러한 스코프들의 연결 관계를 ✨스코프 체인(scope chain)이라 하며, 이를 따라 검색하는 과정을 ✨스코프 체이닝(scope chaining)이라 한다.
자바스크립트에서는
with문
과eval() 함수
를 사용하여 렉시컬 스코프를 동적으로 변경할 수 있다. 동적 스코프 변경은 성능을 저하시키며 보안상 문제를 야기할 수 있다는 점을 알자. 두 방법 모두 사용하지 않는 것이 권장된다.
📝 함수 스코프와 블록 스코프는 스코프의 단위이며, 렉시컬 스코프는 이 스코프들의 범위를 결정하는 규칙이다. 헷갈리지 않도록 하자.
호이스팅
이란 선언문이 스코프 내의 가장 최상단으로 끌어올려지는 것을 의미한다. 자바스크립트하면 호이스팅이라는 단어를 들어보지 않을 수가 없을 정도로 유명한 특징이지 뭐…
console.log(a); // undefined
var a = 1;
// 위 코드는 아래 코드처럼 처리된다.
// **스코프의 최상단에서 변수 a의 선언과 초기화가 한 번에 실행된다.**
var a;
console.log(a); // undefined
a = 1;
📌 자바스크립트의 변수 생성의 세 가지 단계
선언
: 스코프에 변수를 선언한다.
초기화
: 변수의 값을 undefined로 초기화하며, 실제로 변수에 접근 가능한 단계이다.
할당
: 할당문을 만나면 변수에 실제 값을 할당한다.
var
키워드로 선언한 변수는 먼저 스코프의 최상단으로 끌어올려진 뒤, 선언과 초기화 돤계를 한 번에 실행한다. 때문에 선언하기 전에 변수에 접근하여도 이미 초기화되어 접근이 가능하다. 이것이 호이스팅이다.
function foo() {
console.log(a);
var a = 1;
}
// 위 코드는 아래 코드처럼 처리된다.
function foo() {
var a; // **선언** + **초기화가 같이 됨!**
console.log(a); // 변수에 접근이 가능
a = 1; // **할당**
}
var
로 선언한 변수는 선언과 초기화가 한번에 같이 실행된다. 때문에 선언하기 전에 변수에 접근해도 이미 초기화되어 접근이 가능하다는 것이다. 근데 let
과 const
로 선언한 변수는 다르다.
let
과 const
로 선언한 변수는 var
로 선언한 변수와 다르게 초기화 단계가 분리되어 실행된다. 선언 단계는 스코프의 최상단으로 끌어올려져 실행되지만, 초기화 단계는 선언문을 만나면 실행된다. 초기화 단계 이전에 변수에 접근하려하면 ReferenceError가 발생한다.
📌 선언 단계가 실행되는 스코프의 최상단부터 초기화 단계를 실행하는 선언문이 나오기 전까지는 변수에 접근할 수 없다. 이 구간은 ✨TDZ(Temporal Dead Zone)이라고 부른다.
뭔 소리여?
단계로 따지면...
console.log(a); // Uncaught ReferenceError: a is not defined
let a = 1;
let
키워드로 선언된 변수 a
를 초기화 단계를 실행하기 전인 TDZ 구간에서 참조하였기에 에러가 발생한다.
⇒ let a = 1;
라인에서 변수 a
의 선언은 위로 끌어올려졌지만 초기화는 안되어있는 상태로 첫 줄인 console.log(a);
가 실행된다는 거다.
⇒ 얘네는 선언 전에는 접근이 안된다는 거임.
🤷 그렇지만 선언 단계는 스코프의 최상단으로 끌어올려져 실행된다.
let a = 1;
function foo() {
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 1;
}
foo();
let a = 1;
)을 만나기 전까지 TDZ 구간이 생성된다.실제로 let과 const의 호이스팅은 논란이 많은 부분이라고 한다. 선언 단계는 최상단으로 끌어올려지니 let과 const도 호이스팅이 발생한다고 주장하는 개발자들이 있고, 초기화 단계가 실행되지 않아 변수에 직접 접근이 불가능하니까 호이스팅이 발생하지 않는다고 주장하는 개발자도 있다. 이는 ECMAScript 명세에도 명확하게 내용이 없어 의견이 분분하다.
😒 아무튼간에 let과 const의 선언 단계도 최상단으로 끌어올려져 실행된다는 것은 팩트다.
❓ 근데 왜 선언은 위로 올려지도록 설계한거지...?
함수 선언문도 선언문이니 호이스팅이 발생한다. 다른 점은 함수 선언문은 선언, 초기화, 할당 모두 스코프 최상단에서 실행되므로 어느 위치에서든 함수를 사용할 수 있다는 점이다.
📌 함수 선언문이 아닌 함수 표현식
은 변수의 호이스팅 규칙에 따라 동작한다.
(이건 뭐 직관적이니까 의문이 생기진 않는다.)