Stack
자바스크립트에서 메모리 힙은 메모리가 할당되는 곳이고 호출 스택은 코드가 실행되는 곳입니다.
원시 타입이 저장되는 방식: 변수에 저장되는 것은 값 자체이므로 원시 타입은 값 타입이라고도 합니다.
객체 유형을 저장하는 방법: 변수에서 객체 "참조"에 저장되므로 객체 유형을 참조 유형이라고도 합니다.
호출 스택은 매우 간단하게 이해할 수 있습니다. 메서드가 발생하면 호출 스택으로 푸시되고 메서드가 실행되어 스택을 터뜨리면 각 메서드를 호출 프레임이라고 합니다.
이벤트 루프
스택을 이해했으니 이제 스택과 관련된 이벤트 루프로 이동해 보겠습니다.
먼저 명확히 해야 할 것은 자바스크립트는 단일 스레드 언어이고 모든 코드가 스레드에서 실행되기 때문에 일반적으로 메서드가 너무 오래 걸리면 페이지 전체가 멈추는 문제가 발생하므로 이러한 상황을 피하기 위해 자바스크립트에는 이벤트 루프 메커니즘이 있어 이벤트 실행을 반복하고 나중에 루프를 통해 이벤트를 차단한 다음 인터페이스 요청이 완료되었는지 확인하고 실행이 완료되었는지 확인한 다음 나중에 인터페이스 요청이 완료되었는지 확인한 다음 요청이 완료된 후 해당 콜백 함수를 실행합니다.
이벤트 루프는 또한 작업이 동기식 작업과 비동기식 작업으로 나뉘고 작업이 순서대로 실행된다는 것을 의미합니다.
이벤트 루프에서 중요한 개념은 매크로 작업과 마이크로 작업이며, 매크로 작업은 함수를 실행하는 스레드의 첫 번째 라운드이고, 마이크로 작업은 작업 내부의 매크로 작업이며, 프로세스와 스레드의 관계와 유사하게 매크로 작업은 프로세스, 마이크로 작업은 스레드이며, 다음은 세 가지 간의 관계를 살펴 봅니다:
실제로 이벤트 루프는 매크로태스크와 마이크로태스크에 대해 반복되며, 매크로태스크에 마이크로태스크가 있으면 그 안에 있는 마이크로태스크가 실행됩니다.
다음은 자바스크립트에서 어떤 함수가 매크로 작업이고 어떤 함수가 마이크로 작업인지 정확히 살펴보는 것입니다:
실행 프로세스를 구체적으로 살펴보세요:
- 전체 스크립트는 첫 번째 매크로 작업으로 메인 스레드에 들어갑니다;
- 설정 시간 초과, 설정 간격이 발생하면 해당 콜백 함수가 매크로 작업 이벤트 큐로 디스패치됩니다;
- process.nextTick()이 발생하면 해당 콜백 함수가 마이크로태스크 이벤트 대기열로 디스패치됩니다;
- Promise, new 및 Promise 함수 본문의 내용이 발생하면 바로 실행됩니다. 그러면 다른 콜백이 마이크로태스크 이벤트 큐로 디스패치됩니다;
- 예를 들어 마이크로태스크가 첫 번째 매크로 태스크에 속하는 경우 첫 번째 매크로 태스크가 실행된 후에 마이크로태스크가 실행되기 시작합니다. 즉, 스크립트 안에 마이크로태스크가 있는 경우 두 번째 매크로 라운드(예: setTimeout 등)가 실행되기 전에 마이크로태스크가 실행됩니다;
- 첫 번째 실행 라운드가 완료된 후, 두 번째 라운드의 시작, 즉 매크로 작업의 두 번째 라운드의 내용 내부에 설정된 시간 초과, 설정 간격 콜백 함수, 마이크로 작업이 포함 된 경우 시작의 구현 완료의 구현 내용 내부의 콜백 함수 직후에 시작됩니다;
- 마이크로태스크 안에 마이크로태스크가 포함되어 있으면 그 바로 뒤에 실행이 시작되는 것은 외부 마이크로태스크입니다.
구체적인 예시를 살펴보겠습니다:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
첫 번째 이벤트 루프 흐름은 다음과 같이 분석됩니다:
- 전체 스크립트는 첫 번째 매크로 작업으로 메인 스레드에 들어가고, console.log를 만나고, 1을 출력합니다.
- setTimeout이 발생하고 해당 콜백 함수가 매크로 작업 이벤트 큐에 배포됩니다. 잠정적으로 이것은 setTimeout1로 표시됩니다.
- 마이크로태스크 이벤트 대기열에 콜백 함수가 배포되는 process.nextTick()을 만납니다. process1로 기록됩니다.
- "약속"을 만나면 new "약속"이 직접 실행되고 출력 7. 이후 마이크로태스크 이벤트 대기열에 배포됩니다. 그때 1로 기록됩니다.
- SetTimeout이 다시 발생하고 해당 콜백 함수가 매크로 작업 이벤트 대기열에 setTimeout2로 표시된 매크로 작업 이벤트 대기열로 배포됩니다.
| setTimeout1 | process1 |
| setTimeout2 | then1 |
- 위의 표는 1과 7이 출력된 이벤트 루프 매크로 작업의 첫 번째 라운드가 끝났을 때 각 이벤트 큐의 상황을 보여줍니다.
- 프로세스1과 다음1이라는 두 개의 마이크로태스크가 발견되었습니다.
- 프로세스1, 출력 6을 실행합니다.
- then1을 실행하고 8을 출력합니다.
첫 번째 이벤트 루프가 공식적으로 끝났고 이 라운드의 결과는 출력 1, 7, 6, 8입니다. 따라서 두 번째 시간 루프는 setTimeout1 매크로 작업으로 시작됩니다:
- 첫 번째 출력은 2입니다. 다음으로 process.nextTick()이 발생하고, 이 역시 마이크로태스크 이벤트 큐에 process2. new Promise로 배포되며 즉시 실행됩니다. 출력 4 역시 그때2로 표시된 마이크로태스크 이벤트 큐에 배포됩니다.
| setTimeout2 | then1 |
| then2 |
- 이벤트 루프 매크로태스크의 두 번째 라운드는 실행할 수 있는 두 개의 마이크로태스크, process2와 then2가 있다는 것을 발견하는 것으로 끝납니다.
- 출력 3.
- 출력 3.
- 두 번째 이벤트 루프는 두 번째 출력 2, 4, 3, 5로 끝납니다.
- 세 번째 이벤트 루프가 시작되며, 이 시점에서는 setTimeout2만 실행됩니다.
- 직접 출력 9.
- process.nextTick()을 마이크로태스크 이벤트 대기열에 배포합니다. process3로 기록합니다.
- new Promise를 직접 실행하여 11번을 출력합니다.
- 그런 다음 그때3으로 표시된 마이크로태스크 이벤트 대기열에 배포합니다.
| process3 |
| then3 |
- 이벤트 루프 매크로 태스크 실행의 세 번째 라운드는 두 개의 마이크로태스크 process3과 then3의 실행으로 끝납니다.
- 출력 10.
- 출력 10.
- 세 번째 이벤트 루프는 세 번째 출력 9, 11, 10, 12로 끝납니다.
코드 전체에서 세 개의 이벤트 루프가 수행되며 전체 출력은 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12...
실행 컨텍스트
실행 컨텍스트로 넘어가서, 간단히 말해 실행 컨텍스트는 자바스크립트 코드가 평가되고 실행되는 환경의 추상화입니다. 자바스크립트 코드가 실행될 때마다 실행 컨텍스트에서 실행됩니다.
자바스크립트에는 세 가지 실행 컨텍스트 유형이 있습니다:
- 전역 실행 컨텍스트 - 기본 또는 기본 컨텍스트이며, 함수 안에 있지 않은 모든 코드는 전역 컨텍스트에 있습니다. 전역 창 객체를 생성하고 this의 값을 전역 객체와 동일하게 설정하는 두 가지 작업을 수행합니다. 프로그램에는 하나의 전역 실행 컨텍스트만 존재합니다.
- 함수 실행 컨텍스트 - 함수가 호출될 때마다 해당 함수에 대한 새 컨텍스트가 만들어집니다. 각 함수에는 고유한 실행 컨텍스트가 있지만 함수가 호출될 때 생성됩니다. 함수 컨텍스트는 여러 개가 있을 수 있습니다. 새 실행 컨텍스트가 생성될 때마다 정의된 순서대로 일련의 단계를 수행합니다.
- 평가 함수 실행 컨텍스트 - 평가 함수 내부에서 실행되는 코드에도 자체 실행 컨텍스트가 있지만 자바스크립트 개발자는 평가 함수를 자주 사용하지 않으므로 여기서는 설명하지 않습니다.
요약하자면, 실행 컨텍스트는 크게 전역과 함수 실행 컨텍스트, 즉 실행 환경으로 나뉘는데, 함수는 외부 함수의 변수를 읽을 수 있으며, 일반적으로 클로저라고도 하며, 이 원리를 통해 정적 언어에 비해 외부 파라미터를 더 유연하게 얻을 수 있습니다.
실행 컨텍스트의 차이는 '이' 값의 내용 차이로 직접 이어집니다.
또한 모든 실행 컨텍스트의 모든 메서드가 실행 스택을 공유하는 대신 실행 컨텍스트가 그 위에 실행 스택을 생성합니다.
범위
스코핑의 이 요소는 매우 간단합니다. 기본적으로 스코핑은 모든 언어에 존재하며, 자바스크립트에서 한 가지 주목할 점은 함수에서 생성된 값은 호출이 아닌 생성 시점에 얻어지는 값이라는 것입니다:
let x = 10
function fn() {
x = 20
console.log(x)
}
function foo() {
x = 30
fn() // 20
}
foo()
위 코드에 출력된 값은 여전히 20인데, 이는 fn 함수가 호출될 때 foo 함수 범위가 아닌 해당 범위에서 20의 값으로 생성되었기 때문입니다.
아래 코드를 참고하세요:
let x = 10
function fn() {
console.log(x)
}
function foo() {
x = 30
fn() // 30
}
foo()
위의 코드는 30을 출력하는데, 무슨 일이 일어나고 있는지, 값이 다시 생성되는 위치에서 값을 가져온다고 하지 않나요?
대답은 실제로 생성된 위치에 있지만 먼저 실행된 foo 함수가 바깥쪽 x의 값을 변경했다는 것입니다. 다음 코드가 이를 설명합니다:
let x = 10
function fn() {
console.log(x)
}
function foo() {
let x = 30
fn() // 10
}
foo()
보시다시피, 출력되는 것은 실제로 foo 함수의 값이 아니라 함수가 생성된 시점의 값입니다.
그런 다음 생성 시점의 가치가 무엇인지 이해하는 것이 중요한데, 여기서 범위 체인 또는 가져온 값의 체인이라는 개념이 등장합니다:
- 이제 현재 범위에서 a를 찾습니다. 있으면 가져와서 끝내고 없으면 계속합니다;
- 현재 범위가 전역 범위인 경우 a가 정의되지 않았음을 증명하고 종료하고, 그렇지 않으면 계속합니다;
- 함수가 생성된 범위를 현재 범위로 사용합니다;
- 1단계로 이동합니다.
var a = 10
function fn() {
var b = 20
function bar() {
console.log(a) // 10
console.log(b) // 20
}
return bar
}
var x = fn()
var b = 200
x()
요약하자면
함수 컨텍스트 환경은 함수가 실행될 때 생성되며, 컨텍스트에 해당 변수가 생성됩니다. 같은 함수라도 전달된 매개변수에 따라 다른 변수를 갖게 됩니다;
범위는 함수가 생성될 때 생성되며, 범위 범위는 솔직히 말해서 호출 여부와 상관없이 이 함수가 이 땅을 가지고 있는 자신의 땅의 함수입니다;
컨텍스트 환경은 호출될 때만 생성되며, 다른 매개변수를 전달하는 등 여러 컨텍스트 환경이 생성될 수 있으며, 컨텍스트 환경은 솔직히 해당 환경에서 변수 값을 사용할 수 있는 환경입니다.
클로저
이전 섹션에서는 다른 함수 내부의 변수를 읽을 수 있는 함수인 클로저를 소개하는 것이 주된 목적이었습니다. 자바스크립트에서는 함수 내의 하위 함수만 로컬 변수를 읽을 수 있으므로 클로저는 "함수 내부에 정의된 함수"로 해석할 수 있습니다. 본질적으로 클로저는 함수의 내부와 외부를 연결하는 다리 역할을 합니다.
다음은 클로저가 사용되는 두 가지 형태를 살펴봅니다:
첫째, 반환 값으로 함수를 사용합니다:
function fn() {
var max = 100
return function bar(x) {
if (x > max) {
console.log(x)
}
}
}
var f1 = fn()
f1(115)
위에서 반환된 내부 함수는 외부 fn 함수의 최대값을 읽는 클로저이며, 이 경우 다음 사례도 클로저입니다:
var max = 100
function fn() {
console.log(max)
}
fn()
위의 두 코드에서 볼 수 있듯이 실제로 함수가 외부 변수 내부에서 읽을 수있는 한 모든 함수는 클로저라고 할 수 있습니다. 즉, 함수는 적어도 변수의 전역 환경을 읽을 수 있기 때문에 모든 함수는 클로 저이며 두 번째 코드 만 일반적으로 클로저의 일반적인 사용, 형식 또는 함수를 반환 값으로 사용하는 일반적인 사용은 아닙니다.
둘째, 함수는 인자로 전달됩니다:
var max = 10
var fn = function (x) {
if (x > 100) {
console.log(x) // 아무것도 인쇄하지 않음
}
}
;(function (f) {
var max = 100
f(15)
})(fn)
함수가 다른 함수의 내용으로 다른 함수에 인수로 전달되는 경우, 이 시점에서 전달된 함수는 클로저이며, 이전 범위 원칙에 따라 여기서 최대값은 호출 시점이 아닌 최대값의 함수 정의 시점에 읽혀집니다.




