>  기사  >  웹 프론트엔드  >  JavaScript 실행 메커니즘을 완전히 이해합니다.

JavaScript 실행 메커니즘을 완전히 이해합니다.

coldplay.xixi
coldplay.xixi앞으로
2020-06-16 16:47:491637검색

JavaScript 실행 메커니즘을 완전히 이해합니다.

이 글의 목적은 여러분이 자바스크립트의 실행 메커니즘을 완전히 이해하도록 하는 것입니다. 이 글을 읽은 후에도 여전히 이해가 되지 않는다면 저를 때리셔도 됩니다.

JavaScript 초보자이든 경험자이든, 취업 면접이든 일상적인 개발 작업이든 우리는 종종 다음과 같은 상황에 직면합니다. 몇 줄의 코드가 주어지면 출력 내용과 순서를 알아야 합니다. javascript는 단일 스레드 언어이기 때문에 다음과 같은 결론을 내릴 수 있습니다.

  • javascript는 명령문이 나타나는 순서대로 실행됩니다.

이 내용을 보면 독자들이 충격을 받을 것입니다. js가 실행되는 줄을 모르나요? 줄로? 그런 말까지 할 필요가 있나요? js는 한 줄씩 실행되므로 js는 다음과 같다고 생각합니다.

let a = '1';console.log(a);let b = '2';console.log(b);复制代码

그러나 실제로 js는 다음과 같습니다.

