자바스크립트를 처리하는 엔진은 싱글 쓰레드로, 하나의 일처리밖에 처리를하지 못한다.
이것을 보완하기 위해 비동기적인 처리를 담당하는 event loop를 설정하여 해당 스레드가 처리가 완료되면 순차적으로 이벤트를 처리하는 개념이다.
컴파일러가 js 파일을 파싱하기 시작하면 우선 어휘분석기라는 스캐너를 이용하여 해당 단순 텍스트를 의미론적 단위인 토큰으로 분리한 후, 주석이나 단순 공백텍스트등을 제거하여 하나의 객체 구조를 만들어낸다.
그 후, 구문분석기인 파서를 이용하여 해당 토큰객체를 실행단위인 AST라는 트리구조로 재결합한다.
일반적으로 말하는, 실행 컨텍스트라고 하는 구조가 바로 이 해당 내용
일반적으로 사용되는 prettier, typescript, eslint등이 바로 이 AST를 활용하여 오류검증을 한다.
이 AST는 callstack이라고 하는 자료구조에 들어간 후, First In Last Out 개념대로 가장 스텍의 위에 있는 순서대로 빠져나오게 된다.
이때 AST는 Ignition이라고 하는 인터프리터를 통해 컴퓨터가 이해할 수 있는 수준의 단위인 0,1로 이루어진 바이트 코드 로 변환되어 실행되고, 자주 사용되는 코드로 확인되면 TurboFan이라는 구조에 최적화 과정을 거쳐 캐싱되었다가 재사용되고, 자주 사용되지 않는다면 자동으로 deoptimization을 실행한다.
AST, 즉 추상적 의미로 실행 컨텍스트라는 자료구조의 내용에는 아래의 내용이 들어간다.
처음 실행 컨텍스트에는 아직 초기화 단계의 정보는 들어가있지 않으며, ("init"부분)
런타임, 즉 실행 시점에서 JS엔진에 의해 참조되면서 초기화가 실행된다.
이때 초기화에 해당하는 부분은 식별자에 대한 데이터구조 바인딩(할당) 과, 함수 호출에 해당한다.
일반적인 코드는 해당 과정이 끝나고 난 후, 콜스텍에서 빠져나오면서 GC에 의해 수거된후 사라지지만,
만약 해당 런타임 실행 코드 내부에 브라우저가 파악하고 있는 비동기 이벤트가 존재한다면, 브라우저 기준으로 Web API는 해당 호출내용을 가져온 뒤, 이 호출내용을 처리하기 위한 작업을 (즉, 스코프 체인을 통해 식별자를 파악한 후 해당 단순 함수 객체의 [[call]] 슬롯을 확인해 호출가능함을 확인하면 몸체에 있는 지역 스코프의 내용을 분석하여 지역 실행 컨텍스트를 만드는 작업) 완료되는 순대로 task queue로 전달한다.
이후, callstack이 비워진다면 event loop는 해당 상태를 확인한 후, task queue에 저장되어 있는 실행컨텍스트를 차례대로 전달하여 실행시킨다.
참고로, promise와 같은 비동기 작업에 연결되어 있는 then, catch, finally의 경우
micro task queue라고 하는 특수한 자료구조에 들어가 있다가 해당 비동기 작업이 task queue에서 빠져나와 callstack에서 실행되는 순간 찾아져서 같이 실행된다.
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
즉, 위의 내용은 차례대로
script start
script end
promise1
promise2
setTimeOut
순서대로 콘솔이 찍히게 된다.
비동기 작업은 일반적인 호출과는 다른 방식으로 진행되기 떄문에, 작업 순서를 보장할 수 없다는 단점이 있다.
반드시 해당 비동기 작업이 처리된 이후 특정 작업이 이루어지도록 만들어야 한다면, 함수가 다른 함수 객체를 인자로 받아 중첩함수로 내부에서 호출할 수 있다는 특징을 이용한 callback 패턴을 사용할 수 있다.
function loadLong( callback = function(err,data){
//did something
//
//
if(error) {
callback(error);
}else{
callback(null, data);
}
})
문제는 해당 패턴이 만약 여러개의 비동기 작업이 순차적으로 실행되길 보장되어야 한다면 그만큼 계속해서 중첩함수가 늘어나 깊이가 깊어지는 콜백헬이 발생한다는 것이다.
이는 가독성이 좋지 않아 유지보수가 나빠지게 하는 원인이 된다.
이를 해결하기 위한 방법으로 고안된 것이 Promise이다.
Promise는 생성자 함수로서, new를 통해 호출하여 객체를 생성할 때 prototype에 존재하는 resolve, reject 메서드를 자동으로 인자로 받아들이는 executor 함수의 인자로 전달하면서 만들어진다.
let promise = new Promise(function(resolve, reject) {
// executor의 바디이다.
});
Promise는 자동으로 객체를 생성할 때 이 executor의 비동기 작업을 호출한 뒤 코드실행이 완료되어 callstack에서 실행 컨텍스트가 빠져나가는 것이 아니라 [[PromiseState]]에 pending을 저장한 채로 해당 비동기 작업의 처리가 끝나기를 기다린다. ( 즉, 여기서 우리는 Promise가 실행하는 비동기 작업이 또다른 쓰레드에서 실행되고 있다는 추론을 할 수 있다)
해당 작업이 완료되면 Promise는 resolve가 호출되면 state를 fulfilled로, reject가 호출되면 state를 rejected 로 변경한 뒤 콜스텍에서 빠져나간다.
만일, then, catch,finally와 같은 연쇄 메서드 호출이 존재한다면 이 마이크로테스크큐에 저장되어 있는 호출 역시 같이 처리된 이후에 콜스텍에서 빠져나간다.
async await은 비동기 작업의 문법적 설탕과 같다.
aysnc가 걸려있을 경우, 해당 함수는 어떤 결과든 해당 값을 PromiseResult 슬롯에 저장하여 리턴한다.
내부 몸체에 await이 존재하는 비동기 처리가 있을 경우, 이때는 위에서 이야기했던 마이크로테스크 큐 처리까지 모두 완료된 이후의 Promise의 result슬롯에 저장되어 있는 값을 리턴하게 된다.
blocking은 특정 실행 컨텍스트의 실행내용이 다른 실행컨텍스트의 실행을 차단하고 해당 처리가 완료되기 전까지는 다른 내용이 실행되지 않는것을 뜻한다. 이에 반대로 none-blocking은 어떤 실행 컨텍스트의 실행내용이 존재한다 하더라도 다른 실행 컨텍스트 역시 동시에 처리될 수 있는 것을 의미한다.
동기적 실행이란, 한 작업이 실행된 이후 그다음 작업이 순차적으로 실행되는 실행방식을 뜻한다.
만약 한 스레드에서 실행되고 있는 작업내용이 다른 스레드의 실행을 차단한다면 이것은 동기적 실행이라고 할 수 있다.
만약 한 스레드에서 실행되고 있는 작업내용과 무관하게 다른 스레드도 역시 작업을 실행한다면 이것은 비동기적인 작업이라고 할 수 있다.
node.js 는 프로그램이다. 즉, 운영체제에 의해 메모리공간과 static value, 처리함수, command line등을 전달받은 후 내부 프로세스에서 thread를 통해 연산작업을 하는 프로세스로서 일을 한다. 즉, 이때 node.js라는 프로세스가 돌아가기 위한 내부 구조적 환경은 여러 다른 thread를 통해 실행되고 있지만, 내부적으로 javascript 코드를 처리하는 스레드는 하나이기 때문에 싱글 쓰레드라고 여겨지는 것이다.
node.js의 js 처리부는 크롬의 v8엔진을 사용하고 있기 때문에 그 절차는 브라우저와 동일하나, webAPI 가 없는 대신 해당 행위를 libuv라고 하는 영역에서 처리하고 있고, javascript에서는 허용되지 않았던 파일을 컨트롤하는 fs 시스템 코드를 변환하여 C++ 와 같은 언어에 적용해 실행시키는 binding 파트가 존재한다.
event-driven architecture 이란 브라우저에서 볼 수 있는 addEventListner("click") 와 같이 사용자의 특정 입력행위를 이벤트로 정의하여 처리하는 방식을 뜻한다.
Node.js는 브라우저가 아니기 때문에 해당 이벤트는 존재하지 않지만, 각종 비동기 처리 작업이나 파일 작업 시스템을 위와 같이 이벤트 형식으로 저장해둔 후, 코드에서 이를 발견하면 실행시키고 있기 때문에 이벤트 기반 처리 구조라고 할 수 있다.
this란 함수가 호출될 때 만들어지는 실행 컨텍스트에서 자기 자신을 포함하고 있는 객체 혹은 new를 통해 호출될 경우 새롭게 만들어질 임시 인스턴스의 주소가 바인딩된 자기참조 식별자를 뜻한다.
자바스크립트는 정적 스코프 개념을 따른다. 즉, 함수가 실행 컨텍스트가 만들어지며 해당 토큰을 함수객체로 평가해 생성하는 순간, 이미 Environment 슬롯에 해당하는 영역에 자신을 포함한 상위 컨텍스트의 내용을 바인딩하기 떄문에 런타임 시점에서 어느 장소에서 호출된다 하더라도 해당 스코프는 변하지 않고 고정된다.
하지만, C++와 같은 타 언어에서는 함수가 정의된 시점이 아닌 함수가 호출되는 시점에서 해당 Environment가 결정되는 정적 스코프를 따르고 있다.
객체 지향적 개발이란 프로그램에서 다루어져야 하는 데이터를 상태값으로, 이 상태를 변동시키는 역할을 메서드로 구분해 객체의 단위로 묶어 개발하는 패턴이다. 해당 패턴을 통해 이룰 수 있는 결과는 다음과 같다
마치 번들처럼 하나의 캡슐형태로 데이터 상태를 정의하여 관심사를 분리하고 각 데이터 상태별로의 느슨한 결합을 이룰 수 있다.
특정한 공통 분모를 지닌 데이터 상태를 하나의 추상적인 클래스로 정의한 뒤 세부적으로 추가되어 객체를 생성할 수 있다.
자바스크립트는 프로토타입 상속을 통해 이루어지는 개념으로, 추상화 클래스의 내용을 상속받아 필요한 데이터를 추가하는 방식을 통해 불필요한 메모리 소비를 낭비하는것을 억제하고 재활용성을 늘릴 수 있다.
하나의 추상 클래스를 통해 파생되는 다양한 객체들은 각자 자신의 내부 상태에 따라서 메서드 호출을 통해 나오는 결과물이 달라진다. 이를 다형성이라고 한다. 즉, 오버로딩의 개념과 같은데 예를들어 추상화 클래스가 "동물" 이고 메서드가 울음소리이라면, 이 클래스를 통해 만들어진 "사자" 객체와 "고양이" 객체의 울음소리 메서드 호출결과는 내부 상태에 따라 달라진다. 이는 기존에 상속받은 메서드를 오버라이팅하지 않고 그대로 사용하지만 상태에 따라 결과물이 달라지는 오버로딩으로서, 필요한 메서드를 계속해서 생성하는 것이 아니라 오로지 해당 상태에 의해서 다변적인 결과물을 내는 것과 같다.
자바스크립트 엔진이 식별자를 스코프 체인으로 탐색한다면, 객체 내 프로퍼티를 탐색할 때에는 프로토타입 체인을 사용한다.
이 프로토타입 체인은 어떠한 객체가 만들어질 때 이것의 원본이 되는 생성자 함수가 평가되면서 만들어지는 함수 객체 내부 프로퍼티인 prototype에 정의되어 있는 객체를 상속받으면서 이루어진다. (내부에 constructor 프로퍼티가 기본적으로 존재한다)
예를들어,
function Tool () {
this.name = "exe"
}
const tool = new Tool();
console.log(tool);
위와 같은 정의에서 [[prototype]] 슬롯을 열어본다면, 해당 new 명령문에 의해 생성되는 인스턴스 객체에 할당된 Object.prototype 객체를 확인할 수 있다.
Object.prototype은 모든 프로토타입 체인의 종점지이다.
Immediately Invoked function expressions의 줄임말로, 어떠한 함수를 코드가 평가되는 시점에서 즉시 실행시키는 구문을 뜻한다.
해당 표현의 실행 영역 안은 독립적인 스코프를 가지고 있기 때문에 외부에서 참조를 할 수 없다는 점을 이용하여 전역 환경을 더럽히지 않고 변수를 선언하거나 이용하는 패턴이 가능하다.
예를들어,
const name = (function (a,b){
return a + b;
})(1,2);
console.log(name) // 3
위와 같이 1, 2의 값은 function 내부의 인자로 전달받아 호출되어 해당 return 결과를 곧바로 보여주는 것을 알 수 있다.
즉, 해당 1,2의 값을 따로 전역적으로 선언하지 않더라도 내부 IIFE 스코프 내부에서 정의되어 사용됨으로서 전역적 오염을 막을 수 있다.
this는 자신이 호출되는 순간 누가 자신을 호출했는지에 따라 동적으로 this 바인딩이 결정된다. setTimeOut의 경우, 비동기적인 작업이 이루어지며 첫째 인자로 들어오는 함수를 콜스텍이 비워지는 순간 전달하여 실행시킨다.
그런데 이때 첫째 인자는 누군가에 의해 호출되는 것이 아닌, 전역적으로 실행되는것과 같은 상황이 되기 떄문에 전역 즉 window가 this가 되며, 만약 해당 환경이 엄격 모드라면 undefined가 되게 된다.