자바스크립트를 더 깊이 배우고 싶다면 다음 글을 읽어보시면 도움이 될 것입니다.
머리말
훌륭한 프론트엔드 엔지니어가 되고 싶거나, 자바스크립트를 심도있게 배우고 싶다면 비동기 프로그래밍은 필수 지식 포인트이자, 중급, 고급, 고급을 구별하는 기초 중 하나이기도 합니다. 프론트엔드. 비동기 프로그래밍에 대한 명확한 개념이 없다면 JavaScript 비동기 프로그래밍을 배우는 데 시간을 할애하는 것이 좋습니다. 비동기 프로그래밍에 대한 고유한 이해가 있다면 이 기사를 읽고 함께 소통하는 것이 좋습니다.
동기화 및 비동기
비동기를 도입하기 전에, 소위 동기 프로그래밍이란 컴퓨터가 코드를 한 줄씩 순서대로 실행한다는 것을 검토해 보겠습니다. 현재 코드 작업을 실행하는 데 시간이 많이 걸리므로 후속 코드의 실행이 차단됩니다.
동기 프로그래밍은 일반적인 요청-응답 모델입니다. 요청이 함수나 메서드를 호출하면 응답이 반환될 때까지 기다린 후 후속 코드를 실행해야 합니다.
일반적인 상황에서 동기식 프로그래밍은 코드가 순서대로 실행되므로 프로그램 실행을 확실히 보장할 수 있습니다. 그러나 파일 내용을 읽거나 서버 인터페이스 데이터를 요청하는 등의 일부 시나리오에서는 반환이 필요합니다. 내용에 따른 데이터 후속 작업을 수행하고 데이터가 반환될 때까지 파일을 읽고 인터페이스를 요청하는 데 시간이 걸립니다. 네트워크 상태가 좋지 않을수록 JavaScript는 다른 데이터를 처리할 수 없습니다. 데이터가 반환되기를 기다리는 동안 페이지 상호 작용 및 스크롤과 같은 모든 작업도 차단됩니다. 이는 분명히 비동기 프로그래밍이 그 재능을 보여줘야 하는 시나리오입니다. 아래에서는 시간이 많이 걸리는 작업 A가 차단됩니다. 작업 B를 실행하려면 작업 A가 완료될 때까지 기다렸다가 B를 계속 실행하세요.
비동기 프로그래밍을 사용하는 경우 기다리기 전에 후속 코드를 계속 실행할 수 있습니다. 현재 작업의 응답이 반환됩니다. 즉, 현재 실행 작업이 후속 실행을 차단하지 않습니다.
비동기 프로그래밍은 동기 프로그래밍의 요청-응답 모드와 다릅니다. 요청이 함수나 메서드를 호출한 후 즉시 응답을 기다릴 필요가 없습니다. 다른 작업을 수행하기 위해 상태, 알림 및 콜백을 반환한 후 이전 작업 응답을 전달하여 호출자에게 알릴 수 있습니다.
멀티스레딩
앞서 설명했듯이 비동기 프로그래밍은 동기 프로그래밍의 차단 문제를 효과적으로 해결할 수 있습니다. 그렇다면 비동기 프로그래밍을 구현하는 방법은 무엇일까요? 비동기 구현을 달성하는 일반적인 방법은 C#과 같은 멀티스레딩입니다. 즉, 여러 스레드가 동시에 시작되고, 아래에 표시된 것처럼 시간이 많이 걸리는 작업 A가 실행되는 동안 서로 다른 작업을 병렬로 실행할 수 있습니다. , 작업 B는 스레드 2에서도 실행될 수 있습니다.
JavaScript 단일 스레드
JavaScript 언어 실행 환경은 단일 스레드가 프로그램을 실행할 때 사용되는 프로그램 경로가 연속 순서로 정렬됩니다. 첫 번째 작업은 이후 작업이 실행되기 전에 처리되어야 합니다. 비동기 구현을 사용하면 여러 작업이 동시에 실행될 수 있습니다. 그렇다면 JavaScript에서 비동기 프로그래밍을 구현하는 방법은 무엇입니까? 다음 섹션에서는 비동기 메커니즘에 대해 자세히 설명합니다.
병렬 및 동시성
앞서 언급했듯이 다중 스레드 작업은 병렬로 실행될 수 있는 반면 JavaScript 단일 스레드 비동기 프로그래밍은 다중 작업의 동시 실행을 달성할 수 있습니다. 병렬성과 동시성의 차이점을 설명할 필요가 있습니다.
병렬은 동시에 수행되는 여러 작업을 의미합니다.
동시성은 동시에 수행되는 여러 작업을 의미하지만 특정 순간에는 하나의 작업만 실행됩니다. 동시 연결 수는 브라우저가 서버에 요청을 시작하고 TCP 연결을 설정할 때 서버가 초당 설정하는 총 연결 수를 의미합니다. 서버가 10ms 내에 하나의 연결을 처리할 수 있는 경우 동시 연결 수는 다음과 같습니다. 100. JavaScript 비동기 메커니즘
이 섹션에서는 JavaScript 비동기 메커니즘을 소개합니다. 먼저 예를 살펴보겠습니다. for (var i = 0; i < 5; i ++) {
setTimeout(function(){
console.log(i);
}, 0);
}
console.log(i);
//5 ; 5 ; 5 ; 5; 5 최종 출력은 모두 5입니다.
i다음은 for가 있는 컨텍스트의 변수입니다. 그리고 루프 끝에 i가 하나만 있습니다.
i==5;
JavaScript 단일 스레드 이벤트 프로세서는 스레드가 유휴 상태가 될 때까지 다음 이벤트를 실행하지 않습니다. .
위의 세 번째 항목에서 언급했듯이 위 예제의 setTimeout()과 JavaScript 비동기 메커니즘을 제대로 이해하려면 JavaScript 이벤트 루프와 동시성 모델을 이해해야 합니다. 동시성 모델
현재 우리는 JavaScript가 비동기 작업을 수행할 때 응답이 반환될 때까지 기다릴 필요가 없으며 응답이 반환되면 알림을 받고 콜백을 통해 다른 작업을 계속 수행할 수 있다는 것을 이미 알고 있습니다. 또는 이벤트 핸들러가 실행됩니다. 그렇다면 이 모든 작업은 정확히 어떻게 이루어지며, 어떤 규칙이나 순서로 작동합니까? 다음으로 우리는 이 질문에 답해야 합니다.
참고: 콜백과 이벤트 핸들러 사이에는 기본적으로 차이가 없으며 단지 상황에 따라 다르게 호출될 뿐입니다.
앞서 언급한 것처럼 JavaScript 비동기 프로그래밍을 사용하면 여러 작업을 동시에 실행할 수 있으며, 이 기능을 구현하는 기반은 JavaScript가 이벤트 루프 기반의 동시성 모델을 가지고 있다는 것입니다. 스택 및 큐
JavaScript 동시성 모델을 소개하기 전에 스택과 큐의 차이점을 간략하게 소개하겠습니다.
힙: 메모리의 차단되지 않은 영역, 일반적으로 객체(참조 유형)를 저장합니다. 데이터 구조는 후입선출 순서로 저장되며 일반적으로 함수 매개변수와 기본 유형 값 변수(값으로 액세스)를 저장합니다.
큐: 데이터 구조는 선입선출 순서로 저장됩니다. .
Event Loop JavaScript 엔진은 JavaScript 코드를 구문 분석하고 실행하는 역할을 담당하지만 단독으로 실행할 수는 없지만 일반적으로 호스트 환경, 일반적으로 브라우저 또는 Node 서버가 필요하며 위에서 언급한 단일 스레드를 생성하는 것을 말합니다. 이러한 호스트 환경의 단일 스레드는 JavaScript 엔진을 호출하여 여러 JavaScript 코드 블록의 예약 및 실행을 완료하는 메커니즘을 제공합니다(예, JavaScript 코드는 블록 단위로 실행됩니다). 이 메커니즘을 이벤트 루프라고 합니다. . -
注:这里的事件与DOM事件不要混淆,可以说这里的事件包括DOM事件,所有异步操作都是一个事件,诸如ajax请求就可以看作一个request请求事件。
JavaScript执行环境中存在的两个结构需要了解:
注:关于全局代码,由于所有的代码都是在全局上下文执行,所以执行栈顶总是全局上下文就很容易理解,直到所有代码执行完毕,全局上下文退出执行栈,栈清空了;也即是全局上下文是第一个入栈,最后一个出栈。
任务
分析事件循环流程前,先阐述两个概念,有助于理解事件循环:同步任务和异步任务。
任务很好理解,JavaScript代码执行就是在完成任务,所谓任务就是一个函数或一个代码块,通常以功能或目的划分,比如完成一次加法计算,完成一次ajax请求;很自然的就分为同步任务和异步任务。同步任务是连续的,阻塞的;而异步任务则是不连续,非阻塞的,包含异步事件及其回调,当我们谈及执行异步任务时,通常指执行其回调函数。
事件循环流程
关于事件循环流程分解如下:
宿主环境为JavaScript创建线程时,会创建堆(heap)和栈(stack),堆内存储JavaScript对象,栈内存储执行上下文;
栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件时(或该异步操作响应返回时),需向消息队列插入一个事件消息;
当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及回调);
当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被丢弃,执行完任务后退栈;
当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick,事件循环流转一次称为一次tick)。
使用代码可以描述如下: var eventLoop = []; var event; var i = eventLoop.length - 1; // 后进先出
while(eventLoop[i]) { event = eventLoop[i--];
if (event) { // 事件回调存在
event();
} // 否则事件消息被丢弃
} 这里注意的一点是等待下一个事件消息的过程是同步的。
并发模型与事件循环 var ele = document.querySelector('body'); function clickCb(event) { console.log('clicked');
} function bindEvent(callback) {
ele.addEventListener('click', callback);
}
bindEvent(clickCb); 针对如上代码我们可以构建如下并发模型:
如上图,当执行栈同步代码块依次执行完直到遇见异步任务时,异步任务进入等待状态,通知线程,异步事件触发时,往消息队列插入一条事件消息;而当执行栈后续同步代码执行完后,读取消息队列,得到一条消息,然后将该消息对应的异步任务入栈,执行回调函数;一次事件循环就完成了,也即处理了一个异步任务。
再谈JavaScript 동기 및 비동기 프로그래밍 예제 사용 정보
了解了JavaScript事件循环后我们再看前文关于JavaScript 동기 및 비동기 프로그래밍 예제 사용 정보 的例子就比较清晰了:
JavaScript 동기 및 비동기 프로그래밍 예제 사용 정보 所表达的意思是:等待0秒后(这个时间由第二个参数值确定),往消息队列插入一条定时器事件消息,并将其第一个参数作为回调函数;而当执行栈内同步任务执行完毕时,线程从消息队列读取消息,将该异步任务入栈,执行;线程空闲时再次从消息队列读取消息。
再看一个实例: var start = +new Date(); var arr = [];
setTimeout(function(){ console.log('time: ' + (new Date().getTime() - start));
},10); for(var i=0;i<=1000000;i++){
arr.push(i);
} 执行多次输出如下:
在setTimeout 异步回调函数里我们输出了异步任务注册到执行的时间,发现并不等于我们指定的时间,而且两次时间间隔也都不同,考虑以下两点:
所以异步执行时间不精确是必然的,所以我们有必要明白无论是同步任务还是异步任务,都不应该耗时太长,当一个消息耗时太长时,应该尽可能的将其分割成多个消息。
Web Workers
每个Web Worker或一个跨域的iframe都有各自的堆栈和消息队列,这些不同的文档只能通过postMessage方法进行通信,当一方监听了message事件后,另一方才能通过该方法向其发送消息,这个message事件也是异步的,当一方接收到另一方通过postMessage方法发送来的消息后,会向自己的消息队列插入一条消息,而后续的并发流程依然如上文所述。
JavaScript异步实现
关于JavaScript的异步实现,以前有:回调函数,发布订阅模式,Promise三类,而在ES6中提出了生成器(Generator)方式实现,关于回调函数和发布订阅模式实现可参见另一篇文章,后续将推出一篇详细介绍Promise和Generator。
以上就是javascript同步与异步的全部内容了,感谢大家的阅读。
|