Weekly paper - week 3

amberim·2024년 7월 11일

이번주 Weekly paper의 주제는 3가지 였다.

  • var, let, const 의 차이점.
  • 자바스크립트에서 this 키워드의 사용과 그 특성
  • 렉시컬 스코프(Lexical Scope)의 개념과 그 특성

그런데 공부를 하면 할수록 결국 자바스크립트의 실행 컨텍스트(Execution Context)를 알아야 이해할수 있는 주제들이였다. 이번 위클리 페이퍼는 실행 컨텍스트를 대략적으로 설명하며 this와 렉시컬 스코프를 언급 하고, 마지막에 변수 키워드의 3가지의 차이점을 정리했다.
실행 컨텍스트 내부는 생각보다 일일이 열거하기에는 복잡하고 너무 많은 내부 객체들을 다뤄야해서 간략하게 정리하려 노력하였다.
(본인의 이해를 돕기위해 Figma로 아래 그림들을 그려 보았는데 이해 하는데 도움이 조금 된것같다.)

실행 컨텍스트 (Execution Context)

자바스크립트는 Single-thread로 동작하며, 하나의 메인 스레드에서 한번에 하나의 작업만 처리한다.
스크립트를 load하거나 함수를 부를때마다 새로운 실행 컨텍스트가 생성되어 Call Stack에 push 되어 쌓인다.
생성된 실행 컨텍스트는 그 문맥(context)안의 스코프와 식별자를 관리하며, 코드의 실행순서을 관리한다.

실행 컨텍스트의 종류는 크게 4가지 이지만 중요한 전역실행 컨텍스트와 함수실행 컨텍스트의 차이만 먼저 이해하면 좋을것같다.

전역 실행 컨텍스트 (Global Execuation Context): 코드가 처음 실행될때 만들어지며 오직 하나만 존재한다.
함수 실행 컨텍스트 (Function Execution Context): 함수가 호출될때 마다 새로운 실행 컨텍스트가 생성된다. 이 때 함수 내부의 코드 실행이 시작되며 실행이 끝나면 해당 컨텍스트는 사라진다.

모든 실행 컨텍스트는 크게 2가지 생성단계와 실행단계로 만들어진다.

설명전 너무 잘게 쪼개지 않는선에서 전역 실행 컨텍스트 내부를 figma로 간단히 그려보았다.
(실제로는 더 복잡하고 쪼개져있어 다 넣지 못하겠다..)

global execution context

1. 생성단계 (Creating Phase)

JS 엔진은 코드 실행전 전역 실행 컨텍스트를 생성 후 먼저 전역 스코프(중첩되지 않은)에 있는 선언문(변수, 함수, 클래스 등)을 확보된 메모리에 저장한다. 선언문의 식별자(이름)만 내부 객체의 key로 배정한다.
(전역 실행컨텍스트 생성 후 실행단계에서 함수를 만나면 또 함수 실행 컨텍스트를 만든다.)

1.1 변수객체 생성

위 사진과 같이 실행 컨텍스트에는 Variable Environment 컴포넌트와 Lexical Environment 컴포넌트가 있다. 둘은 비슷하지만 배정되는 선언문이 다르다.

전역스코프에서 var키워드로 선언된 식별자와 함수선언문은 Variable Environment 컴포넌트 안 환경레코드 (Environment record)에 저장된다. 이때 key는 식별자로, value는 undefined로 저장된다.
선언과 동시에 값이 초기화가 된다.

그러나 let 이나 const 키워드는 변수객체 환경이 아닌 렉시컬 환경(Lexical Environment) 컴포넌트 내 환경 레코드에 식별자가 key값으로 저장된다. 여기서 var와 다른점은 초기화가 되지않고 unintialized 상태로 저장된다.
선언 만 되고 초기화는 되지 않는다 .
실행단계 (Execution phase)에서 초기화 되어 값이 할당된다.

