Rumah > Artikel > hujung hadapan web > Mari kita bincangkan tentang prinsip dan contoh gelung peristiwa JavaScript
Artikel ini membawakan anda pengetahuan yang berkaitan tentang JavaScript, yang terutamanya memperkenalkan kandungan gelung acara yang berkaitan Mari kita lihat bersama-sama.
[Cadangan berkaitan: Tutorial video JavaScript, bahagian hadapan web]
Fahami JavaScript Gelung peristiwa selalunya disertai dengan isu berkaitan seperti tugasan makro dan tugasan mikro, proses pelaksanaan berbenang tunggal JavaScript dan mekanisme tak segerak penyemak imbas Pelaksanaan gelung peristiwa dalam penyemak imbas dan NodeJS juga sangat berbeza. Membiasakan diri dengan gelung peristiwa dan memahami mekanisme pengendalian penyemak imbas akan sangat membantu kami dalam memahami proses pelaksanaan JavaScript dan menyelesaikan masalah menjalankan kod.
JS adalah satu benang, iaitu, ia hanya boleh melakukan satu perkara pada masa yang sama, jadi fikirkan: mengapa penyemak imbas boleh melaksanakan secara tak segerak pada masa yang sama Bagaimana dengan misi?
Oleh kerana penyemak imbas berbilang benang, apabila JS perlu melaksanakan tugas tak segerak, penyemak imbas akan memulakan urutan lain untuk melaksanakan tugas itu. Dalam erti kata lain, "JS adalah satu-benang" bermaksud bahawa hanya terdapat satu utas yang melaksanakan kod JS, iaitu utas enjin JS (benang utama) yang disediakan oleh penyemak imbas. Terdapat juga utas pemasa dan utas permintaan HTTP dalam penyemak imbas Utas ini tidak digunakan terutamanya untuk menjalankan kod JS.
Contohnya, jika anda perlu menghantar permintaan AJAX dalam utas utama, tugas ini diserahkan kepada utas penyemak imbas lain (benang permintaan HTTP) untuk menghantar permintaan itu apabila permintaan itu kembali, JS yang perlu dilaksanakan dalam panggilan balik Panggilan balik diserahkan kepada benang enjin JS untuk dilaksanakan. **Iaitu, penyemak imbas ialah orang yang benar-benar melaksanakan tugas menghantar permintaan, dan JS hanya bertanggungjawab untuk melaksanakan pemprosesan panggilan balik terakhir. **Jadi asynchronous di sini tidak dilaksanakan oleh JS sendiri, tetapi sebenarnya adalah keupayaan yang disediakan oleh penyemak imbas.
Ambil Chrome sebagai contoh Penyemak imbas bukan sahaja mempunyai berbilang urutan, tetapi juga berbilang proses, seperti proses pemaparan, proses GPU dan proses pemalam. Setiap halaman tab ialah proses pemaparan bebas, jadi jika satu tab ranap secara tidak normal, tab lain pada dasarnya tidak akan terjejas. Sebagai pembangun bahagian hadapan, anda memberi tumpuan terutamanya pada proses pemaparan Proses pemaparan termasuk urutan enjin JS, utas permintaan HTTP dan utas pemasa, dsb. Urutan ini menyediakan asas untuk JS menyelesaikan tugasan tak segerak dalam penyemak imbas.
Di sebalik prinsip pelaksanaan tugas tak segerak penyemak imbas sebenarnya adalah satu set mekanisme dipacu peristiwa. Pencetusan peristiwa, pemilihan tugas dan pelaksanaan tugas semuanya dicapai oleh mekanisme yang dipacu peristiwa. Reka bentuk NodeJS dan penyemak imbas adalah didorong oleh peristiwa Secara ringkasnya, tugasan tertentu dicetuskan oleh peristiwa tertentu. benang pemasa dalam penyemak imbas akan mencetuskan acara pemasa selepas pemasa tamat. Tema artikel iniGelung acara sebenarnya ialah satu set proses untuk mengurus dan melaksanakan acara dalam model dipacu peristiwa.
Ambil adegan mudah sebagai contoh. Katakan terdapat butang gerak dan model watak pada antara muka permainan Selepas setiap klik untuk bergerak ke kanan, kedudukan model watak perlu dipaparkan semula dan dialihkan ke kanan sebanyak 1 piksel. Kami boleh melaksanakannya dalam cara yang berbeza bergantung pada pemasaan pemaparan.
Kaedah pelaksanaan satu: dipacu peristiwa. Selepas mengklik butang, apabila kedudukan koordinatX diubah suai, acara pemaparan antara muka segera dicetuskan dan pemaparan semula dicetuskan.
Kaedah pelaksanaan dua: dipacu keadaan atau dipacu data. Selepas mengklik butang, hanya kedudukan koordinatX diubah suai dan pemaparan antara muka tidak dicetuskan. Sebelum ini, setInterval pemasa akan dimulakan atau requestAnimationFrame akan digunakan untuk terus mengesan sama ada positionX berubah. Jika terdapat perubahan, tunjuk semula dengan segera.
Pemprosesan acara klik dalam penyemak imbas juga biasanya didorong oleh peristiwa. Dalam sistem terdorong peristiwa, apabila peristiwa dicetuskan, peristiwa yang dicetuskan akan disimpan sementara dalam baris gilir mengikut urutan Selepas tugas penyegerakan JS selesai, peristiwa yang akan diproses akan dikeluarkan daripada baris gilir ini dan diproses. Jadi bila hendak mengambil tugasan dan tugas mana yang hendak diambil dahulu, ini dikawal oleh proses gelung peristiwa.
Apabila JS menghuraikan sekeping kod, ia akan menyusun kod penyegerakan di suatu tempat mengikut tertib, iaitu timbunan pelaksanaan, dan kemudian Laksanakan fungsi di dalam mengikut urutan. Apabila tugas tak segerak ditemui, ia diserahkan kepada utas lain untuk diproses Selepas semua kod penyegerakan dalam timbunan pelaksanaan semasa dilaksanakan, panggilan balik tugas tak segerak yang lengkap akan dikeluarkan daripada baris gilir dan ditambah pada timbunan pelaksanaan. untuk meneruskan pelaksanaan Apabila tugasan tak segerak ditemui, tugas itu akan diproses semula. Selepas tugas tak segerak yang lain selesai, panggilan balik diletakkan dalam baris gilir tugas untuk dikeluarkan daripada timbunan pelaksanaan untuk pelaksanaan.
JS melaksanakan kaedah dalam timbunan pelaksanaan dalam urutan Setiap kali kaedah dilaksanakan, persekitaran pelaksanaan yang unik (konteks) akan dihasilkan untuk kaedah ini Selepas pelaksanaan kaedah ini selesai, pelaksanaan semasa persekitaran akan dimusnahkan , dan keluarkan kaedah ini dari timbunan (iaitu, penggunaan selesai), dan kemudian teruskan ke kaedah seterusnya.
Ia boleh dilihat bahawa dalam mod dipacu peristiwa, sekurang-kurangnya satu gelung pelaksanaan disertakan untuk mengesan sama ada terdapat tugasan baharu dalam baris gilir tugas. Dengan terus menggelung untuk mengeluarkan panggilan balik tak segerak untuk pelaksanaan, proses ini ialah gelung peristiwa dan setiap gelung ialah kitaran peristiwa atau tanda.
Terdapat lebih daripada satu baris gilir tugasan, ia boleh dibahagikan kepada baris gilir tugasan mikro dan baris gilir tugasan makro.
Semasa gelung acara, selepas pelaksanaan kod penyegerakan selesai, timbunan pelaksanaan terlebih dahulu menyemak sama ada terdapat tugas dalam baris gilir microtask yang perlu dilaksanakan, jika tidak, ia kemudiannya pergi ke baris gilir macrotask untuk menyemak sama ada terdapat tugasan yang perlu dilaksanakan, dan sebagainya. Tugasan mikro biasanya dilaksanakan terlebih dahulu dalam kitaran semasa, manakala tugasan makro akan menunggu sehingga kitaran seterusnya Oleh itu, tugasan mikro biasanya dilaksanakan sebelum tugasan makro, dan hanya terdapat satu baris gilir tugasan mikro dan mungkin terdapat baris gilir tugasan berbilang . Selain itu, acara klik dan papan kekunci biasa kami juga tergolong dalam tugas makro.
Mari kita lihat tugas makro biasa dan tugas mikro biasa.
Tugas makro biasa:
Microtasks biasa:
console.log('同步代码1');setTimeout(() => { console.log('setTimeout') }, 0)new Promise((resolve) => { console.log('同步代码2') resolve() }).then(() => { console.log('promise.then') })console.log('同步代码3');// 最终输出"同步代码1"、"同步代码2"、"同步代码3"、"promise.then"、"setTimeout"
Kod di atas akan dikeluarkan dalam susunan berikut: "Kod penyegerakan 1", "Kod penyegerakan 2", "Kod penyegerakan 3", "promise.then ","setTimeout", analisis khusus adalah seperti berikut.
(1) Panggilan balik setTimeout dan promise.then dilaksanakan secara tidak segerak dan akan dilaksanakan selepas semua kod segerak; kelewatan ditetapkan kepada 0, ia akan lalai kepada 4ms dan NodeJS ialah 1ms. Nilai yang tepat mungkin berbeza-beza, tetapi ia bukan 0.
(2) Walaupun promise.then ditulis kemudian, perintah pelaksanaan adalah sebelum setTimeout kerana ia adalah tugasan mikro
(3) Janji baharu disegerakkan Apabila dilaksanakan, panggilan balik dalam promise.then adalah tak segerak.
Mari kita lihat demonstrasi proses pelaksanaan kod di atas:
Sesetengah orang juga memahaminya dengan cara ini: microtasks dilaksanakan pada penghujung gelung peristiwa semasa; Tugas makro dilaksanakan pada permulaan gelung acara seterusnya. Mari kita lihat perbezaan penting antara tugasan mikro dan tugasan makro.Kami sudah mengetahui bahawa apabila JS menghadapi tugas tak segerak, ia akan menyerahkan tugas itu kepada urutan lain untuk diproses dan urutan utamanya akan terus melaksanakan tugas segerak. Sebagai contoh, masa setTimeout akan dikendalikan oleh benang pemasa penyemak imbas Apabila masa tamat, tugas panggil balik pemasa akan diletakkan dalam baris gilir tugas dan menunggu urutan utama mengeluarkannya untuk dilaksanakan. Kami telah menyebut sebelum ini bahawa kerana JS dilaksanakan dalam satu utas, untuk melaksanakan tugas tak segerak, utas penyemak imbas lain diperlukan untuk membantu Iaitu, multi-threading ialah ciri yang jelas bagi tugas tak segerak JS.
Mari kita analisa pemprosesan promise.then (microtask). Apabila promise.then dilaksanakan, enjin V8 tidak akan menyerahkan tugas tak segerak kepada utas penyemak imbas lain Sebaliknya, ia akan menyimpan panggilan balik dalam baris gilirnya sendiri Selepas pelaksanaan timbunan pelaksanaan semasa selesai, ia akan segera melaksanakan baris gilir di mana promise.then disimpan , promise.then microtasks tidak melibatkan multi-threading Walaupun dari beberapa perspektif, microtasks tidak boleh menjadi tak segerak sepenuhnya.
setTimeout mempunyai tugas "masa menunggu", yang perlu dilaksanakan oleh utas pemasa; permintaan ajax mempunyai tugas "menghantar permintaan", yang perlu dilaksanakan oleh utas HTTP, manakala promise.then tidak mempunyai sebarang tugas tak segerak yang perlu dilaksanakan oleh utas lain Ia hanya mempunyai panggilan balik, dan walaupun ada, ia hanyalah satu lagi tugasan makro yang bersarang di dalamnya.
Ringkasan ringkas tentang perbezaan penting antara tugasan mikro dan tugasan makro.
Dalam gelung peristiwa, kod segerak sentiasa dilaksanakan dahulu, dan kemudian panggil balik tak segerak diambil daripada baris gilir tugas untuk pelaksanaan. Apabila setTimeout dilaksanakan, penyemak imbas memulakan urutan baharu untuk memasa panggilan Selepas pemasa tamat, acara pemasa dicetuskan dan panggilan balik disimpan dalam baris gilir tugasan makro, menunggu urutan utama JS mengeluarkan pelaksanaan. Jika utas utama masih melaksanakan tugas penyegerakan pada masa ini, maka tugas makro pada masa ini perlu digantung terlebih dahulu, yang akan menyebabkan masalah pemasa yang tidak tepat. Semakin lama kod penyegerakan diambil, semakin besar ralat dalam pemasa. Bukan sahaja kod penyegerakan, kerana tugasan mikro akan dilaksanakan terlebih dahulu, tugasan mikro juga akan menjejaskan pemasaan Jika terdapat gelung tak terhingga dalam kod penyegerakan atau rekursi dalam tugasan mikro sentiasa memulakan tugasan mikro yang lain, maka kod dalam tugasan makro. mungkin tidak pernah dapat dilaksanakan. Oleh itu, adalah sangat penting untuk meningkatkan kecekapan pelaksanaan kod utas utama.
Senario yang sangat mudah ialah terdapat jam pada antara muka kami yang tepat kepada detik dan mengemas kini masa setiap saat. Anda akan perasan bahawa kadangkala detik hanya melangkau selang 2 saat, dan itulah sebabnya.
Selepas pelaksanaan baris gilir microtask selesai, iaitu selepas gelung acara tamat, penyemak imbas akan melakukan pemaparan paparan Sudah tentu, akan ada pengoptimuman penyemak imbas di sini dan berbilang paparan mungkin digabungkan Hasil daripada gelung ini ialah paparan semula, jadi paparan dikemas kini selepas gelung acara, jadi tidak setiap operasi pada Dom semestinya akan menyegarkan paparan dengan serta-merta. Panggilan balik requestAnimationFrame akan dilaksanakan sebelum paparan dilukis semula, jadi menjadi kontroversi sama ada requestAnimationFrame ialah tugasan mikro atau tugasan makro Dari sini, ia bukan tugasan mikro atau tugasan makro.
Enjin JS sendiri tidak melaksanakan mekanisme gelung peristiwa, yang dilaksanakan oleh hosnya Gelung peristiwa dalam penyemak imbas juga dilaksanakan oleh penyemak imbas mempunyai pelaksanaan gelung acara sendiri. Dalam NodeJS, proses baris gilir tugas kitaran dan tugasan mikro diutamakan berbanding tugasan makro, dan prestasi umum adalah konsisten dengan penyemak imbas. Walau bagaimanapun, ia juga mempunyai beberapa perbezaan daripada penyemak imbas, dan beberapa jenis tugas dan peringkat tugas baharu telah ditambahkan. Seterusnya, kami memperkenalkan proses gelung acara dalam NodeJS.
Oleh kerana semuanya berdasarkan enjin V8, kaedah tak segerak yang disertakan dalam penyemak imbas juga sama dalam NodeJS. Terdapat juga beberapa bentuk tak segerak yang lain dalam NodeJS.
Bayangkan jika borang di atas wujud pada masa yang sama dengan setTimeout, promise, dll., bagaimana untuk menganalisis susunan pelaksanaan kod? Selagi kita memahami mekanisme gelung peristiwa NodeJS, ia akan menjadi jelas.
Keupayaan merentas platform NodeJS dan mekanisme gelung peristiwa semuanya dilaksanakan berdasarkan pustaka Libuv Anda tidak perlu mengambil berat tentang kandungan khusus pustaka ini. Kita hanya perlu tahu bahawa perpustakaan Libuv adalah didorong oleh peristiwa dan merangkum serta menyatukan pelaksanaan API pada platform yang berbeza.
Dalam NodeJS, enjin V8 menghuraikan kod JS dan memanggil API Node kemudian menyerahkan tugas kepada Libuv untuk diperuntukkan, dan akhirnya mengembalikan hasil pelaksanaan kepada enjin V8. Satu set proses gelung peristiwa dilaksanakan dalam Libux untuk mengurus pelaksanaan tugasan ini, jadi gelung peristiwa NodeJS diselesaikan terutamanya dalam Libuv .
Mari kita lihat rupa gelung di Libuv.
Dalam pelaksanaan JS dalam NodeJS, proses yang kami perlu ambil berat terbahagi kepada peringkat berikut Setiap peringkat di bawah mempunyai baris gilir tugas tersendiri Apabila melaksanakan Apabila peringkat yang sepadan dicapai, ia dinilai sama ada terdapat tugasan yang perlu diproses dalam baris gilir tugasan peringkat semasa.
上面每个阶段都会去执行完当前阶段的任务队列,然后继续执行当前阶段的微任务队列,只有当前阶段所有微任务都执行完了,才会进入下个阶段。这里也是与浏览器中逻辑差异较大的地方,不过浏览器不用区分这些阶段,也少了很多异步操作类型,所以不用刻意去区分两者区别。代码如下所示:
const fs = require('fs'); fs.readFile(__filename, (data) => { // poll(I/O 回调) 阶段 console.log('readFile') Promise.resolve().then(() => { console.error('promise1') }) Promise.resolve().then(() => { console.error('promise2') }) });setTimeout(() => { // timers 阶段 console.log('timeout'); Promise.resolve().then(() => { console.error('promise3') }) Promise.resolve().then(() => { console.error('promise4') }) }, 0);// 下面代码只是为了同步阻塞1秒钟,确保上面的异步任务已经准备好了var startTime = new Date().getTime();var endTime = startTime;while(endTime - startTime < 1000) { endTime = new Date().getTime(); }// 最终输出 timeout promise3 promise4 readFile promise1 promise2
另一个与浏览器的差异还体现在同一个阶段里的不同任务执行,在 timers 阶段里面的宏任务、微任务测试代码如下所示:
setTimeout(() => { console.log('timeout1') Promise.resolve().then(function() { console.log('promise1') }) }, 0);setTimeout(() => { console.log('timeout2') Promise.resolve().then(function() { console.log('promise2') }) }, 0);
浏览器中运行
每次宏任务完成后都会优先处理微任务,输出“timeout1”、“promise1”、“timeout2”、“promise2”。
NodeJS 中运行
因为输出 timeout1 时,当前正处于 timers 阶段,所以会先将所有 timer 回调执行完之后再执行微任务队列,即输出“timeout1”、“timeout2”、“promise1”、“promise2”。
上面的差异可以用浏览器和 NodeJS 10 对比验证。是不是感觉有点反程序员?因此 NodeJS 在版本 11 之后,就修改了此处逻辑使其与浏览器尽量一致,也就是每个 timer 执行后都先去检查一下微任务队列,所以 NodeJS 11 之后的输出已经和浏览器一致了。
实际项目中我们常用 Promise 或者 setTimeout 来做一些需要延时的任务,比如一些耗时计算或者日志上传等,目的是不希望它的执行占用主线程的时间或者需要依赖整个同步代码执行完成后的结果。
NodeJS 中的 process.nextTick() 和 setImmediate() 也有类似效果。其中 setImmediate() 我们前面已经讲了是在 check 阶段执行的,而 process.nextTick() 的执行时机不太一样,它比 promise.then() 的执行还早,在同步任务之后,其他所有异步任务之前,会优先执行 nextTick。可以想象是把 nextTick 的任务放到了当前循环的后面,与 promise.then() 类似,但比 promise.then() 更前面。意思就是在当前同步代码执行完成后,不管其他异步任务,先尽快执行 nextTick。如下面的代码,因此这里的 nextTick 其实应该更符合“setImmediate”这个命名才对。
setTimeout(() => { console.log('timeout'); }, 0);Promise.resolve().then(() => { console.error('promise') }) process.nextTick(() => { console.error('nextTick') })// 输出:nextTick、promise、timeout
接下来我们再来看看 setImmediate 和 setTimeout,它们是属于不同的执行阶段了,分别是 timers 阶段和 check 阶段。
setTimeout(() => { console.log('timeout'); }, 0);setImmediate(() => { console.log('setImmediate'); });// 输出:timeout、 setImmediate
分析上面代码,第一轮循环后,分别将 setTimeout 和 setImmediate 加入了各自阶段的任务队列。第二轮循环首先进入 timers 阶段,执行定时器队列回调,然后 pending callbacks 和 poll 阶段没有任务,因此进入check 阶段执行 setImmediate 回调。所以最后输出为“timeout”、“setImmediate”。当然这里还有种理论上的极端情况,就是第一轮循环结束后耗时很短,导致 setTimeout 的计时还没结束,此时第二轮循环则会先执行 setImmediate 回调。
再看这下面一段代码,它只是把上一段代码放在了一个 I/O 任务回调中,它的输出将与上一段代码相反。
const fs = require('fs'); fs.readFile(__filename, (data) => { console.log('readFile'); setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('setImmediate'); }); });// 输出:readFile、setImmediate、timeout
如上面代码所示:
[Cadangan berkaitan:
Tutorial video JavaScriptAtas ialah kandungan terperinci Mari kita bincangkan tentang prinsip dan contoh gelung peristiwa JavaScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!