티스토리 뷰
진도가 느려진다.
이해가 안 가더라도 다시 찾아올 수 있도록 정리만 해 놓고 빠르게 넘어가자
실행 컨텍스트
c의 함수 호출 스택과 비슷
현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면 (전역 코드, eval() 함수로 실행되는 코드, 함수 안의 코드) 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다.
생성 과정
1. 활성 객체(=변수 객체) 생성
2. argument 객체 생성 (활성 객체는 arguments 프로퍼티로 이 객체 참조)
3. 스코프 체인 생성 (생성된 활성 객체가 스코프 체인 맨 앞에 추가)
4. 변수 생성 (instantiation), 값이 넘겨지지 않았으면 undefined가 할당 주의: 초기화(initialization)은 나중에 수행!
5. this 바인딩
6. 코드 실행: 함수 초기화, 연산, 다른 함수 실행 등..
함수 호이스팅
발생 이유: 생성과 초기화의 시간차 때문
함수 선언문: 생성 때 값이 할당되므로 선언문이 함수 호출 뒤에 있어도 호이스팅에 의해 호출 가능
함수 표현식: 생성 때 let x; (= undefined)만 수행되고, 함수 할당 부분 x = function () {} 은 코드 실행 단계에서 수행되므로, 선언 전에 호출하면 에러가 남
스코프 체인: 새롭게 생성된 변수 객체 + 상위 컨텍스트의 스코프 체인([[scope]] 프로퍼티) 복사
변수를 자기 스코프부터 찾으며 상위로 올라간다고 이해하면 될듯 (단 es5 기준 설명이라 함수 단위로 컨텍스트가 생성된다고 하는데, let은?)
함수 객체가 처음 생성될 때의 [[scope]]를 참조한다. 호출 기준이 아니라 생성 기준
스코프 체인으로 식별자 인식 (identifier resolution)이 이루어짐. this는 식별자가 아니라 키워드로 분류되므로 스코프 체인의 참조 없이 접근가능
with(표현식) {} : 표현식이 객체이면 현재 실행 컨텍스트의 스코프 체인 맨 앞에 추가
성능을 높이고자 하면 절대 쓰지 마라
클로저: 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수
자유 변수(free variable): 클로저로 참조되는 외부 변수
기본 틀
function outerFunc() {
let x = 1;
return function() {
/* x와 arguments를 활용한 로직 */
}
}
let newFunc = outerFunc();
//outerFunc 실행 컨텍스트가 끝났다.
newFunc(); //그럼에도 newFunc()에서 x에 접근 가능
outerFunc가 실행되면서 생성되는 변수 객체가 innerFunc()의 스코프 체인으로 참조됨
따라서 innerFunc에서 접근 가능하며, 가비지 컬렉션의 대상이 되지도 않음
단, 뒤쪽의 변수를 자주 접근하면 성능이 저하되고 메모리 부담이 많아지니까 영리하게 사용하자
활용
setTimeOut()에 함수 인자 함께 넘기기
function callLater(obj, a, b) {
return (function() {
obj["sum"] = a + b;
console.log(obj["sum"]);
});
}
let sumObj = {
sum : 0;
};
let func = callLater(sumObj, 1, 2);
//리턴되어 func에 담긴 함수는 obj, a, b에 (callLater 활성 객체에) 접근할 수 있는 클로저
setTimeOut(func, 500);
주의사항
1. 클로저의 프로퍼티값이 쓰기 가능하므로 그 값이 여러 번의 호출로 항상 변할 수 있음에 유의
2. 하나의 클로저가 여러 함수 객체의 스코프 체인에 들어가 있는 경우도 있다 (다른 함수가 클로저의 프로퍼티값을 변경할 수 있음)
3. 루프 안에서 클로저를 활용할 때는 주의하자
클로저가 루프가 끝난 후의 결과로 변화되어 있을 수 있다.
1,2,3을 1초 간격으로 출력하는 예제
//var 버전
function countSec(howMany) {
for (var i = 1; i <= howMany; i++) {
setTimeout(function() {
console.log(i);
}, i*1000);
}
};
countSec(3);
/*
출력 결과:
4
4
4
(1초 간격으로)
*/
//let 버전
function countSec(howMany) {
for (let i = 1; i <= howMany; i++) {
setTimeout(function() {
console.log(i);
}, i*1000);
}
};
countSec(3);
/*
출력 결과:
1
2
3
(1초 간격으로)
*/
let으로 하면 생명 주기가 끝나서 클로저가 아니라 복사되나?
//책 답안. 즉시 실행 함수 이용해서 i 복사
function countSec(howMany) {
for (let i = 1; i <= howMany; i++) {
(function (curI) {
setTimeout(function () {
console.log(curI);
}, curI * 1000);
})(i);
}
};
countSec(3);
/*
출력 결과:
1
2
3
(1초 간격으로)
*/
TODO: es6 공부할 때 let은 실행 컨텍스트와 어떻게 연관되는지 알아봐야겠다..
'프로그래밍언어공부 > javascript' 카테고리의 다른 글
[인사이드 자바스크립트] 5일차 (0) | 2020.08.29 |
---|---|
[인사이드 자바스크립트] 2일차 (0) | 2020.08.26 |
[인사이드 자바스크립트] 1일차 (0) | 2020.08.21 |