Skip to content

Latest commit

 

History

History
124 lines (106 loc) · 11.6 KB

20210924.md

File metadata and controls

124 lines (106 loc) · 11.6 KB

Javascript

인상 깊은 조언

  • 일할 때 잘하려면 배우는 지금 나의 무식함이 드러나고 지적받아야 한다. 창피해하지 말자
  • 선배에게 잘 보이려고 괜히 빨리 끝내겠다고 하지 말고, 여유로운 시간 마감을 가지고 잘 해서 결과물로 잘 보여라.
  • 데드라인에 대해서 코멘트할때는 근거를 가지고 설명해라(얼마나 걸릴지 수치로 얘기하고, 거의 다 됐습니다 라고 하지 말 것)

배운 것들

실행 컨텍스트의 역할

  • 표현식을 평가하면 값이 만들어지듯, 소스코드를 평가하면 실행 컨텍스트가 만들어진다.
  • 실행 컨텍스트는 4가지 소스코드가 평가될 때 만들어진다.
    • 전역 코드
    • 함수 코드: 함수 몸체 안의 코드를 의미하며, 호출하기 전까지는 평가도, 실행도 되지 않는다. 객체로만 존재하다 호출되어야 평가 시작
    • eval 코드: 문자열로 된 소스코드를 인수로 전달
    • 모듈 코드: 자신의 실행컨텍스트와 스코프를 만든다.
  • 소스코드 평가 단계
    • 평가되면 선언문들만 실행(호이스팅)되어 식별자로 등록된다.
    • 선언문의 특징은 식별자를 꼭 갖는다는 것이다. 그래야 스코프에 key가 등록되니까.
    • 어디에 등록되는가? 등록되려면 자료구조가 있어야 한다. 이 자료구조가 바로 실행 컨텍스트
  • 소스코드 실행 단계
    • 선언문 제외한 코드가 한줄씩 실행된다.
    • 할당문이나 참조하는 순간 식별자를 갖는 객체에 들어가 값을 변경하거나 가져온다.
  • 즉 실행 컨텍스트는 소스코드가 평가되고 실행되는 중 식별자를 관리하며, 다른 언어에도 이름만 다를 뿐(symbol table) 존재한다.

실행 컨텍스트의 동작 과정

var x;
x = 1;
  • 위와 같은 전역컨텍스트가 있다고 할 때, 소스코드가 로드되면 전역코드의 평가가 시작된다.
  • 전역실행컨텍스트를 생성하고, 실행컨텍스트 스택에 이를 push한다.
    • 선언문을 실행하여 var 키워드로 선언된 x라는 식별자를 실행컨텍스트에 등록하되, 값은 undefined로.
  • 전역코드의 실행 단계(런타임)에서는 선언문은 건너뛰고 할당문만 실행한다.
    • 현재 실행중인(최상단) 실행컨텍스트에서 선언되었는지 식별자를 찾고, 만약 선언문이 종점스코프에까지 없다면 암묵적 전역으로 전역객체에 프로퍼티를 추가한다.
    • x에 할당될 숫자 리터럴 1은 식별자 x를 찾은 후 값으로 만들어져 할당된다.
const x = 1;
const y = 2;

function foo(a) {
  const x = 10;
  const y = 20;

  console.log(a + x + y); // 130
}

foo(100);