setTimeout(function(){    console.log('定时器开始啦')
});new Promise(function(resolve){    console.log('马上执行for循环啦');    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){    console.log(&#39;执行then函数啦&#39;)
});console.log(&#39;代码执行结束&#39;);复制代码

Follow js 개념 출력된 결과를 자신있게 적었습니다:

//"定时器开始啦"//"马上执行for循环啦"//"执行then函数啦"//"代码执行结束"复制代码

확인하러 크롬에 갔는데 결과가 완전히 틀려서 한 줄씩 실행했나요? 선?

우리는 자바스크립트의 실행 메커니즘을 철저하게 이해해야 합니다.

1. javascript에 대하여

javascript는

단일 스레드 언어입니다. Web-Worker는 최신 HTML5에서 제안되지만 Javascript의 핵심은 단일 스레드로 변경되지 않았습니다. 따라서 "멀티스레딩"의 모든 JavaScript 버전은 단일 스레드로 시뮬레이션되며 모든 JavaScript 멀티스레드는 종이 호랑이입니다!

2.javascript 이벤트 루프

js는 단일 스레드이기 때문에 창구가 하나뿐인 은행과 같습니다. 마찬가지로 고객은 비즈니스를 하나씩 처리하기 위해 줄을 서야 합니다. 마찬가지로 js 작업도 하나씩 실행해야 합니다. 한 작업이 너무 오래 걸리면 다음 작업도 기다려야 합니다. 그렇다면 질문이 생깁니다. 뉴스를 탐색하고 싶지만 뉴스에 포함된 초고해상도 사진이 매우 느리게 로드되는 경우 사진이 완전히 표시될 때까지 웹 페이지가 계속 멈춰 있어야 합니까? 따라서 똑똑한 프로그래머는 작업을 두 가지 범주로 나눕니다.

    동기 작업
  • 비동기 작업
웹 사이트를 열 때 웹 페이지의 렌더링 프로세스는 페이지 뼈대 렌더링과 같은 많은 동기 작업입니다. 페이지 요소. 사진, 음악 로딩 등 리소스를 많이 차지하고 시간이 오래 걸리는 작업은 비동기 작업입니다. 이 부분에 대해서는 엄격한 텍스트 정의가 있지만 이 글의 목적은 최소한의 학습 비용으로 실행 메커니즘을 철저히 이해하는 것이므로 지도를 사용하여 설명합니다.

지도가 원하는 콘텐츠 즉,

    동기 및 비동기 작업은 각각 다른 실행 "장소"에 들어가고, 동기적으로 메인 스레드에 들어가고, 비동기적으로 이벤트 테이블에 들어가고 함수를 등록합니다.
  • 지정된 작업이 완료되면 이벤트 테이블이 이 기능을 이벤트 큐로 이동합니다.
  • 메인 스레드의 작업이 실행되고 비어 있으면 이벤트 큐로 이동하여 해당 함수를 읽고 실행을 위해 메인 스레드에 들어갑니다.
  • 위의 과정이 지속적으로 반복되는데, 이를 흔히 이벤트 루프라고 합니다.
메인 스레드 실행 스택이 비어 있는지 어떻게 알 수 있는지 묻지 않을 수 없습니다. js 엔진에는 메인 스레드 실행 스택이 비어 있는지 지속적으로 확인하는 모니터링 프로세스가 있습니다. 일단 비어 있으면 이벤트 큐로 이동하여 호출 대기 중인 함수가 있는지 확인합니다.

말이 너무 많아서 코드를 직접 작성하는 것이 좋습니다.

let data = [];
$.ajax({    url:www.javascript.com,    data:data,    success:() => {        console.log('发送成功!');
    }
})console.log('代码执行结束');复制代码
위는 간단한 ajax 요청 코드입니다.

ajax请求代码:

  • ajax进入Event Table,注册回调函数success
  • 执行console.log('代码执行结束')
  • ajax事件完成,回调函数success进入Event Queue。
  • 主线程从Event Queue读取回调函数success并执行。

相信通过上面的文字和代码,你已经对js的执行顺序有了初步了解。接下来我们来研究进阶话题:setTimeout。

3.又爱又恨的setTimeout

大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:

setTimeout(() => {    console.log('延时3秒');
},3000)复制代码

渐渐的setTimeout

ajax는 이벤트 테이블에 들어가 콜백 함수를 등록합니다. 성공. 🎜🎜 console.log('코드 실행 종료')를 실행하세요. 🎜🎜ajax 이벤트가 완료되고 콜백 함수 success가 이벤트 큐에 들어갑니다. 🎜🎜메인 스레드는 이벤트 큐에서 콜백 함수 success를 읽고 실행합니다. 🎜🎜🎜위의 텍스트와 코드를 통해 여러분은 이미 js의 실행 순서를 미리 이해했다고 믿습니다. 다음으로 고급 주제인 setTimeout을 살펴보겠습니다. 🎜

3. 애증의 setTimeout🎜🎜더 이상 말이 필요 없는 그 유명한 setTimeout은 모두의 첫인상입니다. 비동기 실행이 지연될 수 있습니다. 우리는 종종 다음과 같이 3초의 지연을 구현합니다. 🎜
setTimeout(() => {
    task();
},3000)console.log('执行console');复制代码
🎜 점차적으로 setTimeout이 더 많은 곳에서 사용되며 때로는 지연이 3초로 명확하게 기록되는 경우도 있습니다. . 하지만 실제로는 함수를 실행하는 데 5~6초가 걸립니다. 무슨 일인가요? 🎜

先看一个例子:

setTimeout(() => {
    task();
},3000)console.log('执行console');复制代码

根据前面我们的结论,setTimeout是异步的,应该先执行console.log这个同步任务,所以我们的结论是:

//执行console//task()复制代码

去验证一下,结果正确!
然后我们修改一下前面的代码:

setTimeout(() => {
    task()
},3000)

sleep(10000000)复制代码

乍一看其实差不多嘛,但我们把这段代码在chrome执行一下,却发现控制台执行task()需要的时间远远超过3秒,说好的延时三秒,为啥现在需要这么长时间啊?

这时候我们需要重新理解setTimeout的定义。我们先说上述代码是怎么执行的:

  • task()进入Event Table并注册,计时开始。
  • 执行sleep函数,很慢,非常慢,计时仍在继续。
  • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
  • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

上述的流程走完,我们知道setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

我们还经常遇到setTimeout(fn,0)这样的代码,0秒后执行又是什么意思呢?是不是可以立即执行呢?

答案是不会的,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。举例说明:

//代码1console.log('先执行这里');
setTimeout(() => {    console.log('执行啦')
},0);复制代码
//代码2console.log('先执行这里');
setTimeout(() => {    console.log('执行啦')
},3000);复制代码

代码1的输出结果是:

//先执行这里//执行啦复制代码

代码2的输出结果是:

//先执行这里// ... 3s later// 执行啦复制代码

关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。有兴趣的同学可以自行了解。

4.又恨又爱的setInterval

上面说完了setTimeout,当然不能错过它的孪生兄弟setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。

5.Promise与process.nextTick(callback)

传统的定时器我们已经研究过了,接着我们探究Promiseprocess.nextTick(callback)的表现。

Promise的定义和功能本文不再赘述,不了解的读者可以学习一下阮一峰老师的Promise。而process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

我们进入正题,除了广义的同步任务和异步任务,我们对任务有更精细的定义:

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick

不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。听起来有点绕,我们用文章最开始的一段代码说明:

setTimeout(function() {    console.log('setTimeout');
})new Promise(function(resolve) {    console.log('promise');
}).then(function() {    console.log('then');
})console.log('console');复制代码
  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。(注册过程与上同,下文不再描述)
  • 接下来遇到了Promisenew Promise立即执行,then函数分发到微任务Event Queue。
  • 遇到console.log(),立即执行。
  • 好啦,整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
  • ok,第一轮事件循环结束了,我们开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
  • 结束。

事件循环,宏任务,微任务的关系如图所示:

我们来分析一段较复杂的代码,看看你是否真的掌握了js的执行机制:

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')
    })
})复制代码

