하다보니
[TIL] 프론트 엔드 개발자란.., 자료형, 메모리, 스코프와 클로저 본문
예 안녕하세요. 앞서 2개월 차 회고 쓴 사람입니다.

맞습니다. 복습기간을 맞아 1주차 회고부터 쓰며 노션에만 고이 모셔놓았던 제 학습의 기록을 블로그로 남겨보려 합니다.
1주 차는 JavaScript의 주요 문법에 대해 배우는 시간을 가졌습니다. 자료구조도 정리하였습니다.
바로 시작합니다. 출바알~!
프론트 엔드 개발자와 JavaScript
브라우저는 웹 프론트엔드 개발자가 작성한 로직이 돌아가는 실행기입니다. 다른 직군과 다르게 운영체제와 환경에 크게 구애받지 않고 브라우저라는 가상 환경에만 집중하여 개발하는 것이 가능합니다.
브라우저의 동작원리
통신, 렌더링, 스크립트 실행, 3가지 동작으로 나눌 수 있습니다. 통신은 서버와의 통신을 말합니다. 렌더링은 돔이라는 객체에 화면을 그리는 것입니다. 돔은 통신을 통해 받은 html을 브라우저가 읽어 생성되며, 트리구조로 이루어져 있습니다. 화면에 보이는 요소 하나하나가 모두 트리구조로 이루어져 있습니다. 스크립트 실행은 말 그대로 js파일을 실행하는 것을 말합니다. 이런 방식으로 동적인 화면을 구현할 수 있습니다.
프론트엔드 개발자의 역할
브라우저에서 동작하는 UI를 개발하는 일을 하는 사람입니다. UI를 만들기까지의 과정은 매우 험난합니다. 프론트엔드 개발자는 거의 항상 마지막을 담당하는 직군입니다. 디자인이 있어야 UI를 만들 수 있고, 백엔드로부터 데이터를 받을 수 있어야 사용자에게 가공하여 정보를 제공할 수 있게 됩니다. 따라서 일정이 늦춰지지 않도록 잘 조율하는 것이 중요합니다. 이 과정이 결코 순탄하지 않아서 개발 능력도 중요하지만, 다른 직군과 협업을 위한 소통 능력이 상당히 중요합니다. 또한 고객과 맞닿아있는 영역이기에 다방면으로 공부가 필요합니다.
자료형과 메모리
- 변수 : 직접 메모리에 할당한 값을 의미합니다.
- var : 재선언 가능, 재할당 가능.
- let : 재선언 불가, 재할당 가능.
- const : 재선언 불가, 재할당 불가.
var의 사용은 권장하지 않습니다.
1. 재선언 가능.
이미 선언했던 변수를 선언하게 되면 의도하지 않은 변수값 변경이 일어날 수 있어 그 변수를 사용하는 다양한 로직들에 치명적인 문제가 생기게 됩니다.
=> 이렇게 재선언이 가능한 이유는 바로 호이스팅 때문인데, var 키워드로 변수 선언 시, 해당 변수의 선언부를 스코프 최상단으로 올려버리는 것을 말합니다. 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다.
JavaScript의 변수 생성과 초기화의 작업이 분리되어 진행되는데, var 같은 경우는 선언과 초기화가 한 번에 일어납니다. undefined로 초기화가 이뤄지는데, 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않습니다.
var는 if 나 for문에서 중괄호문에 해당하는 block-scoped가 아니라 function-scoped를 갖습니다. 따라서 전역 함수 외부에서 생성한 변수는 모두 전역 변수입니다. 이는 전역 변수를 남발할 가능성을 높입니다. 대부분의 문제는 전역 변수로 인해 발생해서, 전역 변수는 최대한 사용을 자제해야 합니다. 전역 변수는 유효 범위(scope)가 넓어서 어디에서 사용될 것인지 파악하기 힘들고, 의도치 않게 변경될 수 있어서 복잡성을 증가시킵니다. 따라서 변수의 스코프는 좁을수록 좋습니다.
- let은 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러가 발생한다. 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대 (TDZ)에 빠지기 때문이다.
var funcs = [];
// 함수의 배열을 생성하는 for 루프의 i는 전역 변수다.
for (var i = 0; i < 3; i++) {
funcs.push(function () { console.log(i); });
}
// 배열에서 함수를 꺼내어 호출한다.
for (var j = 0; j < 3; j++) {
funcs[j]();
}
출력은 3 3 3 이다.
함수는 함수가 정의될 때 변수 자체만 기억하고 있을 뿐, 실제 변수로부터 값을 꺼내오지는 않습니다. 따라서 위에서 funcs[j]가 호출될 타이밍엔 이미 모든 i값이 3의 값을 가지게 된 후여서 3이 출력됩니다. 하지만 let은 for문 초기식에 존재하는 경우, 특수한 작용이 일어납니다. 이 작용은 for 루프가 돌 때마다 해당 함수 스코프에 새로운 i를 만들고, 여기에 이전 루프 스코프의 i값을 대입합니다. 이로 인해 각각의 익명 함수는 저마다 다른 i를 가리키게 됩니다. let의 이 특수한 작용은 오직 for문의 초기식 안에서만 일어납니다.
var functions = [];
for (let i = 0; i < 3; i++) { // let 사용
functions.push(function () {
console.log(i);
});
}
functions[0](); // 출력: 0
functions[1](); // 출력: 1
functions[2](); // 출력: 2
아래처럼 let을 for문 밖에서 선언할 경우 i가 하나만 존재하므로, 모두 3이 출력된다.
var functions = [];
let i = 0; // for 문 바깥에서 let 사용
for (; i < 3; i++) { // 초기식을 비워 둠
functions.push(function () {
console.log(i);
});
}
functions[0](); // 출력: 3
functions[1](); // 출력: 3
functions[2](); // 출력: 3
- const는 반드시 선언과 동시에 할당이 이뤄져야 한다. 그렇지 않으면, 문법 에러가 발생한다. const는 재할당은 불가능하지만 할당된 객체의내용은 변경할 수 있다.
const user = { name: 'Lee' };
// const 변수는 재할당이 금지된다.
// user = {}; // TypeError: Assignment to constant variable.
// 객체의 내용은 변경할 수 있다.
user.name = 'Kim';
console.log(user); // { name: 'Kim' }
//객체의 내용이 변경되더라도 객체 타입 변수에 할당된 주소값은 변경되지 않는다. 따라서 객체 타입
변수 선언에는 const를 사용하는 것이 좋다.
그럼 어떻게 써??이 세 개의 변수를??!🙄🧐
ES6를 사용한다면 var는 사용하지 않는다.
재할당이 필요한 경우에 한정해 let을 사용한다. 이때 변수의 스코프는 최대한 좁게 만든다.
변경이 발생하지 않는 원시값과 객체에는 const를 사용한다. const는 재할당을 금지하므로, var, let보다 안전하다.
변수를 선언하는 시점에는 재할당이 필요할지 잘 모르는 경우가 많다. 그리고 객체는 의외로 재할당 하는 경우가 드물다. 따라서 변수를 선언할 때는 일단 const로 선언을 하고, 반드시 재할당이 필요하면 그때 let으로 변경해도 늦지 않다.
자바스크립트에서 메모리를 어떻게 다루는가
할당 -> 사용 -> 해제
할당된 메모리는 값을 넣어 사용할 수 있습니다. 사용 후 해제하여 메모리 제거.
메모리는 한정되어있다. 메모리가 꽉 차면 별도의 조치가 필요하다. 메모리를 해제하는 과정이 필요.
자바스크립트 엔진은 garbage collector가 사용하지 않는 메모리를 해제하는 역할을 맡고 있다. 따라서 JS는 개발자가 직접 메모리를 신경써줘야하는 언어에 비해 메모리를 크게 신경 쓰지 않아도 돼서 편하다.
- 변수 선언시 변수의 고유 식별자를 생성하고 메모리 주소를 할당한다. 생성한 주소에 값을 넣는다. 변수나 상수는 값이 아닌 메모리 주소를 바라보고 있다. 새로운 변수에 기존 변수를 대입하면 기존 메모리 주소를 참조하게 된다. 원시 타입은 변경이 불가능하기 때문에, 항상 메모리가 새로 할당된다.
- 자바스크립트 엔진은 가상머신으로 구성되어있다. 가상 머신엔 메모리 모델을 구현해놓았는데, 각각 heap영역과 call stack 영역이다. heap은 참조 타입이 들어가고, call stack은 원시 타입이 들어가게 된다.
- 배열의 경우는 참조 타입이다. 이때 배열을 선언하면 heap에 배열 영역이 생성되는데, call stack에 선언된 배열 변수가 힙에서 생성된 배열의 메모리 주소를 참조하게 됩니다.
- 힙 영역의 메모리는 동적으로 크기가 변할 수 있어서 배열에 값을 추가하면 힙 메모리 영역에 그대로 할당이 됩니다.
스코프, 클로저
스코프 : 유효 범위, 변수가 어느 범위까지 참조되는지를 뜻한다.
- 어디서든 접근 가능한 전역 스코프
- 해당 컨텍스트 내에서만 접근 가능한 지역 스코프
- 동적 스코프 : 함수를 어디서 호출하였는지에 따라 상위 스코프를 결정한다.
- 렉시컬 스코프, 정적 스코프 : 함수를 어디서 선언하였는지에 따라 상위 스코프 결정한다.
자바스크립트를 비롯한 대부분의 프로그래밍 언어가 렉시컬 스코프를 따릅니다. 렉시컬 스코프는 어디서 호출하였는지가 아니라 어디서 선언하였는지에 따라 결정됩니다. 따라서 함수를 선언한 시점에 상위 스코프가 결정됩니다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // 1
bar(); // 1
// 둘 다 1을 출력한다.
- 암묵적 전역
아래와 같은 코드에서 y는 선언되지 않았습니다. 따라서 값을 할당하면 참조 에러가 발생할 것처럼 보이지만, 선언하지 않은 식별자 y는 마치 선언된 변수처럼 동작합니다. 선언하지 않은 식별자에 값을 할당하면 전역 객체의 프로퍼티가 되기 때문입니다. 즉, window.y=20으로 해석해서 프로퍼티를 동적 생성합니다. 이를 암묵적 전역이라 합니다.
하지만 y는 변수가 아니고, 전역 객체의 프로퍼티로 추가되었을 뿐이라. 호이스팅이 발생하지 않는다.
var x = 10; // 전역 변수
function foo () {
// 선언하지 않은 식별자
y = 20;
console.log(x + y);
}
foo(); // 30
- 전역변수의 최소화 사용을 위해, 전역 변수 객체 하나를 만들어 사용하는 방법이 있다. 또한 즉시 실행 함수를 사용해 전역 변수 사용을 억제한다.
클로저
클로저는 함수가 선언된 환경의 스코프를 기억하여 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 만드는 문법입니다. 클로저를 알아야 하는 이유는 유용하게 사용해서라기 보다는 알기 힘든 버그를 잘 수정하기 위해서입니다. 내부 함수에서 외부 함수의 지역변수에 접근할 수 있는 것. 외부 함수가 종료된 이후에도 내부 함수에서 외부 함수에 접근이 가능합니다.
클로저와 실행 컨텍스트는 따로 글을 작성하며 자세히 알아보도록 하겠습니다.
참고 : https://www.bangseongbeom.com/javascript-var-let.html#fn:create-per-iteration-environment,
프로그래머스 데브 코스 강의
'프로그래머스 데브코스' 카테고리의 다른 글
[TIL] 네트워크 기초 (0) | 2022.05.25 |
---|---|
데브코스 2개월차 회고 (0) | 2022.05.21 |