JS 에 대해 누구나 알고있을 법한 상식들~

공부는 혼자하는 거·2023년 1월 17일
0

1. 역사

1995년 자바스크립트는 유저와의 상호작용을 위해 10일만에 탄생한 언어다. "HTML 페이지를 동적으로 만드는 것"에 목적이 있어 가벼운 인터프리터 언어로 만들어졌다. 2009년 구글은 당시 브라우저에서 자바스크립트를 동작시키는 엔진에 한계를 느꼈고, V8 엔진을 출시했다. 자바스크립트는 interpreter 언어다. 한줄한줄 씩 읽고 바로 기계어로 해석한다. 하지만 엔진 내부에서 컴파일 과정을 거치기도 한다. 먼저 엔진이 실행할 JS 파일을 받게 된다. 파싱, AST(Abstract Syntax Tree)를 구축하는 과정을 거친다.

다음으로 Interpreter가 코드를 읽으며 실행한다. 코드를 수행하는 과정에서 프로파일러가 지켜보며 최적화 할 수 있는 코드를 컴파일러에게 전달해준다. 주로 반복해서 실행되는 코드 블록을 컴파일(최적화)한다. 그리고 원래 있던 코드와 최적화된 코드를 바꿔준다. 코드를 우선 인터프리터 방식으로 실행하고 필요할 때 컴파일 하는 방법을 JIT(Just-In-Time) 컴파일러 라고 부른다.

2. 엔진구조

메인 JS 엔진의 메모리 할당영역

Memory Heap : 메모리 할당이 일어나는 곳
Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

자바스크립트는 기본적으로 싱글 쓰레드 기반 언어이다. 호출 스택이 하나이며 한 번에 하나의 작업만 처리할 수 있다. 참조타입의 데이터들은 힙 영역에 저장되고, 원시타입 데이터는 콜 스택에 저장된다. 참조타입 데이터가 저잘된 메모리힙의 주소값은 콜스택의 변수 식별자 값으로 저장된다. 콜스택에 할당된 변수 식별자 자체는 콜스택 상의 실행 컨텍스트의 렉시컬 환경에 저장된다.

GC

자바스크립트는 GC를 지원하므로, 사용되지 않는 변수의 메모리 수거는 우리가 신경쓰지 않아도 된다. 하지만 몇가지 신경써줘야 되는 상황이 있는데,
1. global variable
2. setInterval 내부의 변수 참조
3. Event Listener
등등 메모리 누수가 일어날 법한 상황들을 인지하고 코드를 짜야 한다.

Web APIs

본디 JS는 브라우저에서 돌아가도록 설계된 언어이며, 브라우저를 좀 더 사용자와 인터렉티브하게 동작시키는 용도의 언어이다. 브라우저에는 자바스크립트 언어 자체의 스펙이 아닌 여러가지 내장된 기능들을 제공해주는데, 이를 자바스크립트를 통해서 조작시켜줄 수 있는 인터페이스를 제공해준다.(Web Apis) 이러한 web api의 요청들의 처리는 JS 엔진의 쓰레드와는 다른 쓰레드들에서 이루어진다. 해당 쓰레드는 요청이 완료되는 순간 전달받았던 콜백 함수를 JS엔진의 테스크 큐에 집어넣는다.

Task Queue

태스크 큐는 웹 API를 처리하고 있던 쓰레드로부터 전달받은 콜백 함수들을 저장하는 자료구조로 JS 엔진 자체에 포함되어있다. 여기에 저장된 콜백 함수들은 스택이 비는 순간 스택에 순서되로 푸시된다.

이벤트 루프

위와 같은 동작이 가능하려면 매 순간 스택이 비어있는지 여부와 테스크 큐에 콜백 함수가 기다리는지 여부를 확인해야만 한다. 이러한 역할을 수행하는 것이 이벤트 루프이다. 역시 JS 엔진 표준 스펙의 일부이며, 매 순간 스택이 비어있는지 확인해서 스택이 비어있다면 첫번째로 들어온 콜백함수를 스택에 쌓는 역할을 한다.

Single Threaded, Non-blocking, Asynchronous

console.log("first")
setTimeout(() => {
    console.log("second")
}, 1000)
console.log("third")


//결과

first
third
undefined
second

순서대로 동기식으로 코드가 실행되다, 엔진이 JS에서 처리할 수 없는 setTimeout을 알아차리고 비동기 적으로 수행되도록 WebApi 에 푸쉬한다. 호출스택은 Web API에 전달된 코드를 신경쓰지 않고 계속 진행하고 (Non-blocking), 별도의 스레드에서 처리된 WebApi의 결과는 Task Queue에 담긴다. 이벤트 루프는 Call stack과 테스크 큐 사이에서 스택이 비어있는지 매법 확인하며, stack이 비워지면 큐에 담겨진 함수들을 들어온 순서대로 스택에 넘겨준다.

즉 자바스크립트를 실행시키는 스레드는 단 하나뿐이지만, 런타임으로 확장해서 보면 멀티 스레드라고 할 수 있다. 브라우저 엔진은 렌더링, 통신 준비, 메모리 초기화, 프로세스 정리 등과 같은 수많은 작업을 백그라운드에서 멀티스레드로 처리한다. 자바스크립트 엔진은 콜 스택과 메모리 힙만 담당하고 나머지는 브라우저 영역에서 담당, 이처럼 실제로 자바스크립트 엔진은 독립적으로 실행되지 않고, 런타임(웹 Browser or NodeJS같은) 멀티 스레드 환경에 임베디드되어 실행된다.

  1. MACROTask Queue 에 넣는 함수
    setTimeout setInterval setImmediate requestAnimationFrame I/O UI Rendering
  2. Microtask Queue 에 넣는 함수
    process.nextTick Promise Object.observe MutationObserver