console.log(x + y); // 3
  • 소스코드 로드하며 전역코드 평가 시작, 전역 실행 컨텍스트 생성 및 실행컨텍스트에 push
    • AST 트리자료구조로 소스코드를 parsing한 후 자료구조를 싹 한 번 돌면서(traverse) 선언문만 발라내 선두에 있는 선언문부터 실행하여 아까 만들어진 전역실행컨텍스트의 렉시컬환경에 식별자를 넣는다.
    • const로 선언된 식별자는 우리가 알 수 없는 값(ECMAScript에 명시되지 않음)으로 초기화가 된 후, 할당 이전에 참조하면 에러를 일으키도록 TDZ 처리된다.
    • function 선언문이 실행될 때, 함수이름을 식별자로 하는 키에 함수객체가 만들어져 binding된다. 이 때 함수객체가 갖는 프로퍼티와 내부슬롯 [[Environment]]이 세팅된다. 런타임 이전에 함수객체가 생성되기 때문에 함수 호이스팅이 일어나는 것이다.
  • 전역코드의 실행단계에 들어가 할당문을 실행하고, 함수를 호출한다.
    • 함수를 호출할 때 인수로 들어가는 숫자 리터럴 100은 값으로 평가되어 메모리 어딘가에 저장된 후 전달된다.
  • 함수코드의 평가 시작, 함수실행컨텍스트를 생성 후 실행컨텍스트 스택에 push하며 현재 실행중인 실행컨텍스트가 된다.
    • 매개변수와 지역 변수, 함수선언문이 실행되어 함수 실행컨텍스트의 렉시컬환경에 식별자가 등록된다
    • foo 함수 코드가 실행되며 할당문이 실행되고, console.log를 만나 console이라는 식별자를 현재 실행중인 실행컨텍스트에서 찾는다.
    • 없으면 상위 스코프인 전역 실행컨텍스트로 옮겨가 찾는데, console은 전역 실행컨텍스트의 렉시컬환경의 객체환경레코드에서 찾을 수 있다.
      • 객체환경레코드는 bindingObject라는 하나의 프로퍼티만 갖고 있는 객체로, 이 프로퍼티의 값은 전역객체를 가리킨다.
    • console 객체의 메서드인 log를 찾아 인수로 넘어온 표현식을 평가한 값을 전달하며 호출한다.
      • 이 때, console.log도 함수이니 당연히 실행컨텍스트를 만들어 실행한 후 함수 종료와 함께 실행컨텍스트 스택에서 pop된다.
  • foo 함수의 모든 실행문이 끝났으므로 종료되면서 실행컨텍스트 스택에서 pop된다.
  • 전역 실행 컨텍스트로 돌아와 console.log에서 전역변수를 넘기며 함수를 호출한 뒤 전역 실행 컨텍스트도 실행 컨텍스트 스택에서 pop

전역 vs. 함수 실행 컨텍스트

  • 실행 컨텍스트에는 기본적으로 두개(Lexical Environment, Variable Environment)의 component가 있다.
    • 생성 초기에는 둘 다 같은 것을 가리키지만 with문 등에 의해 나중엔 다른 것을 가리킨다. 그러나 우리가 거기까지 알 필요는 없으니 하나라고 생각하자.
  • 전역 실행 컨텍스트는 전역 렉시컬 환경 객체를 가리킨다. 이 객체는 전역 환경레코드와 외부 렉시컬 환경에 대한 참조를 프로퍼티로 갖는다.
    • 전역환경레코드는 특별히 객체환경레코드와 선언적 환경레코드, GlobalThis를 프로퍼티로 갖는다.
      • 객체환경레코드의 유일한 프로퍼티인 BindingObject에는 전역객체가 바인딩되어있다.
      • 선언적 환경레코드에는 let, const로 선언된 식별자가 등록된다.
      • GlobalThis 또한 전역객체를 가리킨다.
    • 외부 렉시컬 환경에 대한 참조는 전역의 경우 null 값을 갖는다.
  • 함수 실행 컨텍스트도 렉시컬 환경이라는 객체를 가리킨다. 이 객체 또한 환경레코드와 외부 렉시컬 환경에 대한 참조, this를 프로퍼티로 갖는다.
    • 환경레코드에는 함수의 매개변수, arguments, 함수 몸체에 선언된 지역변수와 함수가 식별자로 등록된다.
    • 함수객체가 생성될 때 세팅된 [[Environment]] 내부슬롯에 바인딩된 렉시컬 환경이 함수의 외부 렉시컬환경에 대한 참조 값으로 들어간다.
    • this는 호출시 동적으로 결정된다.

전역객체

  • 전역객체는 가장 먼저, 전역 코드 평가 이전에 생성된다.
  • 전역코드가 실행되기 이전의 태초와, 전역코드 실행 종료시 전역 렉시컬 환경의 객체환경레코드(BindingObject)도, GlobalThis가 사라졌을 때에는 아무도 전역객체를 가리키지 않는데, 그럼 garbage collector의 대상이 되는 것인가?
    • 전역객체는 예외이다. 태초부터 끝까지 살아있어야만 한다.
  • 전역코드가 종료되고 전역객체가 살아있지 않다면, 브라우저에 걸어둔 이벤트 핸들러는 다 전역객체에 있는데 못쓰게 된다.

