자바스크립트의 프로토타입은 객체가 가진 기본 속성과 메서드를 정의하는 객체입니다. 프로토타입에 정의된 이러한 속성과 메서드가 각 타입을 타입답게 만들게 됩니다. 예를 들어 숫자에
toString()
메서드가 있는 것처럼 말입니다. 옛날에 클래스가 나오기 전에는 이 프로토타입을 통해 직접 메서드를 만들어주고는 했습니다.
참조에 의한 복사가 일어나는 이유는 값 자체를 복사해 주는 게 아니라 값이 들어있는 메모리 주소를 복사해서 주기 때문입니다. 그렇기에 객체를 복사하게 되면 서로 같은 메모리 주소를 가리키게 되어 한쪽이 값을 바꾸면 다른 쪽이 보여주는 값도 바뀌게 되는 겁니다. 이러한 특징 때문에 여러 오류가 생기곤 합니다. 그래서 깊은 복사를 통해 객체 또한 값으로서 복사하도록 하는 방법들이 있는데 대표적으로는
JSON.stringify()
를 통해 객체를 문자열로 바꾸어서 복사하는 방법이 있고, 비교적 최근에 나온structuredClone()
을 사용하는 방법이 있습니다. 사용하기에는 후자가 더 짧고 간편합니다.
자바스크립트에서 nullish는 말 그대로
null
과 같은 값을 뜻합니다. 이에 대한 예시로는 대표적으로null
과undefined
가 있습니다. 여기서 알고 넘어가면 좋은 게 있는데, 바로 nullish 병합 연산자입니다. 기존에는||
를 이용해서 병합을 구현했는데요. 이렇게 되면 0이나 ''처럼 빈 문자열 등이false
로 평가되어 제대로 동작하지 않을 가능성이 생기곤 합니다. 그때 바로 물음표 2개를 붙인??
로 대체해준다면 nullish한 값에 대해서만 병합을 실행하게 됩니다.
비교할 때 판단이 다릅니다. 간단히 요약하자면 ==는 타입은 다르지만 값이 같을 때, 예를 들어 문자열 1과 숫자 1을 비교하면 같다고 뜰 겁니다. 그래서 숫자 0과 nullish 값을 이 연산자를 이용해 비교하면 true가 떠서 같다고 나올 겁니다. 그리고 ===는 좀 더 엄격하게 타입과 값을 모두 비교합니다. 그래서 문자열 1과 숫자 1을 비교하면 다르다고 뜰 것입니다. 엄격한 비교를 위해 ===를 더 선호해 사용하는 편입니다.
자바스크립트에서 동기는 작업을 순서대로 실행하며, 한 작업이 끝나야 다음 작업을 실행하는 것을 뜻합니다. 반면에 비동기 처리는 한 작업이 완료되기까지 기다리지 않고 바로 다음 작업을 실행할 수 있어 더 효율적으로 리소스를 활용할 수 있습니다. 비동기 프로그래밍은 주로 네트워크 요청, 파일 IO와 같이 시간이 얼마나 걸릴지 알 수 없는 작업을 처리할 때 필요합니다.
우선 이벤트 루프는 이벤트를 순서대로 돌아가며 처리하는 역할을 합니다. 마치 줄을 서있는 것처럼 이벤트 루프는 큐에 담긴 이벤트를 하나하나 호출 스택으로 옮깁니다. 물론 실행 중인 컨텍스트가 없어 호출 스택이 비어있을 때만 말입니다.
그리고 실행 컨텍스트는 이러한 이벤트가 실행되는 환경을 뜻합니다. 앞서 말한 호출 스택에 이러한 실행 컨텍스트를 쌓아두고 작업이 종료되면 스택에서 빠져나오게 됩니다.
이러한 이벤트 루프와 실행 컨텍스트는 싱글 스레드 환경인 자바스크립트에서 비동기 작업을 효과적으로 처리하기 위해 필수적인 메커니즘입니다.
자바스크립트에서는 작업이 종료되어 호출 스택에서 빠져나오게 되면 참조가 사라져 가비지 컬렉터에 의해 메모리에서 사라지게 됩니다. 그런데 호출 스택에서 빠져나왔지만 특정 행동에 의해서 참조가 사라지지 않았다면 어떻게 될까요? 이 객체는 아무도 접근할 수는 없지만 메모리에 존재는 하는, 일종의 유령이 되는 겁니다. 이러한 특징을 활용하여 데이터 은닉화 및 캡슐화를 구현할 수 있습니다. 다만 남용할 경우 메모리의 누수를 피할 수 없다는 단점도 있습니다.
- 클로저가 하는 일
외부 함수의 변수에 접근할 수 있다. 클로저는 내부 함수가 외부 함수의 변수에 접근할 수 있게 합니다. 이는 함수가 선언된 렉시컬 환경을 "기억"하기 때문에 가능합니다. 클로저를 사용하면 외부 함수의 실행이 끝난 후에도 해당 함수의 변수들을 계속해서 참조할 수 있습니다.- 데이터 은닉화 및 캡슐화
클로저를 사용하면 특정 데이터나 메서드를 외부에서 직접 접근할 수 없게 만들 수 있습니다. 이는 객체 지향 프로그래밍의 캡슐화와 비슷한 역할을 합니다. 예를 들어, 클로저를 이용해 "프라이빗(private)" 변수를 만들고, 외부에서는 접근하지 못하게 할 수 있습니다.- 메모리 누수의 위험
클로저를 사용할 때 주의해야 할 점은, 내부 함수가 외부 함수의 변수를 계속 참조하고 있기 때문에 메모리에 대한 참조가 남아 있어 가비지 컬렉터에 의해 제거되지 않을 수 있다는 점입니다. 이로 인해 의도치 않은 메모리 누수가 발생할 수 있습니다.
그 첫 번째 이유는 페이지의 로딩 속도 최적화 때문입니다. 브라우저는 html을 읽어내려가다 script 태그를 만나게 되면 해당 자바스크립트 코드를 읽고 실행하기 시작합니다. 만약 script 태그가 상단에 있다면 DOM 트리 구축 시기가 늦어져 페이지의 로딩이 느려질 수 있습니다.
여기에 이어 두 번째 이유는 DOM 접근시에 오류가 생길 수 있기 때문입니다. 앞서 자바스크립트가 먼저 읽히고 실행될 수도 있다고 말씀드렸죠? 그런데 만약 자바스크립트 코드에 document.querySelector처럼 html 태그를 가져오는 코드가 있다면 어떻게 될까요? 아직 DOM 트리가 구축되지 않았으므로 원하는 태그를 가져오지 못하고 undefined를 반환하게 됩니다. 이러한 이유로 script 태그를 body의 최하단에 넣곤 합니다.
물론 다른 방법도 있는데요. 바로 script 태그에 defer나 module을 작성하는 것입니다.
그러면 자동으로 script 태그를 지연 로딩할 수 있습니다.
보통 async/await를 사용해 데이터를 불러오게 되면 순차적으로 데이터를 불러오기 마련입니다. 이때 Promise.all() 함수를 사용한다면 함수의 인자로 넘겨준 비동기 함수를 병렬로 실행할 수 있습니다. 예를 들어 3초의 실행 시간을 가진 비동기 함수 3개가 있다고 한다면, 기본적으로 순차적으로 호출할 시에 모든 함수가 끝나기까지 3 * 3 = 9초가 걸리게 됩니다. 하지만 이걸 병렬로 호출하게 된다면 3개의 요청을 동시에 보내게 되어 3초만 걸리게 됩니다.
Promise.race()
를 사용하면 됩니다. 이 함수의 기능은, 인자에 비동기 함수를 넣어주게 되면 넣어준 함수들 중 가장 빠르게 resolve되는 함수의 결과만을 가져오게 됩니다. 그러면 여기에 내가 사용할 비동기 함수와,setTimeout
을 함께 넣어주게 된다면 비동기 함수와setTimeout
둘 중 먼저 끝나는 쪽을 반환해주게 됩니다. 만약setTimeout
을 3초로 정해둔다면 3초 안에 비동기 함수가 resolve 되지 않을 시 타임 아웃 판단을 내리도록 구현할 수도 있습니다.