5 분 소요

JS 기초

JS에 대한 명확한 개념과 메소드 사용법을 쉽게 참고할 수 있도록 구성한 가이드.

클로저란

클로저는 함수를 지칭하고, 그 함수가 선언된 환경과의 관계의 개념. JS 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다. 클로저는 ‘내부함수가 외부함수의 context에 접근’할 수 있는 것을 가리킨다.

클로저

클로저(Closure)란 함수를 지칭하며, 그 함수가 선언된 환경과의 관계를 의미하는 개념이다. 쉽게 말하면 개념 자체가 외부 변수 접근 성질을 가진 함수라는 것이다.

일반적으로 함수를 호출하면 실행되고 끝나지만, 클로저는 실행이 끝난 후에도 외부 함수의 변수에 접근할 수 있는 상태로 유지되는 함수다. JS에 국한되지 않으며, 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 중요한 특성이다.

function outer() {
  let count = 0; // 외부 함수의 변수

  return function inner() { // 내부 함수 (클로저)
    count++;  // 외부 변수에 접근
    console.log(count);
  };
}

const closureFunc = outer(); // outer 실행 → 내부 함수 반환
closureFunc(); // 1
closureFunc(); // 2
closureFunc(); // 3

호이스팅

변수나 함수 선언을 코드의 최상단으로 끌어올리는 것처럼 동작하는 특성이다. 이는 자바스크립트 엔진이 코드를 실행하기 전에 변수와 함수 선언을 메모리에 저장하는 방식 때문에 발생한다.

메모리에 저장하는 방식

이벤트 루프란

이벤트 루프

JS는 싱글쓰레드 언어라고 들은 적이 있을 것이다. 싱글쓰레드면 한 번에 하나의 작업만 수행할 수 있다. 하지만 JS를 사용해 보면 멀티쓰레드처럼 여러 작업을 동시에 실행하는 것을 볼 수 있다. 그렇다면 JS는 왜 싱글쓰레드 언어라고 불리는 것인가? 이유는 ```이벤트 루프(Event Loop)```가 싱글쓰레드이기 때문이다. 정리하자면, 브라우저나 NodeJs와 같은 멀티쓰레드 환경에서 JS가 실행되고 메인 쓰레드로 **이벤트 루프를** 사용함으로 멀티쓰레드처럼 사용할 수 있던 것이다. 이를 확실히 이해하기 위해선 JS 동작 과정을 알아야 한다. ### JS 동작 과정 ![js_basic](../assets/img/js_basic.png) 위 사진은 전체 과정을 그린 것이며, 하나씩 정리해가며 살펴보자. #### Code Area JS 코드를 실행하기 위해 저장하는 영역이다. #### Call Stack 실행 중인 함수를 추적하고 계산을 수행하며, 지역 변수를 LIFO(Last In First Out) 방식으로 저장한다. 원시 타입 데이터도 이곳에 저장한다. #### Heap 참조 타입(객체 등)이 저장되는 영역으로, 메모리 할당이 LIFO(Last In First Out) 방식이 아닌 랜덤하게 이루어진다. JS 엔진의 가비지 컬렉터가 메모리 누수를 방지하며 관리를 담당한다. ###### 변수의 필요 유무를 판단하고 메모리에서 제거하는 역할을 하는 것이 가비지 컬렉션이다. 가비지 컬렉션으로 자동 메모리 관리가 가능하다. #### Callback Queue 비동기 코드가 들어가고, 실행을 위해 대기하는 곳이다. JS 코드가 실행 중에 이벤트를 만나면 해당 이벤트들은 콜백 큐에 쌓인다. 스택과 다르게 큐이므로, 선입선출된다는 특징이 있다. #### 이벤트 루프 싱글 스레드인 JS의 작업을 멀티 스레드로 돌려 작업을 동시에 처리시키거나, 여러 작업 중 어떤 작업을 우선 동작 시킬지 결정하는 것이 이벤트 루프다. 브라우저 내부의 콜 스택, 콜백 큐, Web APIs 등의 요소를 모니터링 하며 비동기적으로 실행되는 작업을 관리하고, 순서대로 처리하여 프로그램 실행 흐름을 제어한다. 즉, 브라우저 동작을 제어하는 관리자이다. ###### Web APIs는 타이머, 네트워크 요청, 파일 입출력, 이벤트 처리 등 브라우저에서 제공하는 다양한 API를 포괄하는 총칭이다. #### JS Engine JS 엔진은 JS 코드를 실행하는 프로그램으로, 주요 구성 요소는 파싱, 컴파일, 실행 등을 처리하는 인터프리터와 컴파일러다. 콜 스택, 힙, 콜백 큐는 **JS 엔진이 사용하는 실행 환경(런타임)**의 일부로, JS 엔진 자체와는 구분되는 개념이다.

