Chapter 6 - 함수 심화학습
Advance JS Function
6.3 변수의 유효범위와 Closure
JS: 함수지향 언어 => 유동적 (동적으로 생성, 인수로 넘기기, 생성 외 곳에서 함수 호출 가능)
let
,const
(modern 방식)으로 진행 (var
: 과거의 잔재)
- 코드블록 내에서 정의 => 그 안에서만 사용 가능 (밖에서는
ReferenceError
)중첩 함수
- 함수 안의 함수
- 새로운 객체의 프로퍼티 형태나 중첩 함수 그 자체로 반환 가능
- 그래도 외부 변수 접근 가능
function makeCounter() {
let count = 0;
return function() {
return count++;
}
}
makeCounter
내 중첩함수 실행let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1 alert( counter() ); // 2
- \(Q\): 각
counter()
독립적인지,count
변수에 어떤 값이 할당되는지
- \(Q\): 각
렉시컬 환경
단계 1. 변수
- Lexical Environment: 내부숨김연관객체 (internal hidden associated obj) 로서 JS에서 실행 중인 함수, 코드블록, 스크립트 전체에 존재
- 구성:
- 환경 레코드 (Environment Record): 모든 지역 변수를 Property로 저장하는 객체 (
this
)와 같은… - 외부 렉시컬 환경 (Outer Lexical Environment) 에 대한 참조
- 환경 레코드 (Environment Record): 모든 지역 변수를 Property로 저장하는 객체 (
- 구성:
- 변수:
환경레코드의 프로퍼티 변수 GET/Modify:
환경 레코드의 프로퍼티를 가져오거나 변경 - 전역 렉시컬 환경: 스크립트 전체와 관련된 렉시컬 환경
// lexical environment let phrase = "Hello"; // ---- | phrase: "Hello" | ---(outer)--> null alert(phrase)
| box |
: 저장되는 환경 레코드-->
: 외부 렉시컬 환경에 대한 참조null
인 이유: 전역 렉시컬 환경은 외부 참조 X
- 코드 실행, 이어져 나가면 렉시컬 환경은 변화함
- 스크립트 시작 : (pre-populate) 스크립트 내 선언된 변수 전체가 lexcial 환경에 올라감
execution start ------- | phrase: <uninitialized> | ---(outer)--> null
uninitialized
: JS engine이 인지하긴 하지만let
전까지 참조 불가능
- 변수에게
let
(할당 전이기에undefined
), 여기부터 사용 가능let phrase; ------- | phrase: undefined |
phrase
에 값 할당phrase = "Hello"; ------- | phrase: "Hello" |
phrase
값 변경phrase = "Bye"; ------- | phrase: "Bye" |
Lexical Environment (명세서에만 존재) only used to explain JS, 이론상의 객체이다
- 직접 렉시컬 환경 얻기/조작 불가능
- JS Engine들이 렉시컬 환경을 최적화 (rmv unused var…etc)
- 스크립트 시작 : (pre-populate) 스크립트 내 선언된 변수 전체가 lexcial 환경에 올라감
단계 2. 함수 선언문
- 값 임 (like variables)
- Function declaration 으로 선언한 함수 (unlike variables)
- 바로 초기화됨
- 렉시컬 환경 만들어지자마자 사용 가능 (선언되기도 전에!)
- 스크립트 시작
execution start ------- | phrase: <uninitialized>; example: function | ---(outer)--> null
phrase
값 선언, 함수 선언문으로example
fn 선언let phrase = "Hello"; ------- | phrase: "Hello" | function example(name){ alert(...); }
- 스크립트 시작
- function expression으로 선언된건 안됨 (
js let example = function(name)...
)
단계 3. 내부와 외부 렉시컬 환경
변수 접근: 내부 렉시컬 함수에서 먼저 찾기 -> 없으면 참조된 외부 렉시컬로 범위를 넓힘 (전역 렉시컬 환경 확장까지 반복)
전역렉시컬 환경에서도 변수 못 찾으면 에러 발생 (in 엄격 모드)
- 비엄격 모드에서는 새로운 전역변수가 만들어진다 (하위호환 위한 기능)
단계 4. 함수를 반환하는 함수
- 각 함수는
[[Environment]]
라는 숨김 property 존재- 함수가 만들어진 곳의 렉시컬 환경에 대한 참조 저장
- => 호출 장소와 무관하게 출생지 기억할 수 있는 이유 (변하지 않음)
counter
을 찾았으니 해당 단계에서++
해줘야 함변숫값 갱신 = 변수 저장된 렉시컬 환경에서 이루어짐
counter()
을 여러번 호출하면count
가 증가하는 이유
Closure
외부 변수를 기억하고 이 외부 변수에 접근 할 수 있는 함수
JS에서는 모든 함수가 자연스럽게 클로저가 됨 (다른 언어들은 비교적 어려움)
new Function: 예외 정리: 함수 본문에서는 사용해야 할 변수가 외부에 있으면 해당 변수의
[[Environment]]
property를 사용해 접근함
Garbage Collection
- 함수 호출 끝나면 관련 렉시컬 함수 메모리에서 제거 (호출 완료 시 변수 참조 불가능한 이유)
JS의 모든 객체는 도달 가능한 상태일 때만 메모리에 유지
- 호출 끝나도 도달 가능한 중첩함수 (
[[Environment]]
에 외부 함수 렉시컬환경 정보 저장됨 => 도달 가능 상태!)
function f() {
let value = 123;
return function() {
alert(value);
}
}
let g = f();
g.[[Environment]]
에f()
호출 시 만들어지는 렉시컬 환경 정보 저장- 유의점:
f()
여러번 호출 & 저장 => 각 렉시컬 환경 모두 메모리에 유지됨let arr = [f(), f(), f()]; // => 각 f() 호출할때 저장됨
- 렉시컬 환경 객체 : (like other obj) 도달 불가능 시 메모리에서 삭제
- 해당 객체를 참조하는 중첩함수 하나라도 있으면 안 사라짐
- (선) 중첩함수 메모리에서 삭제 (후) 감싸는 렉시컬 환경 (and
value
) 제거let g = f(); // g 존재 시 연관 렉시컬 환경 다 메모리에 존재 g = null; // 도달 불가능 상태 => 메모리에서 삭제
최적화 프로세스
- 이론:
if
함수 alivethen
외부 변수 역시 메모리에 유지 - 실제: JS Engine keeps OPTIMIZING (변수 사용 분석, 미사용 판단 => 메모리에서 제거)
- 때문에 디버깅 시 이론상 도달가능하지만
ReferenceError
뜰 때가 많다- 자세한 예시는 원본페이지에서 확인
- 때문에 디버깅 시 이론상 도달가능하지만
6.5 전역 개체
- 언제 어디서나 사용가능한 변수/함수 생성 가능 (언어 자체, 호스트 환경에 기본 내장되어 있는 경우 많음)
-
ex: Array, window.innerHeight... - 브라우저 환경:
window
, Nodejs 환경:global
, 요즘 통일 트렌드:globalThis
// ex: in window: both are SAME alert("Hello"); window.alert("Hello");
-
var
로 선언한 전역함수/변수 => 전역 객체의 property 등극var gVar = 5; alert(window.gVar);
- 단, 하위호환성 떄문에 동작하지만 권장 X (Modern JS style아님)
let
사용시 전역객체 통해 접근 불가능let gLet = 5; alert(window.gLet); // undefined
- 꼭 만들어야 한다면..중요 변수 권장 전역처리 방법
window.currentUser = { name: "John" }
- 모든 스크립트에서 아래처럼 접근 가능
alert(currentUser.name) // John
- 지역변수
currentUser
이미 존재하지만 전역객체꺼 쓰고 싶다면:alert(window.currentUser.name) // John
- 모든 스크립트에서 아래처럼 접근 가능
- 전역 변수 되도록 사용하지 않는 것이 좋음 (input output 명확한 함수들이 테스트, 에러 방지에 좋다)
폴리필 사용하기
- 직접 함수를 만들어 전역객체에 추가하는 방식
- 예: 최신 브라우저는
Promise
를 지원하지만 구식 브라우저에서는 폴리필을 만들어야 함if(!window.Promise){ // Promise 지원 안하면 window.Promise = ... // 직접 구현 }
6.6 객체로서의 함수와 기명 함수 표현식
- JS에서 함수 = 값,
type: Object
name property
function sayHi() { alert("Hi"); }
alert(sayHi.name) // sayHi
// 익명함수
let sayHi = function() { alert("Hi"); }
alert(sayHi.name) // sayHi
// 기본값 사용 이름할당
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi
}
f();
Contextual Name: JS 명세, 이름 없는 함수의 이름을 지정할떄 이 context에서 이름을 가져옴
단, 객체 메서드는 익명 시 함수처럼 자동 할당 X
- name property에 빈 문자열 저장됨
length property
- 함수 매개변수의 개수 반환 (나머지
...other
은 미포함) length
로 다른 함수 안에서 동작하는 함수의 타입 검사로 사용 가능aka polymorphism (다형성) : 인수 종류에 따라 인수를 다르게 처리하는 방식 ```js function ask(question, …handlers) { let isYes = confirm(question);
for(let handler of handlers) { if (handler.length == 0) { if (isYes) handler(); } else { handler(isYes); } }
}
// 사용자가 OK를 클릭한 경우, 핸들러 두 개를 모두 호출함 // 사용자가 Cancel을 클릭한 경우, 두 번째 핸들러만 호출함 ask(“질문 있으신가요?”, () => alert(‘OK를 선택하셨습니다.’), result => alert(result)); ```
Custom property
- property는 변수가 아님
- 아래
sayHi.counter
은 변수가 아니라 property 이다 ```js function sayHi() { alert(“Hi”);
// 함수를 몇 번 호출했는지 세봅시다. sayHi.counter++; } sayHi.counter = 0; // 초깃값
- 아래
sayHi(); // Hi sayHi(); // Hi
alert( 호출 횟수: ${sayHi.counter}회
); // 호출 횟수: 2회
- 클로저 대신 함수 프로퍼티 사용하기:
```js
function makeCounter() {
// let count = 0 대신 아래 메서드(프로퍼티)를 사용함
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
count
외부 렉시컬 환경 X, 함수 프로퍼티에 바로 저장됨- 차이점:
- 클로저 사용 시: 외부 코드에서
count
접근 불가 - 함수 프로퍼티 사용 시:
count
를 함수에 바인딩, 외부에서 값 수정 가능function makeCounter() { function counter() { return counter.count++; }; counter.count = 0; return counter; } let counter = makeCounter(); counter.count = 10; alert( counter() ); // 10
- => 목적에 따라 선택해 사용하기
- 클로저 사용 시: 외부 코드에서
기명 함수 표현식
NFE (Named Function Expression): 이름이 있는 함수 표현식