블록문의 렉시컬 환경

  • 소스코드 4가지(전역, 함수, 모듈, eval)는 왜 구분되어 있을까?
    • 이들이 실행컨텍스트를 만드는 특별한 케이스들이기 때문에.
  • var로 선언된 변수는 함수레벨 스코프를 갖지만, let, const로 선언되는 변수는 블록레벨 스코프를 갖는다.
    • if문 안에 let이나 const로 변수를 선언하면, 블록 렉시컬 환경이 생성되고 현재 실행중인 실행컨텍스트의 Lexical Environment는 이 블록 렉시컬 환경을 가리킨다.
    • 블록 렉시컬 환경의 외부 렉시컬 환경 참조는 현재 실행중인 실행컨텍스트가 가리키던 Lexical Environment 객체를 가리키고 있으며, 블록문이 종료되면 이를 통해 현재 실행중인 실행컨텍스트의 Lexical Environment가 원래대로 돌아온다.
  • 블록문은 실행컨텍스트를 만들지 않는다. 렉시컬 환경만을 만들 뿐이다.
  • for문을 돌리면 렉시컬 환경이 여러개 생기므로 나누어 돌려야 한다.

클로저

  • 함수형 프로그래밍 언어라면 클로저를 다 지원한다.
    • 함수형 언어에서, 함수는 객체이다. 그렇기 때문에 프로퍼티를 가질 수 있으며, 이를 통해 상위 스코프 저장이 가능하므로 클로저가 기본으로 제공된다.
  • 클로저는 함수와 그 함수가 선언된 렉시컬 환경(=상위 스코프)의 조합이다.
  • 전역에서 함수 객체가 만들어질 때는, 전역 실행컨텍스트가 현재 실행중인 실행 컨텍스트일 때이다.
    • 그 때의 렉시컬환경은 즉 함수가 선언된 위치의 렉시컬환경이며 함수는 이를 상위스코프로 삼는다.
    • 클로저는 이렇듯 자신의 스코프와 함께 상위 스코프를 갖는 함수이다.
    • 그러나 이 의미라면 자바스크립트의 모든 함수는 클로저이다. 왜냐면 모든 함수는 내부슬롯 [[ Environment ]]를 가지기 때문이다.
  • 일반적인 의미의 클로저는 그러나 그보다 더 좁은 의미를 갖는다.
    • 외부함수의 반환값으로 내부함수가 리턴된다는 전제 (내부 함수가 외부 함수보다 오래 살아남는 경우) 아래, 내부함수가 외부함수에서 선언된 식별자를 바라보는 경우
    • 내부함수가 외부함수가 갖는 식별자를 바라보지 않는 경우, 외부함수의 렉시컬 환경을 참조하고 있다고 하더라도 자바스크립트 엔진이 최적화를 위해 다 날려버리기 때문에 클로저라고 하지 않는다.
    • 클로저인 경우에도 모던 브라우저에서는 내부함수가 참조하지 않는 식별자를 날려버린다.
  • 디버거에서 말하는 특정 함수의 Closure는 그 함수가 바라보는 외부함수의 렉시컬환경, 즉 상위스코프를 말하며 이 때 참조하고 있는 식별자를 보여준다.

클로저의 활용

  • 상태를 안전하게 변경하고 유지하기 위해 사용한다.
  • 전역에 선언된 num 변수를 increase라는 함수로 조작하는 경우, 전역변수라는 것 외에도 외부 환경에 의존하며 부수효과를 낳는다는 점에서 바람직하지 않다.
  • 그렇다고 객체에 넣어 increase 메서드로 조작한다고 하더라도 객체는 public이기 때문에 외부에서 조작이 가능하다.
  • increase라는 함수를 통해서만 num을 변경할 수 있도록 할 때, num을 조작하는 내부함수를 선언하며 동시에 지역변수로 num을 선언 및 할당하는 외부함수를 즉시실행함수로 만든다. 내부함수를 리턴하는 즉시실행함수 실행의 표현식을 increase 변수에 할당한다.
    • 두개 이상의 작업을 하게 하고 싶으면, 함수를 두개 이상 선언하고 해당 함수를 객체에 담아 전달하며 메서드로 호출한다.
    • increase의 상위스코프는 즉시실행함수이며, 즉시실행함수의 몸체에 선언된 num은 increase 외에는 접근하지 못하므로 안전하게 유지된다.

느낀 점

  • 몇 번의 시도 끝에 실행컨텍스트와 클로저 드디어 완벽히 이해했다. 다시 수업듣길 잘했다.