브라우저 환경

브라우저 환경

JS는 웹 브라우저에서 사용하려고 만든 언어다. 이후 업데이트를 통해 다양한 사용처와 플랫폼을 지원하는 언어로 진화했다. JS가 돌아가는 플랫폼은 호스트(host) 라고 불린다. 호스트는 브라우저, 웹서버, 심지어는 커피 머신이 될 수도 있다고 한다. 각 플랫폼은 해당 플랫폼에 특정되는 기능을 제공하는데, JS 명세서에선 이를 호스트 환경(host environment) 이라고 부른다. 호스트 환경은 플랫폼에 특정되는 객체와 함수를 제공한다. 웹 브라우저는 웹 페이지를 제어하기 위한 수단을 제공하고, Node.js는 서버 사이드 기능을 제공해준다. 아래 사진은 호스트 환경이 웹 브라우저일 때를 정리한 것이다. ![object](../assets/img/windowObjects.svg) 최상단에 window 루트 객체가 존재한다. (하위 DOM, BOM, JS) * JS 코드의 전역 객체이다. * '브라우저 창(browser window)'을 대변하고, 이를 제어할 수 있는 메서드를 제공한다. 문서 객체 모델(Document Object Model, DOM)은 웹 페이지 내의 모든 콘텐츠를 객체로 나타내주며 수정 가능하다. document 객체는 페이지의 기본 ‘진입점’ 역할을 한다. document 객체를 이용해 페이지 내 그 무엇이든 변경할 수 있고, 원하는 것을 만들 수도 있다. 브라우저 객체 모델(Browser Object Model, BOM)은 문서 이외의 모든 것을 제어하기 위해 브라우저(호스트 환경)가 제공하는 추가 객체를 나타낸다. ex. 현재 사용 중인 브라우저 정보를 알려주는 ```navigator.userAgent```와 브라우저가 실행 중인 운영체제 정보를 알려주는 ```navigator.platform```. ex. ```location``` 객체는 현재 URL을 읽을 수 있게 해주고 새로운 URL로 변경(redirect)할 수 있게 해준다. ```alert/confirm/prompt``` 도 BOM의 일부다. 문서와 직접 연결되어 있지 않지만, 사용자와 브라우저 사이의 소통을 도와주는 순수 브라우저 메소드다.

반복문

map / forEach / for…of / for…in

반복문

* map: 배열의 각 요소를 순회하고, 그 결과로 새로운 배열을 반환한다. 주로 값을 변환하여 새로운 배열을 만들 때 사용된다. * forEach: 배열의 각 요소를 순회하며, 반환값이 없고 새 배열을 생성하지 않는다. 요소에 대한 작업은 가능하지만, 주로 **부수 효과**를 줄 때 유용하다. ###### 부수 효과: 외부 상태나 변수에 영향을 주는 작업을 의미 * for...of: 배열, 문자열, Set, Map 등 **이터러블** 객체를 순회하며, 각 요소 값을 직접 사용할 수 있다. 주로 배열과 이터러블 객체에서 요소를 순회할 때 적합하다. ###### 이터러블: 자료를 반복할 수 있는, Symbol.iterator가 구현된 객체를 말한다. Symbol.iterator는 객체를 반복 가능한(iterable) 객체로 만들어주는 특수한 심볼 프로퍼티이다. 간혹 'undefined' is not iterable 에러가 나는데, 이터러블 타입의 인자를 받아야 하는 메소드가 다른 타입의 값을 받았기 때문이다. * for...in: 객체의 모든 열거 가능한 속성 키를 순회한다. 주로 객체의 속성 키에 접근할 때 사용된다. ###### 배열에는 비추천 (인덱스와 순서가 달라질 수 있음)