마이크로 테스크 큐는 태스크 큐와는 별도의 큐이며 Microtask Queue 는 Task Queue 보다 우선순위가 높다.

https://www.youtube.com/watch?v=8aGhZQkoFbQ

https://engineering.huiseoul.com/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94%EA%B0%80-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B4%80%EB%A6%AC-4%EA%B0%80%EC%A7%80-%ED%9D%94%ED%95%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98-%EB%8C%80%EC%B2%98%EB%B2%95-5b0d217d788d

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction

http://latentflip.com/loupe/?code=JC5vbignYnV0dG9uJywgJ2NsaWNrJywgZnVuY3Rpb24gb25DbGljaygpIHsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gdGltZXIoKSB7CiAgICAgICAgY29uc29sZS5sb2coJ1lvdSBjbGlja2VkIHRoZSBidXR0b24hJyk7ICAgIAogICAgfSwgMjAwMCk7Cn0pOwoKY29uc29sZS5sb2coIkhpISIpOwoKc2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIkNsaWNrIHRoZSBidXR0b24hIik7Cn0sIDUwMDApOwoKY29uc29sZS5sb2coIldlbGNvbWUgdG8gbG91cGUuIik7!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D

https://dev.to/lydiahallie/javascript-visualized-promises-async-await-5gke

3. 동적타입 + 메모리 구조

JS는 타입이 동적(런타임)으로 정해진다. JS는 원시타입과 참조타입이 존재하며, 독특한 점은 일반적인 컴파일 언어에서는, 변수를 선언할 때, 타입을 사전에 지정해줌으로 정해진 메모리 크기를 할당할 수 있는 반면, JS는 타입이 런타임 상 유동적으로 바뀐다는 점이다. 이는 참조타입이 아닌 원시타입조차, 우리가 일반적으로 알고 있는 (refernce) 에 해당한다고 볼 수 있다.

JS는 원시타입조차, 태그가 지정된 포인터를 사용하여 JS 값을 나타낸다. 이것은 할당 된 메모리가 최소 4 또는 8 바이트를 차지하고 있다는 것을 의미한다. JS는 원시타입조차, 변수 영역과 실제 데이터 영역이 구분되어있다. 다만 같은 값일 때. 데이터 주소번지를 공유함으로서 메모리의 낭비를 최소화시켰다. 이는 일반적인 트릭 중 하나이다.

또한 원시타입조차, 마치 객체인 것처럼, 메서드 호출이 가능한 것을 알 수 있다. 기본 타입(Primitives)에서 표준 빌트인 객체인 Number, String, Boolean, Symbol에 정의된 메서드를 호출할 수 있는 것이다.

▶ 원시타입의 메서드를 호출하면,

1) 순간적으로 원시타입에 해당하는 '객체'가 생성되고,
2) 이 '객체'의 메서드가 호출된다.
3) 메서드 처리가 끝나면 이 '객체'는 사라지고, 원래의 원시타입만 남는다.

특이한 점 중 또 하나는 null 과 동시에 undefined 가 있다는 점이다.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
https://curryyou.tistory.com/184

몇가지 중요하고 독특하고 기괴한? 컨셉

호이스팅

text = 'Hanamon!'; // 선언 없으면 var로 선언한 것과 동일해짐.
console.log(text); // 그래서 선언 없어도 콘솔 출력됨. 
var text;


console.log(a) // undefined
var a = 10;

console.log(b) //Runtime Error!
function test(){
    var b = 10;
}


console.log(num); // ReferenceError
num = 6; // 초기화

일반적으로 변수는 선언, 초가화, 할당 의 과정을 거친다. 그리고 일반적으로 식별자는 동일한 scope 내에서 재할당이 불가능하며, 변수는 선언되기 전에는 참조가 불가능하다. 그러나 JS에서는 식별자의 재할당이나 선언되기 전의 참조가 아주 자연스럽게 되는 것처럼 진행된다.
이는 JS가 식별자로 변수를 선언시, 자동으로 변수를 최상단으로 끌어올려서 undefined로 초기화 해 주듯이 동작을 하기 때문에 가능한 일이다. 만약 식별자가 없거나, function scope 안에 있는 변수들은 참조를 못한다.

아무튼 이러한 특징들로 인해서, 변수의 이름이 중복되어도 눈치 못 챌 가능성이 현격히 높은 언어에 속한다. ES6 이전까지는 이러한 자바스크립트의 문제점을 해결하기 위해, 즉 모듈마다 고유한 Scope을 할당해주기 위해, 즉시실행함수를 많이 썼다. 예전 자바스크립트 라이브러리를 까보다 보면 이런 코드를 많이 보게 될 것이다.

var initText;
(function (number) {
    var textList = ["is Odd Text", "is Even Text"];
    if (number % 2 == 0) {
        initText = textList[1];
    } else {
        initText = textList[0];
    }
})(5);
console.log(initText);
console.log(textList);

함수가 일급시민

사실상 무법지대


Node.js

ES6 이후의 문법

기타 도구들

몇가지 이야기들

브라우저, 서버사이드 랜더링,

WA(WebAssembly)가 등장했다. 브라우저에서 C/C++, Rust 와 같은 저수준의 언어를 바이너리 형식으로 컴파일 해주는 기술이다. 웹에서 네이티브에 가까운 속도를 낼 수 있게 해준다.

참고

시간날때마다 틈틈히 정리..

profile
시간대비효율

0개의 댓글