Cara cepat memulai VUE3.0: Pelajari
tentang konteks eksekusi, tumpukan eksekusi, dan mekanisme eksekusi (tugas sinkron, tugas asinkron, tugas mikro, tugas makro, dan loop peristiwa) di js
Beberapa teman-teman mungkin merasa bingung ketika ditanya, maka dari itu saya akan merangkumnya hari ini, semoga dapat membantu anda di depan layar.
Sebelum membahas konteks eksekusi dan mekanisme eksekusi js
di js
, mari kita bahas thread dan proses.
Secara resmi,线程
adalah unit terkecil dari penjadwalan CPU
.
? Secara resmi,进程
adalah unit terkecil dari alokasi sumber daya CPU
.
线程
线程
unit yang menjalankan program berdasarkan进程
. Dalam istilah进程
,线程
adalah alur eksekusi dalam suatu program.
Hanya ada satu alur eksekusi dalam suatu进程
yang disebut单线程
Artinya, ketika program dijalankan, jalur program yang diambil disusun secara berurutan.
Beberapa aliran eksekusi dalam suatu进程
disebut多线程
, yaitu, beberapa线程
berbeda dapat dijalankan secara bersamaan dalam suatu program untuk melakukan tugas yang berbeda, yang berarti bahwa satu program diperbolehkan membuat beberapa线程
eksekusi paralel untuk menyelesaikan tugasnya masing-masing. .
Penulis akan memberikan contoh sederhana di bawah ini, misalnya jika kita membuka qq音乐
dan mendengarkan lagu, qq音乐
qq音乐
dipahami sebagai sebuah proses ke lagu adalah sebuah thread, dan mengunduh adalah sebuah proses. Jika kita membuka vscode
lagi untuk menulis kode, itu akan menjadi proses lain.
Prosesnya independen satu sama lain, namun beberapa sumber daya dibagi antar thread dalam proses yang sama.
Siklus hidup sebuah thread melewati lima tahap.
Status baru: Setelah menggunakan kata kunci new
dan kelas Thread
atau subkelasnya untuk membuat objek thread, objek thread berada dalam status baru. Tetap dalam keadaan ini sampai program start()
thread.
Status siap: Ketika objek thread memanggil metode start()
, thread memasuki status siap. Thread dalam keadaan siap berada dalam antrian siap dan dapat segera dijalankan selama ia memperoleh hak untuk menggunakan CPU
.
Status berjalan: Jika thread dalam status siap memperoleh sumber daya CPU
, thread dapat mengeksekusi run()
, dan thread berada dalam status berjalan. Thread dalam keadaan berjalan adalah yang paling rumit, dapat diblokir, siap dan mati.
Status pemblokiran: Jika thread menjalankan metode sleep(睡眠)
, suspend(挂起)
, wait(等待)
dan lainnya, setelah kehilangan sumber daya yang ditempati, thread akan memasuki status pemblokiran dari status berjalan. Status siap dapat dimasukkan kembali setelah waktu tidur habis atau sumber daya perangkat telah diperoleh. Ini dapat dibagi menjadi tiga jenis:
pemblokiran menunggu: thread dalam keadaan berjalan mengeksekusi metode wait()
, menyebabkan thread memasuki keadaan pemblokiran menunggu.
Pemblokiran sinkron: Thread gagal memperoleh kunci sinkronisasi synchronized
(karena kunci sinkronisasi ditempati oleh thread lain).
Pemblokiran lainnya: Ketika permintaan I/O
dikeluarkan dengan memanggil sleep()
atau join()
thread, thread akan memasuki status pemblokiran. Ketika status sleep()
habis waktu, join()
menunggu thread berakhir atau waktu habis, atau pemrosesan I/O
selesai, dan thread kembali ke status siap.
Status kematian: Ketika thread yang sedang berjalan menyelesaikan tugasnya atau kondisi terminasi lainnya terjadi, thread beralih ke status dihentikan.
JS
Sebagai bahasa skrip browser, JS
terutama digunakan untuk berinteraksi dengan pengguna dan mengoperasikan DOM
. Ini menentukan bahwa itu hanya dapat berupa single-thread, jika tidak maka akan menyebabkan masalah sinkronisasi yang sangat kompleks. Misalnya, JavaScript
memiliki dua thread secara bersamaan. Satu thread menambahkan konten ke node DOM
tertentu, dan thread lainnya menghapus node tersebut.
Ketika mesin JS
mem-parsing fragmen kode yang dapat dieksekusi (biasanya fase pemanggilan fungsi), ia akan melakukan beberapa pekerjaan persiapan terlebih dahulu sebelum eksekusi. (Konteks eksekusi (disebut EC
)" atau bisa juga disebut lingkungan eksekusi .
Ada tiga jenis konteks eksekusi dalam javascript
, yaitu:
konteks eksekusi global . Ini adalah konteks eksekusi default atau paling dasar. Hanya akan ada satu konteks global dalam suatu program, dan itu akan ada sepanjang siklus hidup program Skrip javascript
. Bagian bawah tumpukan eksekusi tidak akan dihancurkan oleh tumpukan yang muncul. Konteks global akan menghasilkan objek global (mengambil lingkungan browser sebagai contoh, objek global ini adalah window
), dan mengikat nilai this
ke objek global ini.
Konteks Eksekusi Fungsi Setiap kali suatu fungsi dipanggil, konteks eksekusi fungsi baru dibuat (terlepas dari apakah fungsi tersebut dipanggil berulang kali).
Konteks eksekusi fungsi eval Kode yang dieksekusi di dalam fungsi eval
juga akan memiliki konteks eksekusinya sendiri, tetapi karena eval
tidak sering digunakan, kode tersebut tidak akan dianalisis di sini.
Tadi sudah kita sebutkan bahwa js
akan membuat konteks eksekusi saat dijalankan, namun konteks eksekusi tersebut perlu disimpan, lalu apa yang digunakan untuk menyimpannya? Anda perlu menggunakan struktur data tumpukan.
Tumpukan adalah struktur data yang pertama masuk terakhir keluar.
Jadi ringkasnya, konteks eksekusi yang digunakan untuk menyimpan konteks eksekusi yang dibuat saat kode dijalankan adalah tumpukan eksekusi .
Saat mengeksekusi sepotong kode JS
Kemudian mesin JS
akan membuat konteks eksekusi global dan push
ke tumpukan eksekusi. Dalam proses ini, mesin JS
akan mengalokasikan memori untuk semua variabel dalam kode ini dan menetapkan nilai awal (tidak ditentukan). Mesin JS
akan memasuki tahap eksekusi, dalam proses ini mesin JS
akan mengeksekusi kode baris demi baris, yaitu memberikan nilai (nilai sebenarnya) ke variabel yang telah dialokasikan memorinya satu per satu.
Jika ada pemanggilan function
dalam kode ini, mesin JS
akan membuat konteks eksekusi fungsi dan push
ke tumpukan eksekusi. Proses pembuatan dan eksekusi sama dengan konteks eksekusi global.
Ketika tumpukan eksekusi selesai, konteks eksekusi akan dikeluarkan dari tumpukan, dan kemudian konteks eksekusi berikutnya akan dimasukkan.
Izinkan saya memberikan contoh di bawah ini. Jika kita memiliki kode berikut dalam program kita:
console.log("Global Execution Context start"); fungsi pertama() { console.log("fungsi pertama"); Kedua(); console.log("Lagi fungsi pertama"); } fungsi kedua() { console.log("fungsi kedua"); } Pertama(); console.log("Konteks Eksekusi Global berakhir");
Mari kita analisis secara singkat contoh di atas
Pertama, tumpukan eksekusi akan dibuat
, kemudian konteks global akan dibuat, dan konteks eksekusi akan push
ke tumpukan eksekusi
untuk memulai eksekusi. , dan Global Execution Context start
temui metode first
, jalankan metode, buat konteks eksekusi fungsi dan push
ke tumpukan eksekusi
untuk mengeksekusi konteks eksekusi first
, keluaran first function
temui metode second
, jalankan metode. , membuat konteks eksekusi fungsi dan push
ke tumpukan eksekusi untuk
mengeksekusi Konteks eksekusi second
, menampilkan second function
Konteks eksekusi second
telah dieksekusi, dikeluarkan dari tumpukan, dan memasuki konteks first
first
eksekusi, keluaran Again first function
Konteks eksekusi first
telah dieksekusi, dikeluarkan dari tumpukan, dan memasuki konteks Eksekusi berikutnya Konteks eksekusi global Konteks eksekusi
global Lanjutkan eksekusi dan keluaran Global Execution Context end
Kami menggunakan gambar untuk meringkas
Baiklah. Setelah membahas konteks eksekusi dan tumpukan eksekusi, mari kita bicara tentang mekanisme eksekusi js. Berbicara tentang mekanisme eksekusi js
js
perlu memahami tugas sinkron, tugas asinkron, tugas makro, dan tugas mikro di js
.
Di js
, tugas dibagi menjadi tugas sinkron dan tugas asinkron.
Tugas sinkron mengacu pada tugas yang diantri untuk dieksekusi di thread utama. Tugas berikutnya hanya dapat dijalankan setelah tugas sebelumnya dijalankan.
Tugas asinkron mengacu pada tugas yang tidak masuk ke thread utama tetapi masuk ke "antrian tugas" (tugas dalam antrian tugas dijalankan secara paralel dengan thread utama hanya ketika thread utama tidak aktif dan "antrian tugas" memberi tahu thread utama, tugas asinkron Setelah dapat dijalankan, tugas tersebut akan memasuki thread utama untuk dieksekusi. Karena merupakan penyimpanan antrean, maka memenuhi aturan masuk pertama keluar pertama . Tugas asinkron yang umum mencakup setInterval
, setTimeout
, promise.then
, dll.
sebelumnya telah memperkenalkan tugas sinkron dan tugas asinkron. Sekarang mari kita bicara tentang perulangan peristiwa.
Tugas sinkron dan asinkron masing-masing memasuki "tempat" eksekusi yang berbeda, dan memasuki thread utama secara sinkron. Hanya ketika tugas sebelumnya selesai, tugas berikutnya dapat dijalankan. Tugas asinkron tidak masuk ke thread utama tetapi masuk ke Event Table
dan fungsi register.
Ketika hal yang ditentukan sudah selesai, Event Table
akan memindahkan fungsi ini ke dalam Event Queue
. Event Queue
adalah struktur data antrian, sehingga memenuhi aturan masuk pertama keluar pertama.
Ketika tugas di thread utama kosong setelah eksekusi, fungsi terkait akan dibaca dari Event Queue
dan dieksekusi di thread utama.
Proses diatas akan berulang secara terus menerus yang sering disebut dengan Event Loop .
Mari kita rangkum dengan sebuah gambar
Izinkan saya memperkenalkan secara singkat sebuah contoh
fungsi test1() { konsol.log("log1"); setWaktu habis(() => { console.log("setTimeout 1000"); }, 1000); setWaktu habis(() => { console.log("setTimeout 100"); }, 100); konsol.log("log2"); } test1(); // log1, log2, setTimeout 100, setTimeout 1000
Kita tahu bahwa di js, tugas sinkron akan dieksekusi terlebih dahulu sebelum tugas asinkron, jadi contoh di atas akan menampilkan log1、log2
terlebih dahulu,
lalu menjalankan tugas asinkron setelah sinkron
itu
, fungsi panggilan balik dengan penundaan 100
milidetik akan mengeksekusi keluaran setTimeout 100
1000
setTimeout 1000
selama Anda memahami tugas sinkron dan asinkron yang disebutkan penulis di atas, tidak akan ada masalah. Kalau begitu izinkan saya memberi Anda contoh lain. Teman-teman, mari kita lihat seperti apa hasilnya.
fungsi tes2() { konsol.log("log1"); setWaktu habis(() => { console.log("setTimeout 1000"); }, 1000); setWaktu habis(() => { console.log("setTimeout 100"); }, 100); Janji baru((putuskan, tolak) => { console.log("janji baru"); menyelesaikan(); }).lalu(() => { console.log("janji.lalu"); }); konsol.log("log2"); } test2();
Untuk mengatasi masalah di atas, mengetahui tugas sinkron dan asinkron saja tidak cukup. Kita juga perlu mengetahui tugas makro dan tugas mikro.
Di js
, tugas dibagi menjadi dua jenis, satu disebut tugas makro MacroTask
, dan yang lainnya disebut tugas mikro MicroTask
.
Tugas makro umum MacroTask
memiliki
blok kode utama
setTimeout()
setInterval()
setImmediate() - Node
requestAnimationFrame() - browser.
Tugas mikro umum MicroTask
memiliki
Promise.then()
process.nextTick() -
Jadi di contoh di atas Ini melibatkan tugas makro dan tugas mikro. Bagaimana urutan pelaksanaan tugas makro dan tugas mikro?
Pertama-tama, ketika keseluruhan script
(sebagai tugas makro pertama) mulai dijalankan, semua kode akan dibagi menjadi dua bagian: tugas sinkron dan tugas asinkron akan langsung memasuki thread utama untuk dieksekusi secara berurutan, dan tugas-tugas asinkron akan memasuki antrian asinkron dan kemudian Dibagi menjadi tugas makro dan tugas mikro.
Tugas makro memasuki Event Table
dan mendaftarkan fungsi panggilan balik di dalamnya. Setiap kali acara yang ditentukan selesai, Event Table
akan memindahkan Event Queue
ini
ke Antrean Acara. Tugas mikro juga akan memasukkan Event Table
lain dan mendaftar di dalamnya .Setiap kali acara yang ditentukan selesai, Event Table
akan memindahkan fungsi ini ke Event Queue
Ketika tugas di thread utama selesai dan thread utama kosong, Event Queue
dari tugas mikro akan diperiksa , semua Jalankan, jika tidak, jalankan tugas makro berikutnya.
Kami menggunakan gambar untuk meringkasnya.
Setelah memahami contoh tugas makro dan tugas mikro secara asinkron di atas, kita dapat dengan mudah mendapatkan jawabannya.
Kita tahu bahwa di js, tugas sinkron akan dieksekusi terlebih dahulu sebelum tugas asinkron, jadi contoh di atas akan menampilkan log1、new promise、log2
terlebih dahulu. Perlu dicatat di sini bahwa blok kode utama dari janji baru disinkronkan.
Setelah tugas makro dijalankan, semua tugas mikro yang dihasilkan oleh tugas makro ini akan dijalankan, jadi promise.then
akan ditampilkan
setelah semua tugas mikro dijalankan , tugas makro lain akan dieksekusi, menunda fungsi panggilan balik 100
milidetik akan memprioritaskan eksekusi dan keluaran setTimeout 100
Tugas makro ini tidak menghasilkan tugas mikro, jadi tidak ada tugas mikro yang perlu dijalankan
untuk terus menjalankan tugas makro berikutnya fungsi dengan penundaan 1000
akan memprioritaskan eksekusi dan output setTimeout 1000
jadi setelah metode test2 dijalankan, itu akan Output log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000
secara
berurutan Ada perbedaan pendapat tentang apakah akan menjalankan
js
terlebih dahulu dengan tugas makro dan kemudian tugas mikro atau dengan tugas mikro sebelum tugas makro. Pemahaman penulis adalah jika seluruh blok kodejs
dianggap sebagai tugas makro, urutan eksekusijs
kita adalah tugas makro terlebih dahulu dan kemudian tugas mikro.
Seperti kata pepatah, berlatih sekali lebih baik daripada menonton seratus kali di bawah ini. Jika Anda bisa melakukannya dengan benar, berarti Anda sudah menguasai ilmu mekanisme eksekusi js
.
Contoh 1
fungsi test3() { konsol.log(1); setTimeout(fungsi () { konsol.log(2); Janji baru(fungsi (putuskan) { konsol.log(3); menyelesaikan(); }).lalu(fungsi () { konsol.log(4); }); konsol.log(5); }, 1000); Janji baru(fungsi (putuskan) { konsol.log(6); menyelesaikan(); }).lalu(fungsi () { konsol.log(7); setTimeout(fungsi () { konsol.log(8); }); }); setTimeout(fungsi () { konsol.log(9); Janji baru(fungsi (putuskan) { konsol.log(10); menyelesaikan(); }).lalu(fungsi () { konsol.log(11); }); }, 100); konsol.log(12); } test3();
Mari kita analisis secara detail.
Pertama, keseluruhan blok kode js
dijalankan sebagai tugas makro, dan 1, 1、6、12
dikeluarkan secara berurutan.
Setelah keseluruhan tugas makro blok kode dijalankan, satu tugas mikro dan dua tugas makro dihasilkan, sehingga antrian tugas makro memiliki dua tugas makro dan antrian tugas mikro memiliki satu tugas mikro.
Setelah tugas makro dijalankan, semua tugas mikro yang dihasilkan oleh tugas makro ini akan dijalankan. Karena hanya ada satu microtask, maka akan dihasilkan 7
. Tugas mikro ini memunculkan tugas makro lainnya, jadi saat ini terdapat tiga tugas makro dalam antrean tugas makro.
Di antara ketiga tugas makro, tugas makro yang tidak disetel akan dieksekusi terlebih dahulu, jadi tugas makro ini adalah keluaran 8
Tugas makro ini tidak menghasilkan tugas mikro, sehingga tidak ada tugas mikro yang harus dijalankan, dan tugas makro berikutnya terus dijalankan.
Tunda eksekusi makrotask selama 100
milidetik, keluaran 9、10
, dan hasilkan tugas mikro, sehingga antrian tugas mikro saat ini memiliki tugas mikro.
Setelah tugas makro dijalankan, semua tugas mikro yang dihasilkan oleh tugas makro akan dijalankan, sehingga tugas mikro akan dieksekusi
dieksekusi
. Semua tugas mikro dalam keluaran antrian tugas 11
Eksekusi tugas makro menghasilkan 2、3、5
dengan penundaan 1000
milidetik, dan tugas mikro dihasilkan. Oleh karena itu, antrian tugas mikro saat ini memiliki tugas mikro.
makrotask akan dijalankan. Semua tugas mikro dihasilkan, sehingga semua tugas mikro dalam antrian tugas mikro akan dieksekusi, dan 4
akan menjadi keluaran
. Jadi contoh kode di atas akan menampilkan 1、6、12、7、8、9、10、11、2、3、5、4
, 12, 7, 8, 9, 10, 11. , 2, 3, 5, 4 secara berurutan.
Contoh 2:
Kami sedikit memodifikasi contoh 1 di atas async
await
fungsi async dan menunggu async test4() { konsol.log(1); setTimeout(fungsi () { konsol.log(2); Janji baru(fungsi (putuskan) { konsol.log(3); menyelesaikan(); }).lalu(fungsi () { konsol.log(4); }); konsol.log(5); }, 1000); Janji baru(fungsi (putuskan) { konsol.log(6); menyelesaikan(); }).lalu(fungsi () { konsol.log(7); setTimeout(fungsi () { konsol.log(8); }); }); hasil const = menunggu async1(); console.log(hasil); setTimeout(fungsi () { konsol.log(9); Janji baru(fungsi (putuskan) { konsol.log(10); menyelesaikan(); }).lalu(fungsi () { konsol.log(11); }); }, 100); konsol.log(12); } fungsi asinkron async1() { konsol.log(13) return Promise.resolve("Janji.resolve"); } test4();
Apa yang akan dihasilkan oleh contoh di atas? Di sini kita dapat dengan mudah menyelesaikan masalah async
dan await
.
Kita tahu async
dan await
sebenarnya adalah gula sintaksis untuk Promise
. Di sini kita hanya perlu mengetahui await
setara dengan Promise.then
. Jadi kita bisa memahami contoh di atas seperti kode berikut
function test4() { konsol.log(1); setTimeout(fungsi () { konsol.log(2); Janji baru(fungsi (putuskan) { konsol.log(3); menyelesaikan(); }).lalu(fungsi () { konsol.log(4); }); konsol.log(5); }, 1000); Janji baru(fungsi (putuskan) { konsol.log(6); menyelesaikan(); }).lalu(fungsi () { konsol.log(7); setTimeout(fungsi () { konsol.log(8); }); }); Janji baru(fungsi (putuskan) { konsol.log(13); return resolve("Janji.resolve"); }).lalu((hasil) => { console.log(hasil); setTimeout(fungsi () { konsol.log(9); Janji baru(fungsi (putuskan) { konsol.log(10); menyelesaikan(); }).lalu(fungsi () { konsol.log(11); }); }, 100); konsol.log(12); }); } test4();
Bisakah Anda dengan mudah mendapatkan hasilnya setelah melihat kode di atas?
Pertama, seluruh blok kode js
awalnya dijalankan sebagai tugas makro, dan menghasilkan keluaran 1、6、13
secara berurutan.
Setelah keseluruhan tugas makro blok kode dijalankan, dua tugas mikro dan satu tugas makro dihasilkan, sehingga antrian tugas makro memiliki satu tugas makro dan antrian tugas mikro memiliki dua tugas mikro.
Setelah tugas makro dijalankan, semua tugas mikro yang dihasilkan oleh tugas makro ini akan dijalankan. Jadi 7、Promise.resolve、12
akan menjadi output. Tugas mikro ini memunculkan dua tugas makro lagi, sehingga antrean tugas makro saat ini memiliki tiga tugas makro.
Di antara ketiga tugas makro, tugas makro yang tidak disetel akan dieksekusi terlebih dahulu, jadi tugas makro ini adalah keluaran 8
Tugas makro ini tidak menghasilkan tugas mikro, sehingga tidak ada tugas mikro yang harus dijalankan, dan tugas makro berikutnya terus dijalankan.
Tunda eksekusi makrotask selama 100
milidetik, keluaran 9、10
, dan hasilkan tugas mikro, sehingga antrian tugas mikro saat ini memiliki tugas mikro.
Setelah tugas makro dijalankan, semua tugas mikro yang dihasilkan oleh tugas makro akan dijalankan, sehingga tugas mikro akan dieksekusi
dieksekusi
. Semua tugas mikro dalam keluaran antrian tugas 11
Eksekusi tugas makro menghasilkan 2、3、5
dengan penundaan 1000
milidetik, dan tugas mikro dihasilkan. Oleh karena itu, antrian tugas mikro saat ini memiliki tugas mikro.
makrotask akan dieksekusi. Semua tugas mikro yang dihasilkan akan menjalankan semua tugas mikro di antrian tugas mikro dan menghasilkan keluaran 4
Oleh karena itu, contoh kode di atas akan menampilkan 1, 6, 13, 7 1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4
4, apakah kalian melakukannya dengan benar?
Banyak teman-teman yang mungkin masih belum paham setTimeout(fn)
. Bukankah sudah jelas waktu tundanya tidak diatur?
Kita dapat memahami setTimeout(fn)
sebagai setTimeout(fn,0)
, yang sebenarnya memiliki arti yang sama.
Kita tahu bahwa js dibagi menjadi tugas sinkron dan tugas asinkron. setTimeout(fn)
adalah tugas asinkron, jadi meskipun Anda tidak menyetel waktu tunda di sini, ia akan memasuki antrean asinkron dan tidak akan dieksekusi hingga thread utama selesai. menganggur.
Penulis akan menyebutkannya lagi, apakah menurut Anda waktu tunda yang kita atur setelah setTimeout
, js
pasti akan dieksekusi sesuai dengan waktu tunda yang kita tetapkan, menurut saya tidak. Waktu yang kami tetapkan hanyalah agar fungsi panggilan balik dapat dijalankan, tetapi apakah thread utama bebas adalah masalah lain.
fungsi tes5() { setTimeout(fungsi () { console.log("setTimeout"); }, 100); misalkan saya = 0; sementara (benar) { saya++; } } test5();
Akankah contoh di atas pasti menghasilkan setTimeout
setelah 100
milidetik? Tidak, karena thread utama kita telah memasuki loop tak terbatas dan tidak memiliki waktu untuk menjalankan tugas antrian asinkron.
GUI渲染
di sini. Beberapa teman mungkin tidak memahaminya. Saya akan memperkenalkannya secara rinci di artikel tentang browser nanti.
Karena JS引擎线程
dan GUI渲染线程
saling eksklusif, untuk memungkinkan宏任务
dan DOM任务
berjalan secara tertib, browser akan memulai GUI渲染线程
setelah hasil eksekusi satu宏任务
dan sebelum pelaksanaan宏任务
berikutnya, render halaman.
Oleh karena itu, hubungan antara tugas makro, tugas mikro, dan rendering GUI adalah sebagai berikut:
tugas makro -> tugas mikro -> rendering GUI -> tugas makro ->...
[Rekomendasi tutorial video terkait: web front end]
Di atas adalah analisis mendalam tentang JavaScript Untuk detail tentang konteks eksekusi dan mekanisme eksekusi, harap perhatikan artikel terkait lainnya di situs web PHP Mandarin untuk informasi lebih lanjut!