第一轮事件循环流程分析如下:

  • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
  • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1
  • 遇到Promisenew Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2
宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1
  • 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。

  • 我们发现了process1then1两个微任务。

  • 执行process1,输出6。
  • 执行then1,输出8。

好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:

  • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue 微任务Event Queue
setTimeout2 process2

then2
  • 第二轮事件循环宏任务结束,我们发现有process2then2两个微任务可以执行。
  • 输出3。
  • 输出5。
  • 第二轮事件循环结束,第二轮输出2,4,3,5。
  • 第三轮事件循环开始,此时只剩setTimeout2了,执行。
  • 直接输出9。
  • process.nextTick()分发到微任务Event Queue中。记为process3
  • 直接执行new Promise,输出11。
  • then分发到微任务Event Queue中,记为then3
宏任务Event Queue 微任务Event Queue

process3

then3
  • 이벤트 루프 매크로 작업 실행의 세 번째 라운드가 종료되고 두 개의 마이크로 작업 process3then3이 실행됩니다. process3then3
  • 输出10。
  • 输出12。
  • 第三轮事件循环结束,第三轮输出9,11,10,12。

整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

6.写在最后

(1)js的异步

我们从最开头就说javascript是一门单线程语言,不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的,牢牢把握住单线程这点非常重要。

(2)事件循环Event Loop

事件循环是js实现异步的一种方法,也是js的执行机制。

(3)javascript的执行和运行

执行和运行有很大的区别,javascript在不同的环境下,比如node,浏览器,Ringo等等,执行方式是不同的。而运行大多指javascript解析引擎,是统一的。

(4)setImmediate

微任务和宏任务还有很多种类,比如setImmediate

출력 10.

출력 12.
  • 이벤트 루프의 세 번째 라운드가 종료되고 세 번째 라운드에서는 9, 11, 10, 12가 출력됩니다.
  • 전체 코드는 총 3개의 이벤트 루프를 실행했으며, 전체 출력은 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12입니다.
    (노드 환경에서의 이벤트 모니터링은 libuv에 의존하므로 프론트엔드 환경과 정확히 동일하지 않으며 출력 순서에 오류가 있을 수 있으니 주의하세요)

6. 마지막에 작성

(1) js의 비동기

우리는 처음부터 javascript가 새로운 것이 무엇이든 단일 스레드 언어 프레임워크의 새로운 구문 설탕으로 구현된 소위 비동기식은 실제로 동기식 방법을 사용하여 시뮬레이션됩니다. 단일 스레드를 확실하게 파악하는 것이 매우 중요합니다.

(2) 이벤트 루프 이벤트 루프

이벤트 루프는 js가 비동기성을 달성하기 위한 방법이자 js의 실행 메커니즘이기도 합니다.

(3) JavaScript 실행 및 실행 JavaScript는 노드, 브라우저, 링고 등 다양한 환경에서 동작하는 것과 실행하는 것에는 큰 차이가 있습니다. 등, 실행이 다릅니다. 그리고 실행한다는 것은 대부분 통일된 자바스크립트 파싱 엔진을 가리킨다.

(4)setImmediate🎜🎜setImmediate 등과 같은 다양한 유형의 마이크로태스크와 매크로태스크가 있으며 모두 동일한 실행을 갖고 관심 있는 학생들이 스스로 학습할 수 있습니다. 🎜🎜(5)마지막으로🎜🎜🎜javascript는 단일 스레드 언어입니다🎜🎜Event Loop는 javascript의 실행 메커니즘입니다.🎜🎜🎜 두 가지 기본 사항을 확실히 파악하고 Javascript를 진지하게 공부하여 프론트 엔드 마스터가 되십시오. 빨리 좋은 꿈 꾸세요! 🎜🎜🎜🎜🎜추천 튜토리얼: "🎜JavaScript 기본 튜토리얼🎜"🎜

위 내용은 JavaScript 실행 메커니즘을 완전히 이해합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.im에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제