따라서 var, const, let 키워드 모두 다 호이스팅 (hoisting)이 되지만, const, let은 실행 전까진 값이 초기화가 되지 않기 때문에 호이스팅이 되지 않은것 처럼 동작한다.

Hoisting이란? 이렇게 코드를 실행하기전에 변수나 함수 선언문을 먼저 끌어올려 메모리에 저장하는 과정을, 끌어올림, 호이스팅이라 한다.

1.2 스코프 체인 (Scope Chain) 생성

먼저 여기서 스코프(scope)란?
식별자가 유효한 범위로, 자바스크립트는 렉시컬 스코프 (Lexical Scope) 또는 정적 스코프(Static Scope)로 변수나 함수가 어디서 '정의' 되었는지에 따라 스코프를 결정한다.
동적 스코프 (Dynamic Scope)는 함수가 호출된 위치에 따라 변수의 스코프가 동적으로 결정된다.

스코프는 전역 스코프 (Global Scope), 함수 스코프 (Function Scope), 블록 레벨 스코프 (Block Scope)로 나뉜다. 말 그대로 전역 스코프는 스크립트 전체 내에서 유효하고, 함수 스코프는 함수내부, 블록 스코프는은 {} 안에서만 유효한 범위이다( if, while, for, etc.).

scope-level

(편의상 Variable Environment와 Lexical Environment 컴포넌트를 렉시컬 환경이라 칭하겠다)
렉시컬 환경은 외부환경 참조(Outer Lexical Environment Reference)에 상위 렉시컬 환경을 가리키는 상위 스코프에 대한 참조를 기록한다. 이는 단방향으로 연결돼 스코프 체인을 만든다.

 렉시컬 스코프이기 때문에 스코프는 부모 자식 계층적 구조를 가지게 되며 외부에서는 내부 변수를 참조를 못하지만 내부에서는 상위 부모 스코프 참조를 타고 올라가 외부 변수에 접근을 가능하게 한다.

자바스크립트에서의 스코프는 운영체제에서 디렉토리 같은것이다. 디렉토리도 계층적 구조를 가져, 한 디렉토리안에서 같은 파일 이름을 중복해서 쓸 수 없지만, 중복된 이름이라도 각 다른 디렉토리, 파일의 상위 폴더가 다르면동일한 이름을 쓸 수 있다.

1.3 this binding

스코프 체인 생성이 끝나면, this 값의 바인딩을 정한다.
this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수로 , this를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 매서드를 참조할수있다.

this가 가르키는, 바인딩되는 값은 함수 호출 방식에 의해 '동적'으로 결정된다.

렉시컬 스코프는 함수정의가 평가되어 생성되는 시점에 상위 스코프를 결정한다. 하지만 this바인딩은 함수 호출 시점에 결정된다.

this 키워드는 현재 실행 컨텍스트의 스코프를 참조하며, 함수를 호출하는 방식에따라 바인딩되는 값이 달라진다

  • 전역 실행 컨텍스트: 전역 객체. 브라우저 환경에서 'window'
  • 일반 함수 호출: 전역 객체. 브라우저 환경에서 'window'
  • 매서드 호출: 매소드를 호출한 객체
  • 생성자 함수: 생성자 함수가 미래에 생성할 인스턴스
  • Function.prototype.apply/ call / bind: 매서드에 첫번째 인수로 전달한 객체

실행단계 (Execution phase)