내장 객체 (Built-in Object)


Promise

Promise

Promise 객체는 비동기 작업의 완료나 실패를 나타내는 독자적인 객체다 (ex. Array, Object). 비동기 작업이 끝날 때까지 결과를 기다리는 것이 아닌, 결과를 제공하겠다는 '약속'을 반환한다는 의미로 Promise라 불리게 되었다. Promise는 함수로 감싸 사용하는 것이 일반적이다. ```javascript function promiseFunction() { return new Promise((resolve, reject) => { if () { resolve(); } else { reject(); } }); } ``` 위처럼 프로미스 객체를 반환하는 함수를 생성하고 호출하면 프로미스 생성자를 반환한다. (생성된 프로미스 객체를 함수 반환값으로 사용하는 기법). 이처럼 프로미스 객체를 함수로 만드는 이유는 3가지가 있다. * 재사용성 : 필요할 때마다 호출하여, 반복되는 비동기 작업을 효율적으로 처리할 수 있다. * 가독성 : 코드의 구조가 명확해져, 비동기 작업의 정의와 사용을 분리해 코드의 가독성을 높일 수 있다. * 확장성 : 인자를 전달하여 동적으로 비동기 작업을 수행할 수 있다. 또한 여러 개의 프로미스 객체를 반환하는 함수들을 연결하여 복잡한 비동기 로직을 구현할 수 있다. ### async function 앞에 ```async```를 붙이면 해당 함수는 항상 Promise 객체를 반환한다. 이 특징 덕분에, async 함수 내에서 반환하는 값이 프로미스가 아니어도 자동으로 Promise.resolve()로 감싸져 반환된다. 예를 들어, 아래와 같은 상황이 있다. #### 일반 함수의 반환 ```javascript function normalFunction() { return 42; } console.log(normalFunction()); // 42 ``` #### async 함수의 반환 ```javascript async function asyncFunction1() { return 42; } console.log(asyncFunction()); // Promise { 42 } async function asyncFunction2() { return Promise.resolve(42); } ``` 위의 두 함수 모두 Promise를 반환하는데, 첫 번째 함수는 반환 값 42가 자동으로 Promise.resolve(42)로 감싸져 반환되는 것이다. 이런 특징은 async/await 패턴에서 값을 처리할 때 일관성을 유지할 수 있게 해 주는 중요한 동작이다. ### await ```await``` 은 async 함수 안에서만 동작한다. JS는 await 키워드를 만나면 Promise가 처리될 때까지 기다린다. 결과는 그 이후 반환된다. 예를 들어, 1초 후 이행되는 Promise 코드를 통해 await가 어떻게 동작하는지 살펴보자. ```javascript async function asyncFunction() { let promise = new Promise((resolve, reject) => { setTimeout(() => resolve("완료"), 1000) }); let result = await promise; // promise가 처리될 때까지 기다림 alert(result); // "완료" } asyncFunction(); ``` 함수를 호출하고, 함수 본문이 실행되는 도중 await 줄에서 실행이 잠시 **중단**되었다가 Promise가 처리되면 재개한다. 이때 Promise 객체의 결과값이 변수 result에 할당된다. 따라서 위 예시를 실행하면 1초 뒤 **완료**가 출력된다. Promise가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않는다. 또한 promise.then보다 가독성 좋고, 사용하기 쉬워 Promise의 result 값을 얻는데 유용하다.

카테고리:

업데이트: