[내일배움캠프 TIL] 22일차

Jaehyeon Ye·2022년 11월 29일
0

오늘 새로 배운 것

변수 영역에 바로 데이터를 저장하지 않는 이유 2가지

1) 예를 들어 변수에 담긴 문자열 데이터 길이가 길어질 경우 데이터 공간을 더 확보하기 위해 변수와 상관없는 다른 데이터들이 뒤로 밀리게 될 수도 있는데 이렇게 메모리를 운용하는 것은 비효율적이다.
2) 변수 영역에 데이터를 직접 담으면 예를 들어 8byte 공간을 차지하는 숫자가 담긴다면 같은 데이터를 담은 변수를 여러번 저장할 때마다 8byte X 변수 개수의 메모리 공간을 차지하게 되는데 변수에 직접 데이터를 저장하지 않고 원래대로 데이터의 주소를 저장하면 같은 데이터를 가진 여러 변수를 저장하더라도 그 데이터를 가진 주소값만 가리키면 되기 때문에 메모리 운용에 있어서 더 효율적이다.

가비지 컬렉팅(garbage collecting)

변수가 가진 데이터가 변경될 때 그 변수에 데이터의 주소값이 변경됨으로 이전 주소의 데이터는 사용되지 않기 때문에 garbage collector의 수거 대상이 된다.

변수의 복사

결론부터 말하면 기본형 데이터의 경우 복사 이후 복사본의 데이터 값을 변경하면 복사당하는 원본과 복사본은 서로 달라진다. 복사본에 담긴 새로운 데이터 값의 주소가 원본에 담긴 데이터 값의 주소와 다르기 때문이다.

하지만 참조형 데이터의 경우는 복사본의 프로퍼티 데이터를 변경해도 원본과 복사본이 같다. 즉, 복사본의 프로퍼티 데이터 값을 새로 지정하면 원본도 함께 달라진다는 말이다. 프로퍼티 변수에 담긴 데이터 값 주소는 달라지지만 그 변수들을 가진 복사본 객체의 참조하는 주소가 변함없기 때문에 원본도 그 프로퍼티의 달라진 주소값을 가리키므로 이런 현상이 발생한다.

그래서 참조형 데이터의 원본과 복사본이 기본형 데이터처럼 달라지려면 프로퍼티로 접근할 것이 아니라 객체 자체를 새롭게 할당해주어야한다.

참조형 데이터 중 새 객체를 생성하는 함수형 데이터의 경우도 마찬가지이다. 원본과 다른 새 객체를 만들기 위해서는 위에 서술한 것처럼 프로퍼티로 접근한 뒤 새 객체를 return할 것이 아니라 아예 새로운 객체 자체를 return해주도록 해야한다. 그런데 여기서 고려해야할 점이 있다. 새 객체를 아예 return하기 위해 프로퍼티를 새로 나열하는 것은 비효율적이다.

그래서 새 같은 프로퍼티를 가지지만 원본과 다른 새 객체를 생성하는 방법으로 일단, for문을 돌려 새로운 프로퍼티들을 생성한 빈 객체에 넣어주고 그 객체를 return해주는 방법이 있다. 객체의 형태는 새 객체와 원본이 같지만 서로 다른 객체이므로 원하는 목적을 달성하는 듯하다. 하지만 여기도 문제가 있는 것이 프로퍼티로 참조형 객체를 가지는 경우이다. 다시 말하면 프로퍼티가 그 안에 또 프로퍼티를 가지기 때문에 1차적으로는 새로운 객체가 나오는 것이지만 그 새로운 객체 안에 프로퍼티가 가지는 객체의 복사본에 새로운 데이터값이 할당되면 위에 서술한 것처럼 그 프로퍼티의 원본 객체가 복사본 객체를 따라 변경되는 문제가 생길 것으로 이해, 추측된다. 그래서 이러한 중첩형 객체를 가진 완전 새로운 객체를 생성하기 위해서는 중첩 for문을 사용해야할 것이므로 좋지 않은 방법이다. 그래서 이보다는 프로퍼티 값이 객체일 경우 for문을 돌려주는 재귀적인 구조로 코드를 구현하는 것이 더 나아보이는 방법이다. 근데 이 방법보다 더 간단한 방법이 있는데 object 타입으로 들어오면 JSON으로 묶어주고 다시 object로 바꿔주는 방법을 통해 새 객체를 생성할 수 있다.

얕은 복사와 깊은 복사

바로 위의 내용으로부터 나온 개념이 내가 대강 들어서 이런 게 있다고 알고만 있었던 얕은 복사와 깊은 복사의 개념이었다. '얕은 복사'는 '바로 아래 단계의 값만 복사'한다. '깊은 복사'는 내부의 모든 값들을 다 찾아서 복사하는 방법이다.

undefined 와 null

둘다 '없음'을 의미한다.

undefined

변수에 값이 지정되지 않은 경우
object의 하위요소로 접근할 때 해당 데이터가 존재하지 않는 경우
변수에 함수를 할당했는데 함수에서 리턴하는 값이 없는 경우

null

'없음'을 명시적으로 표현할 때

실행 컨텍스트

Context의 사전적 정의 : 어떤 일의 맥락, 글의 문맥

실행할 코드에 제공할 환경 정보들을 모아놓은 객체
JS는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리는 호이스팅(hoisting), 외부 환경 정보 구성, this 값 설정을 한다.

생성(활성화) 시점 - 한 실행 컨텍스트가 콜 스택 맨 위에 쌓이는 순간
생성 시점에 JS 엔진은 해당 컨텍스트에 관련된 코드를 실행하는데 필요한 환경 정보를 수집해서 실행 컨텍스트 객체에 저장

콜 스택(Call stack)

스택 자료구조 순서대로 코드 실행

실행컨텍스트에 담기는 정보 - VE / LE / ThisBinding

스냅샷(snapshot) : 어떤 데이터의 특정 시점의 이력을 보관하기 위해서 그 순간을 찍어놓는 것

VaribleEnvironment : 현재 컨텍스트 내의 식별자 정보(예: var a = 3), 외부 환경 정보(Outer), 선언 시점 LexicalEnvironment의 스냅샷
선언 시점에서는 VE나 LE나 똑같다. 차이는 VariableEnvironment는 스냅샷 유지, LexicalEnvironment은 선언 시점에 내용이 정해지지만 변경사항이 실시간으로 반영. 스냅샷 유지X,

ThisBinding : this가 의미하는 것을 결정해주는 것

실행 컨텍스트 생성할 때 VE에 내용을 먼저 담고 이 내용을 그대로 LE에 복사해서 이후에는 값이 변화되는 LE를 사용

VE, LE 구성요소 모두 environmentRecord(record)와 outerEnvironmentReference(outer)로 구성되어있음

LexicalEnvironment(1) - environmentRecord와 호이스팅

현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집)
컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집

LexicalEnvironment(2) - 스코프, 스코프 체인, outer

스코프(Scope)

식별자에 대한 유효범위(변수는 어디까지 유효한가?)

스코프 체인

식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것

콜 스택에 쌓인 각 컨텍스트마다 lexical을 가지고 있는데 그 lexical안에는 record와 outer가 있다. 이제 만약 최상단 실행 컨텍스트 내부에서 변수를 찾을 수 없다면 바로 밑 컨텍스트의 record에서 찾고 없으면 또 바로 밑의 실행 컨텍스트에서 물어보고... 전역 컨텍스트의 record까지 물어보고 그래도 못 찾는다면 다시 자기 lexical환경 안의 outer에 물어본다. 그리고 없으면 outer에 적시된 다음 실행 컨텍스트 lexical환경에 접근해서 찾아보고 그래도 없으면 그 다음 lexical에 접근해서 찾아보고... 이런 식으로 가는 것을 스코프 체인이라고 한다

하루를 돌아보며...

이전에 얕은 복사, 깊은 복사에 대해 들어보긴 했지만 명확하게 알지 못했는데 오늘 기본형 데이터와 참조형 데이터부터 변수의 복사 부분에 대해 공부하면서 왜 이런 개념이 나오게 됐는지에 대해 좀 파악이 되서 보람이 있었던 것 같다. 이어서 실행 컨텍스트 부분에 대해서도 강의를 들으면서 학습을 했는데 예전에 내부함수에서 외부함수로부터 참조하는 변수가 있을 때 외부함수가 사라져도 렉시컬 환경에 그 변수값이 남아서 그걸 참조해서 값을 리턴할 수 있다고 얼핏 본 것 같은데 막상 오늘 제대로 강의를 들으니까 뭔가 용어 자체부터 어렵게 다가오고 갑자기 스코프, 호이스팅도 튀어나오고... 이부분을 왜 배워야되는지부터 알면 그래도 좀 이해가 될텐데... 이부분부터 집중도 잘 안됐고 어렵게 느껴졌다ㅠ 여튼 당분간은 JS 중급, 심화 개념을 공부하면서 기본기를 다지는 시간으로 보내게 될 것 같다.

profile
FE Developer

0개의 댓글