생성단계가 끝나면 JS 엔진은 다시 스캔을 하며 변수객체의 값을 할당하며 코드를 실행한다.
첫번째로 생성되는 전역 실행 컨텍스트는 Call stack에 push 되고 코드가 한줄 한줄 실행됨에 따라 미리 저장했던 선언문에 값을 할당하기 시작한다.
이때 전역 스코프에 있는 let, const의 uninitailzed값이 초기화되고 값이 할당된다.
함수 호출 코드를 만나면 함수 실행 컨텍스트를 생성, 실행단계를 거친다.
Lexical Environment 안 환경 레코드에 함수내부 변수선언, 파라미터 저장, 외부환경 참조에 부모스코프를 저장하는 생성 단계가 끝나면 Call Stack에 함수 실행 컨텍스트가 push된다.
push : Call Stack 맨 위로 올라감.
Stack의 자료구조 특징인 First in Fisrt Out을 따라 제일 위에 있는 실행 컨텍스트가 현재 실행 컨텍스트이며, 현재 실행 컨텍스트인 함수 내부 코드를 실행한다. 이때 초기화가 필요한 값은 초기화 및 할당을 하며, 내부에 식별자가 내부에 없는 경우, 식별자 검색을 해 스코프 체인을 따라 상위 스코프로 타고 올라가 식별자를 찾는다.
함수 내부의 코드의 실행이 끝나면 해당 함수 실행 컨텍스트는 Call Stack에서 pop되어 사라지고, 그 다음 아래 실행 컨텍스트가 실행된다. 결국 제일 먼저 생성되는 전역 실행 콘텍스트가 맨 아래 있게 되며 코드가 모두 끝날때까지 사라지지 않는다.


var, let, const의 차이점

var, let, const 키워드는 모두 변수를 선언할때 쓰이며, 원래는 var키워드만 존재했진만, let, const가 ES6 이후에 소개되었다.

var 키워드

  • 변수 이름 중복 선언 가능

var fruit = 'apple';
var fruit = 'pear';

이렇게 덮어져도 오류가 나지않아 코드실수 하기가 쉽다.

  • 값을 재할당 가능

  • 함수 레벨의 스코프


var fruit = 'grape';

// 함수 내부 변수
function fruits(){
  var fruit = 'apple';
}

// 블록레벨 안에서 선언해도 글로벌 변수로 됨
if(true){
  var fruit = 'pear';
}

console.log(fruit); // pear

var 키워드는 함수레벨의 스코프를 가져, 블록 레벨에서 선언된 var 변수는 전역변수가 된다. 전역변수에 같은 식별자가 있다면 값을 재할당하는 실수가 발생할수 있다.

  • 변수 호이스팅이 되며, 값이 undefined로 초기화된다.

// 호이스팅
console.log(fruit) // undefined

fruit = 'apple';
// 값 할당
console.log(fruit) // apple

// 앞서 말했듯이 var 선언문은 코드 실행전 먼저 끌어올려져 초기화된다.
var fruit;

이렇게 값을 할당전에 참조 할 수 있다. 할단전 값은 undefined;

let키워드

  • 변수 이름 중복 선언 X
  • 값을 재할당 가능
  • 블록 레벨 스코프
  • 호이스팅이 되지 않는것 처럼 동작. (호이스팅 됨)

console.log(fruit) // 참조 에러남.

let fruit // 변수 선언문, 이제 초기화.

var와 다르게 letconst는 값이 변수 선언문에서 초기화가 되게 때문에 이 이전에 참조를 하면 참조 에러가 발생한다. 이 참조 할 수 없는 구간을 일시적 사각지대 (TDZ: Temporal Dead Zone)이라 부른다.

const 키워드

  • 주로 변하지 않는 값인 상수를 선언하기 위해 사용
    (대문자와 언더바 스네이트로 이름을 표기함)

const HOURLY_RATE = '10000';
  • 변수 이름 중복 선언 X
  • 값을 재할당 X
    (그러나 객체를 할당할 경우, 객체의 값을 변경해 다른 값을 줄 수 도 있다.)
  • 블록 레벨 스코프
  • 호이스팅이 되지 않는것 처럼 동작. (호이스팅 됨).

요약

  • var 키워드를 사용하지 않는다.
  • 주로 const를 쓰며 재할당이 필요한 경우에 let을 써도 늦지 않다.
  • 스코프를 최대한 좁게 사용한다.

Reference

0개의 댓글