📢 자바스크립트는 함수 지향 언어이다.
코드 블록
◾️ 코드 블록 {...} 안에서 선언한 변수는 블록안에서만 사용할 수 있다.
{
let message = 'Hello';
console.log(message); // Hello
}
console.log(message); // ReferenceError: message is not defined
◾️ 고유한 작업을 수행하는 코드를 한데 묶는 용도로 활용할 수 있다.
{
let message = 'Hi';
console.log(message); // Hi
}
{
let message = 'Hello';
console.log(message); // Hello
}
⚠️ 블록이 없으면 에러가 발생할 수 있다.
◾️ 이미 선언된 변수와 동일한 이름을 가진 변수를 별도의 블록 없이 let으로 선언하면 에러가 발생한다.
◾️ if, for, while 등에서도 {...} 안에서 선언한 변수는 오직 블록 안에서만 접근 가능하다.
◾️ 변수의 범위를 블록 범위, 특히 if분기문 범위로 한정시킬 수 있어서 아주 유용하다.
if(true){
let message = 'Hello!';
console.log(message); // Hello
}
console.log(message); // ReferenceError: phrase is not defined
◾️ 변수 i는 for 안에서만 사용할 수 있다.
◾️ let i는 {...} 밖에 있긴 하지만 for 옆 괄호 안에서 선언한 변수는 블록 {...}에 속하는 코드로 취급된다.
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
alert(i); // ReferenceError: i is not defined
중첩 함수
◾️ 함수 내부에서 선언한 함수는 중첩(nested) 함수라고 한다.
◾️ 중첩 함수는 코드를 정돈하는데 사용할 수 있다.
◾️ 중첩 함수는 반환될 수 있다. 새로운 객체의 프로퍼티 형태로나 중첩 함수 그 자체로 반환된다.
◾️ 반환된 함수는 어디서든 호출해 사용할 수 있다. (외부 변수에 접근할 수 있다. )
function sayHiBye(firstName, lastName){
// 헬퍼(helper) 중첩 함수
function getFullName() {
return firstName + ' ' + lastName;
}
console.log('Hello, ' + getFullName());
console.log('Bye, ' + getFullName());
}
◾️ makeCounter는 숫자를 세주는 카운터 함수를 만든다. 카운터 함수는 호출될 때마다 다음 숫자를 반환한다.
function makeCounter() {
let count = 0;
return function () {
return count++;
};
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2
렉시컬 환경
단계 1. 변수
◾️ 자바스크립트에선 실행 중인 함수, 코드 블록 {...}, 스크립트 전체는 렉시컬 환경(Lexical Environment)이라 불리는 내부 숨김 연관 객체(internal hidden associated object)를 갖는다.
◾️ 렉시컬 환경 객체의 구성
- 환경 레코드(Environment Record) : 모든 지역 변수를 프로퍼티로 저장하고 있는 객체. (this값과 같은 기타 정보도 저장된다. )
- 외부 렉시컬 환경(Outer Lexical Environment)에 대한 참조 - 외부 코드와 연관됨
◾️ 변수는 특수 내부 객체인 환경 레코드의 프로퍼티일 뿐이다. 변수를 가져오거나 변경하는 것은 환경 레코드의 프로퍼티를 가져오고나 변경함을 의미한다.
◾️ 네모 상자는 변수가 저장되는 환경 레코드를 나타낸다.
◾️ 붉은 화살표는 외부 참조(outer reference)를 나타낸다. (전역 렉시컬 환경은 외부 참조 X → null)
◾️ 전역 렉시컬 환경(global Lexical Environment) : 스크립트 전체와 관련된 렉시컬 환경
◾️코드가 실행되고 실행 흐름이 이어져 나가면서 렉시컬 환경은 변화한다.
- 스크립트가 시작되면 스크립트 내에서 선언한 변수 전체가 렉시컬 환경에 올라간다. (pre-populated)
- 이때 변수의 상태는 특수 내부 상태(special internal state)인 uninitialized가 된다. 자바스크립트 엔진은 uninitialized 상태의 변수를 인지하긴 하지만, let을 만나기 전까진 이 변수를 참조할 수 없다.
- let phrase에서 아직 값을 할당하기 전이기 때문에 프로퍼티 값은 undefined이다. 이 시점 이후부터 phrase를 사용할 수 있다.
- phrase에 값이 할당되었다. (Hello)
- phrase의 값이 변경되었다. (Bye)
⚠️ 렉시컬 환경은 명세서에만 존재한다.
◾️ 렉시컬 환경은 명세서에서 자바스크립트가 어떻게 동작하는지 설명하는 데 쓰이는 이론상의 객체이다. ( 렉시컬 환경을 얻거나 조작하는 것은 불가능)
◾️ 자바스크립트 엔진들은 명세서에 언급된 사항을 준수하면서 엔진 고유의 방법을 사용해 렉시컬 환경을 최적화한다.
단계 2. 함수 선언문
◾️ 함수는 변수와 마찬가지로 값이다.
◾️ 다만 함수 선언문(function declaration)으로 선언한 함수는 일반 변수와는 달리 바로 초기화 된다는 점에서 차이가 있다.
◾️ 함수 선언문으로 선언한 함수는 렉시컬 환경이 만들어지는 즉시 사용할 수 있다. ( 선언되기 전에도 함수를 사용 할 수 있는 이유 )
◾️ 이러한 동작 방식은 함수를 변수에 할당한 함수 표현식(Function Expression)에는 해당하지 않는다.
단계 3. 내부와 외부 렉시컬 환경
◾️ 함수를 호출해 실행하면 새로운 렉시컬 환경이 자동으로 만들어진다. 이 렉시컬 환경엔 함수 호출 시 넘겨받은 매개변수와 함수의 지역 변수가 저장된다.
◾️ 함수가 호출 중인 동안은 호출 중인 함수를 위한 내부 렉시컬 환경과 내부 렉시컬 환경이 가리키는 외부(전역) 렉시컬 환경 두 개를 갖게 된다.
- 내부 렉시컬 환경은 현재 실행 중인 함수인 say에 상응한다. 내부 렉시컬 환경엔 함수의 인자인 name으로 부터 온 프로퍼티가 있다.
- 외부 렉시컬 환경은 전역 렉시컬 환경이다. 전역 렉시컬 환경은 phrase와 함수say를 프로퍼티로 갖는다.
◾️ 내부 렉시컬 환경은 외부 렉시컬 환경에 대한 참조를 갖는다.
◾️ 코드에서 변수에 접근할 땐, 먼저 내부 렉시컬 환경을 검색범위로 잡는다.
◾️ 내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 검색 범위를 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 확장한다.
◾️ 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복된다.
변수 검색 진행 과정
- 함수 say 내부의 alert에서 변수 name에 접근할 땐, 먼저 내부 렉시컬 환경을 살펴본다. (변수 name을 찾음)
- alert에서 phrase에 접근하려는데, 내부 렉시컬 환경에 없기 때문에 검색 범위는 외부 렉시컬 환경으로 확장된다. (외부 렉시컬 환경에서 phrase를 찾음)
단계 4. 반환 함수
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
◾️ makeCounter()를 호출하면 호출할 때마다 새로운 렉시컬 환경 객체가 만들어진다. 그리고 이 렉시컬 환경 객체에는 makeCounter를 실행하는데 필요한 변수들이 저장된다.
◾️ makeCounter()가 실행되는 도중에 한줄짜리 본문을 가진 중첩 함수가 만들어진다. (중첩함수가 생성되기만 하고 실행은 되지 않은 상태)
◾️ 모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억한다.
◾️ 함수는 [[Environment]]라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장된다.
◾️ counter.[[Environment]]엔 {count:0}이 있는 렉시컬 환경에 대한 참조가 저장된다.
→ 호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있다.
[[Environment]]는 함수가 생성될 때 딱 한 번 그 값이 세팅되고, 영원히 변하지 않는다.
◾️ counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 만들어진다. 그리고 이 렉시컬 환경은 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조하게 된다.
◾️ 실행 흐름이 중첩 함수의 본문으로 넘어오면 count 변수가 필요한데, 먼저 자체 렉시컬 환경에서 변수를 찾는다. 익명 중첩 함수엔 지역 변수가 없기 때문에 이 렉시컬 환경은 비어있는 상황이다. (<empty>)
◾️ counter()의 렉시컬 환경이 참조하는 외부 렉시컬 환경에서 count를 찾는다.
◾️ 변수값 갱신은 변수가 저장된 렉시컬 환경에서 이루어진다.
⚠️ 클로저(closure)
◾️ 클로저는 외부 변수를 기억하고 이 외부 변수에 접근할 수 있는 함수를 의미한다.
◾️ 자바스크립트에선 모든 함수가 자연스럽게 클로저가 된다. (예외 new Function 문법)
◾️ 자바스크립트의 함수는 숨김 프로퍼티인 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억한다.
◾️ 함수 내부의 코드는 [[Environment]]를 사용해 외부 변수에 접근한다.
📝 오늘 새롭게 배운 내용 📝
✅ 변수 let은 {} 블록 범위이다.
✅ 렉시컬 환경!!! 에는 지역 변수의 내용을 저장하고 있다.
✅ 변수의 검색 범위는 점점 넓혀간다. 내부에서 외부렉시컬 환경으로..
✅ 모든 함수는 자기의 고향을 항상 기억하고 있다.
✅ 클로저 !!!! 아직도 이해가 잘 안됨 . . . . 내 안에 있는 변수가 아닌 외부 변수를 기억하고 있고...? 기억 하고 있으니깐 외부 변수에 접근을 할 수 있는 그런 함수..? 그게 가능한 이유는 함수의 숨김 프로퍼티가 자기가 어디서 만들어졌는지를 기억하기 때문...?
📌 [JAVASCRIPT_INFO 변수의 스코프]
📌 [MDN 클로저]
'모던 자바스크립트' 카테고리의 다른 글
6.5 전역 객체 (0) | 2020.08.22 |
---|---|
6.4 오래된 'var' (0) | 2020.08.21 |
6.2 나머지 매개변수와 전개 문법 (0) | 2020.08.19 |
6.1 재귀와 스택 (0) | 2020.08.15 |
4.8 객체를 원시형으로 변환하기 (0) | 2020.08.13 |