[{"data":1,"prerenderedAt":1892},["ShallowReactive",2],{"category-javascript":3},[4,1449],{"id":5,"title":6,"body":7,"category":1288,"date":1436,"description":1437,"draft":1438,"extension":1439,"meta":1440,"navigation":410,"path":1441,"seo":1442,"stem":1443,"tags":1444,"__hash__":1448},"blog\u002Fblog\u002Fjs-event-loop.md","浏览器事件循环：从进程模型到任务队列",{"type":8,"value":9,"toc":1418},"minimark",[10,14,27,30,34,39,46,53,57,68,74,78,84,90,108,112,115,134,137,144,150,174,184,194,198,205,233,236,242,253,259,265,269,284,288,295,543,558,561,1162,1166,1176,1185,1204,1213,1216,1283,1286,1341,1344,1348,1359,1363,1369,1383,1386,1415],[11,12,6],"h1",{"id":13},"浏览器事件循环从进程模型到任务队列",[15,16,17,18,22,23,26],"p",{},"JavaScript 常被说成「单线程」，但真正决定页面是否流畅的，是浏览器里那条繁忙的 ",[19,20,21],"strong",{},"渲染主线程","。它既要解析 HTML\u002FCSS、布局绘制，又要执行 JS 与事件回调——这些任务如何排队、谁先谁后，就是 ",[19,24,25],{},"事件循环（消息循环）"," 要回答的问题。",[15,28,29],{},"下文从浏览器进程模型讲起，再到异步模型、阻塞渲染的实测，以及 W3C 与 Chrome 对任务队列的最新理解。",[31,32,33],"h2",{"id":33},"浏览器的进程与线程",[35,36,38],"h3",{"id":37},"进程是什么","进程是什么？",[15,40,41,42,45],{},"程序运行需要专属的内存空间，这块空间可以理解为 ",[19,43,44],{},"进程","。每个应用至少有一个进程；进程之间相互独立，若要通信也需要双方同意。",[15,47,48],{},[49,50],"img",{"alt":51,"src":52},"进程与内存空间示意图","\u002Fdemos\u002Fevent-loop\u002Fprocess.png",[35,54,56],{"id":55},"线程是什么","线程是什么？",[15,58,59,60,63,64,67],{},"有了进程，才能在其中运行代码。执行代码的单元叫 ",[19,61,62],{},"线程","。进程启动后会自动创建一个线程运行代码，称为 ",[19,65,66],{},"主线程","；若需并行执行多段代码，进程内还可再开更多线程。",[15,69,70],{},[49,71],{"alt":72,"src":73},"进程内的主线程与额外线程","\u002Fdemos\u002Fevent-loop\u002Fthread.png",[35,75,77],{"id":76},"浏览器有哪些进程","浏览器有哪些进程？",[15,79,80,83],{},[19,81,82],{},"浏览器是多进程、多线程应用。"," 启动后会拉起多个进程，以降低相互影响和连环崩溃的风险。可在浏览器任务管理器中查看当前进程列表。",[15,85,86],{},[49,87],{"alt":88,"src":89},"浏览器主要进程示意","\u002Fdemos\u002Fevent-loop\u002Fbrowser.png",[15,91,92,93,96,97,99,100,107],{},"与前端开发最相关的是 ",[19,94,95],{},"渲染进程","：启动后会开启 ",[19,98,21],{},"，负责执行 HTML、CSS、JavaScript。默认情况下，每个标签页对应一个渲染进程，标签页之间互不影响。（Chrome 的进程模型仍在演进，详见 ",[101,102,106],"a",{"href":103,"rel":104},"https:\u002F\u002Fchromium.googlesource.com\u002Fchromium\u002Fsrc\u002F+\u002Fmain\u002Fdocs\u002Fprocess_model_and_site_isolation.md#Modes-and-Availability",[105],"nofollow","Chrome 官方说明","。）",[31,109,111],{"id":110},"渲染主线程如何工作","渲染主线程如何工作？",[15,113,114],{},"渲染主线程是浏览器中最繁忙的线程，典型工作包括：",[116,117,118,122,125,128,131],"ul",{},[119,120,121],"li",{},"解析 HTML、CSS",[119,123,124],{},"计算样式、布局、处理图层",[119,126,127],{},"以约 60fps 重绘页面",[119,129,130],{},"执行全局 JS、事件处理函数、计时器回调",[119,132,133],{},"……",[15,135,136],{},"这么多任务如何调度？例如：JS 执行到一半时用户点了按钮，要立即处理点击吗？计时器到时了，要立即跑回调吗？点击与计时器同时到来，先处理谁？",[15,138,139,140,143],{},"主线程的做法是：",[19,141,142],{},"排队","。",[15,145,146],{},[49,147],{"alt":148,"src":149},"事件循环与消息队列","\u002Fdemos\u002Fevent-loop\u002Fqueue.png",[151,152,153,160,167],"ol",{},[119,154,155,156,159],{},"渲染主线程进入一个 ",[19,157,158],{},"不会结束的循环","（事件循环）。",[119,161,162,163,166],{},"每次循环检查 ",[19,164,165],{},"消息队列","：有任务则取出第一个执行，执行完再进入下一轮；没有任务则休眠。",[119,168,169,170,173],{},"其他线程（含其他进程的线程）可随时把任务加到队列 ",[19,171,172],{},"末尾","；若主线程在休眠，会被唤醒继续取任务。",[15,175,176,177,180,181,143],{},"整条链路也叫 ",[19,178,179],{},"事件循环"," 或 ",[19,182,183],{},"消息循环",[185,186,187],"blockquote",{},[15,188,189,190,193],{},"思考题：为什么渲染进程不用多个线程并行做解析、布局、绘制？",[191,192],"br",{},"\n简答：DOM 与样式状态需要一致的可预测顺序；多线程并行改同一棵 DOM 树会带来同步与竞态难题，单主线程 + 队列调度是更稳妥的工程取舍（GPU 合成等仍可由其他线程辅助）。",[31,195,197],{"id":196},"异步主线程不能阻塞","异步：主线程不能阻塞",[15,199,200,201,204],{},"有些任务 ",[19,202,203],{},"无法立刻完成","，例如：",[116,206,207,218,227],{},[119,208,209,210,214,215],{},"计时器：",[211,212,213],"code",{},"setTimeout","、",[211,216,217],{},"setInterval",[119,219,220,221,214,224],{},"网络：",[211,222,223],{},"XHR",[211,225,226],{},"Fetch",[119,228,229,230],{},"用户操作：",[211,231,232],{},"addEventListener",[15,234,235],{},"若主线程一直等到「时间到了」「请求返回」「用户点击」，就会长期阻塞，页面卡死：",[15,237,238],{},[49,239],{"alt":240,"src":241},"同步等待导致主线程阻塞","\u002Fdemos\u002Fevent-loop\u002Fsync.png",[15,243,244,245,248,249,252],{},"渲染主线程承担页面更新与交互，",[19,246,247],{},"绝不能被长时间阻塞","。浏览器因此采用 ",[19,250,251],{},"异步","：",[15,254,255],{},[49,256],{"alt":257,"src":258},"异步模型：其他线程处理，回调入队","\u002Fdemos\u002Fevent-loop\u002Fasync.png",[15,260,261,262],{},"主线程把计时、网络、监听等交给其他线程，自己继续执行后续代码；待时机成熟，将回调包装成任务加入消息队列末尾，由主线程在空闲时调度。",[19,263,264],{},"异步模式下，主线程不会因等待 I\u002FO 而空转阻塞。",[35,266,268],{"id":267},"如何理解-js-的异步面试要点","如何理解 JS 的异步？（面试要点）",[116,270,271,278,281],{},[119,272,273,274,277],{},"JS 运行在渲染主线程中，",[19,275,276],{},"同一时刻只有一条执行栈","，故常被称为单线程语言。",[119,279,280],{},"主线程还要负责渲染；若大量任务同步等待，队列里其他任务无法执行，页面无法及时更新。",[119,282,283],{},"异步做法：计时、网络、事件等交由其他线程；完成后把回调入队，主线程按序执行。",[31,285,287],{"id":286},"js-为何会阻碍渲染","JS 为何会阻碍渲染？",[15,289,290,291,294],{},"改 DOM 与真正画到屏幕上并非同一瞬间完成。若主线程正在执行一段 ",[19,292,293],{},"长时间占用的 JS","，渲染任务只能等在队列后面。",[296,297,300],"code-block",{"lang":298,"title":299},"html","主线程阻塞导致画面延迟更新",[301,302,306],"pre",{"className":303,"code":304,"language":298,"meta":305,"style":305},"language-html shiki shiki-themes github-light github-dark","\u003Ch1>Hello, Event Loop!\u003C\u002Fh1>\n\u003Cbutton>change\u003C\u002Fbutton>\n\u003Cscript>\n  const h1 = document.querySelector('h1')\n  const btn = document.querySelector('button')\n\n  function delay(duration) {\n    const start = Date.now()\n    while (Date.now() - start \u003C duration) {}\n  }\n\n  btn.onclick = function () {\n    h1.textContent = '页面已更新！'\n    delay(3000)\n  }\n\u003C\u002Fscript>\n","",[211,307,308,328,343,353,384,405,412,430,450,475,481,486,503,515,528,533],{"__ignoreMap":305},[309,310,313,317,320,323,325],"span",{"class":311,"line":312},"line",1,[309,314,316],{"class":315},"sVt8B","\u003C",[309,318,11],{"class":319},"s9eBZ",[309,321,322],{"class":315},">Hello, Event Loop!\u003C\u002F",[309,324,11],{"class":319},[309,326,327],{"class":315},">\n",[309,329,331,333,336,339,341],{"class":311,"line":330},2,[309,332,316],{"class":315},[309,334,335],{"class":319},"button",[309,337,338],{"class":315},">change\u003C\u002F",[309,340,335],{"class":319},[309,342,327],{"class":315},[309,344,346,348,351],{"class":311,"line":345},3,[309,347,316],{"class":315},[309,349,350],{"class":319},"script",[309,352,327],{"class":315},[309,354,356,360,364,367,370,374,377,381],{"class":311,"line":355},4,[309,357,359],{"class":358},"szBVR","  const",[309,361,363],{"class":362},"sj4cs"," h1",[309,365,366],{"class":358}," =",[309,368,369],{"class":315}," document.",[309,371,373],{"class":372},"sScJk","querySelector",[309,375,376],{"class":315},"(",[309,378,380],{"class":379},"sZZnC","'h1'",[309,382,383],{"class":315},")\n",[309,385,387,389,392,394,396,398,400,403],{"class":311,"line":386},5,[309,388,359],{"class":358},[309,390,391],{"class":362}," btn",[309,393,366],{"class":358},[309,395,369],{"class":315},[309,397,373],{"class":372},[309,399,376],{"class":315},[309,401,402],{"class":379},"'button'",[309,404,383],{"class":315},[309,406,408],{"class":311,"line":407},6,[309,409,411],{"emptyLinePlaceholder":410},true,"\n",[309,413,415,418,421,423,427],{"class":311,"line":414},7,[309,416,417],{"class":358},"  function",[309,419,420],{"class":372}," delay",[309,422,376],{"class":315},[309,424,426],{"class":425},"s4XuR","duration",[309,428,429],{"class":315},") {\n",[309,431,433,436,439,441,444,447],{"class":311,"line":432},8,[309,434,435],{"class":358},"    const",[309,437,438],{"class":362}," start",[309,440,366],{"class":358},[309,442,443],{"class":315}," Date.",[309,445,446],{"class":372},"now",[309,448,449],{"class":315},"()\n",[309,451,453,456,459,461,464,467,470,472],{"class":311,"line":452},9,[309,454,455],{"class":358},"    while",[309,457,458],{"class":315}," (Date.",[309,460,446],{"class":372},[309,462,463],{"class":315},"() ",[309,465,466],{"class":358},"-",[309,468,469],{"class":315}," start ",[309,471,316],{"class":358},[309,473,474],{"class":315}," duration) {}\n",[309,476,478],{"class":311,"line":477},10,[309,479,480],{"class":315},"  }\n",[309,482,484],{"class":311,"line":483},11,[309,485,411],{"emptyLinePlaceholder":410},[309,487,489,492,495,497,500],{"class":311,"line":488},12,[309,490,491],{"class":315},"  btn.",[309,493,494],{"class":372},"onclick",[309,496,366],{"class":358},[309,498,499],{"class":358}," function",[309,501,502],{"class":315}," () {\n",[309,504,506,509,512],{"class":311,"line":505},13,[309,507,508],{"class":315},"    h1.textContent ",[309,510,511],{"class":358},"=",[309,513,514],{"class":379}," '页面已更新！'\n",[309,516,518,521,523,526],{"class":311,"line":517},14,[309,519,520],{"class":372},"    delay",[309,522,376],{"class":315},[309,524,525],{"class":362},"3000",[309,527,383],{"class":315},[309,529,531],{"class":311,"line":530},15,[309,532,480],{"class":315},[309,534,536,539,541],{"class":311,"line":535},16,[309,537,538],{"class":315},"\u003C\u002F",[309,540,350],{"class":319},[309,542,327],{"class":315},[15,544,545,546,549,550,557],{},"点击按钮后：",[211,547,548],{},"textContent"," 虽已修改，但 ",[19,551,552,553,556],{},"画面要等 ",[211,554,555],{},"delay(3000)"," 跑完"," 才会更新——因为渲染步骤也要主线程空闲才能执行。",[15,559,560],{},"下方可亲自点击体验（约 3 秒内标题不会变，之后才刷新）：",[562,563,565],"code-playground",{"title":564},"JS 阻塞渲染",[301,566,570],{"className":567,"code":568,"language":569,"meta":305,"style":305},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cdiv class=\"demo\">\n    \u003Ch1>{{ title }}\u003C\u002Fh1>\n    \u003Cp class=\"hint\">点击按钮后，标题会延迟约 3 秒才变化。\u003C\u002Fp>\n    \u003Cbutton class=\"btn\" @click=\"onChange\">change\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from 'vue'\n\nconst title = ref('Hello, Event Loop!')\n\nfunction delay(duration) {\n  const start = Date.now()\n  while (Date.now() - start \u003C duration) {}\n}\n\nfunction onChange() {\n  title.value = '页面已更新！'\n  delay(3000)\n}\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.demo {\n  padding: 24px;\n  font-family: system-ui, sans-serif;\n  text-align: center;\n}\n\nh1 {\n  margin: 0 0 12px;\n  font-size: 1.5rem;\n}\n\n.hint {\n  margin: 0 0 16px;\n  color: #64748b;\n  font-size: 0.9rem;\n}\n\n.btn {\n  padding: 10px 20px;\n  border: none;\n  border-radius: 8px;\n  background: #6366f1;\n  color: white;\n  font-weight: 600;\n  cursor: pointer;\n}\n\u003C\u002Fstyle>\n","vue",[211,571,572,581,599,613,633,660,669,677,681,692,706,710,730,734,747,761,780,786,791,802,812,824,829,838,843,856,865,883,902,915,920,925,932,953,969,974,979,987,1005,1018,1032,1037,1042,1050,1069,1082,1097,1110,1122,1135,1148,1153],{"__ignoreMap":305},[309,573,574,576,579],{"class":311,"line":312},[309,575,316],{"class":315},[309,577,578],{"class":319},"template",[309,580,327],{"class":315},[309,582,583,586,589,592,594,597],{"class":311,"line":330},[309,584,585],{"class":315},"  \u003C",[309,587,588],{"class":319},"div",[309,590,591],{"class":372}," class",[309,593,511],{"class":315},[309,595,596],{"class":379},"\"demo\"",[309,598,327],{"class":315},[309,600,601,604,606,609,611],{"class":311,"line":345},[309,602,603],{"class":315},"    \u003C",[309,605,11],{"class":319},[309,607,608],{"class":315},">{{ title }}\u003C\u002F",[309,610,11],{"class":319},[309,612,327],{"class":315},[309,614,615,617,619,621,623,626,629,631],{"class":311,"line":355},[309,616,603],{"class":315},[309,618,15],{"class":319},[309,620,591],{"class":372},[309,622,511],{"class":315},[309,624,625],{"class":379},"\"hint\"",[309,627,628],{"class":315},">点击按钮后，标题会延迟约 3 秒才变化。\u003C\u002F",[309,630,15],{"class":319},[309,632,327],{"class":315},[309,634,635,637,639,641,643,646,649,651,654,656,658],{"class":311,"line":386},[309,636,603],{"class":315},[309,638,335],{"class":319},[309,640,591],{"class":372},[309,642,511],{"class":315},[309,644,645],{"class":379},"\"btn\"",[309,647,648],{"class":372}," @click",[309,650,511],{"class":315},[309,652,653],{"class":379},"\"onChange\"",[309,655,338],{"class":315},[309,657,335],{"class":319},[309,659,327],{"class":315},[309,661,662,665,667],{"class":311,"line":407},[309,663,664],{"class":315},"  \u003C\u002F",[309,666,588],{"class":319},[309,668,327],{"class":315},[309,670,671,673,675],{"class":311,"line":414},[309,672,538],{"class":315},[309,674,578],{"class":319},[309,676,327],{"class":315},[309,678,679],{"class":311,"line":432},[309,680,411],{"emptyLinePlaceholder":410},[309,682,683,685,687,690],{"class":311,"line":452},[309,684,316],{"class":315},[309,686,350],{"class":319},[309,688,689],{"class":372}," setup",[309,691,327],{"class":315},[309,693,694,697,700,703],{"class":311,"line":477},[309,695,696],{"class":358},"import",[309,698,699],{"class":315}," { ref } ",[309,701,702],{"class":358},"from",[309,704,705],{"class":379}," 'vue'\n",[309,707,708],{"class":311,"line":483},[309,709,411],{"emptyLinePlaceholder":410},[309,711,712,715,718,720,723,725,728],{"class":311,"line":488},[309,713,714],{"class":358},"const",[309,716,717],{"class":362}," title",[309,719,366],{"class":358},[309,721,722],{"class":372}," ref",[309,724,376],{"class":315},[309,726,727],{"class":379},"'Hello, Event Loop!'",[309,729,383],{"class":315},[309,731,732],{"class":311,"line":505},[309,733,411],{"emptyLinePlaceholder":410},[309,735,736,739,741,743,745],{"class":311,"line":517},[309,737,738],{"class":358},"function",[309,740,420],{"class":372},[309,742,376],{"class":315},[309,744,426],{"class":425},[309,746,429],{"class":315},[309,748,749,751,753,755,757,759],{"class":311,"line":530},[309,750,359],{"class":358},[309,752,438],{"class":362},[309,754,366],{"class":358},[309,756,443],{"class":315},[309,758,446],{"class":372},[309,760,449],{"class":315},[309,762,763,766,768,770,772,774,776,778],{"class":311,"line":535},[309,764,765],{"class":358},"  while",[309,767,458],{"class":315},[309,769,446],{"class":372},[309,771,463],{"class":315},[309,773,466],{"class":358},[309,775,469],{"class":315},[309,777,316],{"class":358},[309,779,474],{"class":315},[309,781,783],{"class":311,"line":782},17,[309,784,785],{"class":315},"}\n",[309,787,789],{"class":311,"line":788},18,[309,790,411],{"emptyLinePlaceholder":410},[309,792,794,796,799],{"class":311,"line":793},19,[309,795,738],{"class":358},[309,797,798],{"class":372}," onChange",[309,800,801],{"class":315},"() {\n",[309,803,805,808,810],{"class":311,"line":804},20,[309,806,807],{"class":315},"  title.value ",[309,809,511],{"class":358},[309,811,514],{"class":379},[309,813,815,818,820,822],{"class":311,"line":814},21,[309,816,817],{"class":372},"  delay",[309,819,376],{"class":315},[309,821,525],{"class":362},[309,823,383],{"class":315},[309,825,827],{"class":311,"line":826},22,[309,828,785],{"class":315},[309,830,832,834,836],{"class":311,"line":831},23,[309,833,538],{"class":315},[309,835,350],{"class":319},[309,837,327],{"class":315},[309,839,841],{"class":311,"line":840},24,[309,842,411],{"emptyLinePlaceholder":410},[309,844,846,848,851,854],{"class":311,"line":845},25,[309,847,316],{"class":315},[309,849,850],{"class":319},"style",[309,852,853],{"class":372}," scoped",[309,855,327],{"class":315},[309,857,859,862],{"class":311,"line":858},26,[309,860,861],{"class":372},".demo",[309,863,864],{"class":315}," {\n",[309,866,868,871,874,877,880],{"class":311,"line":867},27,[309,869,870],{"class":362},"  padding",[309,872,873],{"class":315},": ",[309,875,876],{"class":362},"24",[309,878,879],{"class":358},"px",[309,881,882],{"class":315},";\n",[309,884,886,889,891,894,897,900],{"class":311,"line":885},28,[309,887,888],{"class":362},"  font-family",[309,890,873],{"class":315},[309,892,893],{"class":362},"system-ui",[309,895,896],{"class":315},", ",[309,898,899],{"class":362},"sans-serif",[309,901,882],{"class":315},[309,903,905,908,910,913],{"class":311,"line":904},29,[309,906,907],{"class":362},"  text-align",[309,909,873],{"class":315},[309,911,912],{"class":362},"center",[309,914,882],{"class":315},[309,916,918],{"class":311,"line":917},30,[309,919,785],{"class":315},[309,921,923],{"class":311,"line":922},31,[309,924,411],{"emptyLinePlaceholder":410},[309,926,928,930],{"class":311,"line":927},32,[309,929,11],{"class":319},[309,931,864],{"class":315},[309,933,935,938,940,943,946,949,951],{"class":311,"line":934},33,[309,936,937],{"class":362},"  margin",[309,939,873],{"class":315},[309,941,942],{"class":362},"0",[309,944,945],{"class":362}," 0",[309,947,948],{"class":362}," 12",[309,950,879],{"class":358},[309,952,882],{"class":315},[309,954,956,959,961,964,967],{"class":311,"line":955},34,[309,957,958],{"class":362},"  font-size",[309,960,873],{"class":315},[309,962,963],{"class":362},"1.5",[309,965,966],{"class":358},"rem",[309,968,882],{"class":315},[309,970,972],{"class":311,"line":971},35,[309,973,785],{"class":315},[309,975,977],{"class":311,"line":976},36,[309,978,411],{"emptyLinePlaceholder":410},[309,980,982,985],{"class":311,"line":981},37,[309,983,984],{"class":372},".hint",[309,986,864],{"class":315},[309,988,990,992,994,996,998,1001,1003],{"class":311,"line":989},38,[309,991,937],{"class":362},[309,993,873],{"class":315},[309,995,942],{"class":362},[309,997,945],{"class":362},[309,999,1000],{"class":362}," 16",[309,1002,879],{"class":358},[309,1004,882],{"class":315},[309,1006,1008,1011,1013,1016],{"class":311,"line":1007},39,[309,1009,1010],{"class":362},"  color",[309,1012,873],{"class":315},[309,1014,1015],{"class":362},"#64748b",[309,1017,882],{"class":315},[309,1019,1021,1023,1025,1028,1030],{"class":311,"line":1020},40,[309,1022,958],{"class":362},[309,1024,873],{"class":315},[309,1026,1027],{"class":362},"0.9",[309,1029,966],{"class":358},[309,1031,882],{"class":315},[309,1033,1035],{"class":311,"line":1034},41,[309,1036,785],{"class":315},[309,1038,1040],{"class":311,"line":1039},42,[309,1041,411],{"emptyLinePlaceholder":410},[309,1043,1045,1048],{"class":311,"line":1044},43,[309,1046,1047],{"class":372},".btn",[309,1049,864],{"class":315},[309,1051,1053,1055,1057,1060,1062,1065,1067],{"class":311,"line":1052},44,[309,1054,870],{"class":362},[309,1056,873],{"class":315},[309,1058,1059],{"class":362},"10",[309,1061,879],{"class":358},[309,1063,1064],{"class":362}," 20",[309,1066,879],{"class":358},[309,1068,882],{"class":315},[309,1070,1072,1075,1077,1080],{"class":311,"line":1071},45,[309,1073,1074],{"class":362},"  border",[309,1076,873],{"class":315},[309,1078,1079],{"class":362},"none",[309,1081,882],{"class":315},[309,1083,1085,1088,1090,1093,1095],{"class":311,"line":1084},46,[309,1086,1087],{"class":362},"  border-radius",[309,1089,873],{"class":315},[309,1091,1092],{"class":362},"8",[309,1094,879],{"class":358},[309,1096,882],{"class":315},[309,1098,1100,1103,1105,1108],{"class":311,"line":1099},47,[309,1101,1102],{"class":362},"  background",[309,1104,873],{"class":315},[309,1106,1107],{"class":362},"#6366f1",[309,1109,882],{"class":315},[309,1111,1113,1115,1117,1120],{"class":311,"line":1112},48,[309,1114,1010],{"class":362},[309,1116,873],{"class":315},[309,1118,1119],{"class":362},"white",[309,1121,882],{"class":315},[309,1123,1125,1128,1130,1133],{"class":311,"line":1124},49,[309,1126,1127],{"class":362},"  font-weight",[309,1129,873],{"class":315},[309,1131,1132],{"class":362},"600",[309,1134,882],{"class":315},[309,1136,1138,1141,1143,1146],{"class":311,"line":1137},50,[309,1139,1140],{"class":362},"  cursor",[309,1142,873],{"class":315},[309,1144,1145],{"class":362},"pointer",[309,1147,882],{"class":315},[309,1149,1151],{"class":311,"line":1150},51,[309,1152,785],{"class":315},[309,1154,1156,1158,1160],{"class":311,"line":1155},52,[309,1157,538],{"class":315},[309,1159,850],{"class":319},[309,1161,327],{"class":315},[31,1163,1165],{"id":1164},"任务有优先级吗","任务有优先级吗？",[15,1167,1168,1171,1172,1175],{},[19,1169,1170],{},"单个任务","在所属队列里通常是先进先出，没有「插队」；但 ",[19,1173,1174],{},"队列之间"," 有优先级差异。",[15,1177,1178,1179,1184],{},"W3C 现行表述要点（",[101,1180,1183],{"href":1181,"rel":1182},"https:\u002F\u002Fhtml.spec.whatwg.org\u002Fmultipage\u002Fwebappapis.html#perform-a-microtask-checkpoint",[105],"Perform a microtask checkpoint","）：",[116,1186,1187,1194,1197],{},[119,1188,1189,1190,1193],{},"每个任务有 ",[19,1191,1192],{},"任务类型","；同类型任务在同一队列，不同类型可分到不同队列。",[119,1195,1196],{},"一次事件循环中，浏览器可按情况从不同队列取任务。",[119,1198,1199,1200,1203],{},"浏览器必须维护 ",[19,1201,1202],{},"微队列（microtask queue）","，其中任务优先于其他队列执行。",[185,1205,1206],{},[15,1207,1208,1209,1212],{},"随着浏览器复杂度提升，简单区分「宏队列 \u002F 微队列」已不足以描述全部行为，但 ",[19,1210,1211],{},"微任务优先清空"," 仍是硬性要求。",[15,1214,1215],{},"在 Chrome 实现中，至少包括：",[1217,1218,1219,1235],"table",{},[1220,1221,1222],"thead",{},[1223,1224,1225,1229,1232],"tr",{},[1226,1227,1228],"th",{},"队列",[1226,1230,1231],{},"典型内容",[1226,1233,1234],{},"优先级（相对）",[1236,1237,1238,1256,1267],"tbody",{},[1223,1239,1240,1244,1253],{},[1241,1242,1243],"td",{},"微队列",[1241,1245,1246,214,1249,1252],{},[211,1247,1248],{},"Promise.then",[211,1250,1251],{},"MutationObserver"," 等",[1241,1254,1255],{},"最高",[1223,1257,1258,1261,1264],{},[1241,1259,1260],{},"交互队列",[1241,1262,1263],{},"用户操作产生的事件处理",[1241,1265,1266],{},"高",[1223,1268,1269,1272,1280],{},[1241,1270,1271],{},"延时队列",[1241,1273,1274,1276,1277,1279],{},[211,1275,213],{}," \u002F ",[211,1278,217],{}," 到期回调",[1241,1281,1282],{},"中",[15,1284,1285],{},"立即把函数放进微队列的常见写法：",[296,1287,1290],{"lang":1288,"title":1289},"javascript","Promise 入微队列",[301,1291,1294],{"className":1292,"code":1293,"language":1288,"meta":305,"style":305},"language-javascript shiki shiki-themes github-light github-dark","Promise.resolve().then(() => {\n  console.log('microtask')\n})\n",[211,1295,1296,1321,1336],{"__ignoreMap":305},[309,1297,1298,1301,1304,1307,1310,1313,1316,1319],{"class":311,"line":312},[309,1299,1300],{"class":362},"Promise",[309,1302,1303],{"class":315},".",[309,1305,1306],{"class":372},"resolve",[309,1308,1309],{"class":315},"().",[309,1311,1312],{"class":372},"then",[309,1314,1315],{"class":315},"(() ",[309,1317,1318],{"class":358},"=>",[309,1320,864],{"class":315},[309,1322,1323,1326,1329,1331,1334],{"class":311,"line":330},[309,1324,1325],{"class":315},"  console.",[309,1327,1328],{"class":372},"log",[309,1330,376],{"class":315},[309,1332,1333],{"class":379},"'microtask'",[309,1335,383],{"class":315},[309,1337,1338],{"class":311,"line":345},[309,1339,1340],{"class":315},"})\n",[31,1342,1343],{"id":1343},"常见面试题",[35,1345,1347],{"id":1346},"阐述-js-的事件循环","阐述 JS 的事件循环",[15,1349,1350,1351,1354,1355,1358],{},"事件循环是渲染主线程的工作方式：开启不会结束的循环，每次从队列取一个任务执行；其他线程在合适时机把任务加到队列末尾。任务按类型分队列，浏览器在一次循环中决定取哪个队列；",[19,1352,1353],{},"微队列任务必须优先调度执行","。Chrome 源码层面可理解为 ",[211,1356,1357],{},"for"," 循环 + 队列取任务，而非过去教科书里单一的「宏队列」模型。",[35,1360,1362],{"id":1361},"计时器能精确计时吗","计时器能精确计时吗？",[15,1364,1365,1368],{},[19,1366,1367],{},"不能做到严格精确","，原因包括：",[151,1370,1371,1374,1377],{},[119,1372,1373],{},"硬件与 OS 计时本身存在误差，JS 计时器最终依赖系统调用。",[119,1375,1376],{},"W3C 规定：嵌套超过 5 层的计时器，最小间隔为 4ms，短于 4ms 的设定会被抬高。",[119,1378,1379,1380,143],{},"回调只在主线程空闲时执行，前面若有长任务，到期时间到了也会 ",[19,1381,1382],{},"晚跑",[31,1384,1385],{"id":1385},"小结",[116,1387,1388,1394,1399,1408],{},[119,1389,1390,1391,1393],{},"浏览器多进程隔离标签页；",[19,1392,21],{}," 是页面与 JS 的核心调度点。",[119,1395,1396,1398],{},[19,1397,179],{}," 用消息队列 + 无限循环保证任务有序执行。",[119,1400,1401,1403,1404,1407],{},[19,1402,251],{}," 避免主线程等待 I\u002FO；",[19,1405,1406],{},"长同步 JS"," 仍会阻塞渲染与交互。",[119,1409,1410,1411,1414],{},"理解 ",[19,1412,1413],{},"微队列优先"," 与多队列模型，比死记「宏\u002F微」更符合当前规范与 Chrome 行为。",[850,1416,1417],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":305,"searchDepth":345,"depth":345,"links":1419},[1420,1425,1426,1429,1430,1431,1435],{"id":33,"depth":330,"text":33,"children":1421},[1422,1423,1424],{"id":37,"depth":345,"text":38},{"id":55,"depth":345,"text":56},{"id":76,"depth":345,"text":77},{"id":110,"depth":330,"text":111},{"id":196,"depth":330,"text":197,"children":1427},[1428],{"id":267,"depth":345,"text":268},{"id":286,"depth":330,"text":287},{"id":1164,"depth":330,"text":1165},{"id":1343,"depth":330,"text":1343,"children":1432},[1433,1434],{"id":1346,"depth":345,"text":1347},{"id":1361,"depth":345,"text":1362},{"id":1385,"depth":330,"text":1385},"2026-06-24","梳理渲染主线程如何调度任务、异步为何必要、JS 为何会阻塞渲染，以及 Chrome 中延时\u002F交互\u002F微队列的优先级。",false,"md",{},"\u002Fblog\u002Fjs-event-loop",{"title":6,"description":1437},"blog\u002Fjs-event-loop",[1445,1446,1447],"browser","event-loop","interview","zpAwwSlMoh5xOkPrZCWcA8rP62VybczZfOtCHemEflo",{"id":1450,"title":1451,"body":1452,"category":1288,"date":1883,"description":1884,"draft":1438,"extension":1439,"meta":1885,"navigation":410,"path":1886,"seo":1887,"stem":1888,"tags":1889,"__hash__":1891},"blog\u002Fblog\u002Fbig-int-add.md","大整数相加",{"type":8,"value":1453,"toc":1874},[1454,1456,1482,1489,1492,1522,1526,1536,1630,1645,1649,1659,1742,1757,1760,1768,1771,1821,1825,1831,1846,1849,1851,1871],[11,1455,1451],{"id":1451},[15,1457,1458,1459,1462,1463,1466,1467,1470,1471,1474,1475,180,1478,1481],{},"JavaScript 的 ",[211,1460,1461],{},"Number"," 基于 IEEE 754 双精度浮点，安全整数范围是 ",[211,1464,1465],{},"-(2^53 - 1)"," 到 ",[211,1468,1469],{},"2^53 - 1","（即 ",[211,1472,1473],{},"Number.MAX_SAFE_INTEGER","）。当两个「大正整数」字符串相加时，不能直接用 ",[211,1476,1477],{},"+",[211,1479,1480],{},"Number()"," 转换，否则会丢失精度。",[15,1483,1484,1485,1488],{},"这道题的核心思路是：",[19,1486,1487],{},"模拟小学竖式加法","——从低位到高位逐位相加，维护进位。",[31,1490,1491],{"id":1491},"思路",[151,1493,1494,1497,1500,1506,1516,1519],{},[119,1495,1496],{},"将两个操作数转为字符串（或本来就是字符串）",[119,1498,1499],{},"从最后一位（个位）向前遍历",[119,1501,1502,1503],{},"对应位数字相加，再加上进位 ",[211,1504,1505],{},"carry",[119,1507,1508,1509,1512,1513],{},"当前位结果 = ",[211,1510,1511],{},"sum % 10","，新进位 = ",[211,1514,1515],{},"Math.floor(sum \u002F 10)",[119,1517,1518],{},"遍历结束后若仍有进位，需要补一位",[119,1520,1521],{},"将结果数组反转（或从高位拼接）得到最终字符串",[31,1523,1525],{"id":1524},"实现一数组收集后反转","实现一：数组收集后反转",[15,1527,1528,1529,1532,1533,143],{},"从字符串末尾双指针向前扫，结果先 ",[211,1530,1531],{},"push"," 到数组，最后 ",[211,1534,1535],{},"reverse().join(\"\")",[296,1537,1539],{"lang":1288,"title":1538},"addBigIntegers",[301,1540,1542],{"className":1292,"code":1541,"language":1288,"meta":305,"style":305},"function addBigIntegers(a, b) {\n  const strA = String(a);\n  const strB = String(b);\n  let i = strA.length - 1;\n  let j = strB.length - 1;\n  let carry = 0;\n  const result = [];\n\n  while (i >= 0 || j >= 0 || carry > 0) {\n    const digitA = i >= 0 ? Number(strA[i--]) : 0;\n    const digitB = j >= 0 ? Number(strB[j--]) : 0;\n    const sum = digitA + digitB + carry;\n    result.push(sum % 10);\n    carry = Math.floor(sum \u002F 10);\n  }\n\n  return result.reverse().join(\"\");\n}\n",[211,1543,1544,1549,1554,1559,1564,1569,1574,1579,1583,1588,1593,1598,1603,1608,1613,1617,1621,1626],{"__ignoreMap":305},[309,1545,1546],{"class":311,"line":312},[309,1547,1548],{},"function addBigIntegers(a, b) {\n",[309,1550,1551],{"class":311,"line":330},[309,1552,1553],{},"  const strA = String(a);\n",[309,1555,1556],{"class":311,"line":345},[309,1557,1558],{},"  const strB = String(b);\n",[309,1560,1561],{"class":311,"line":355},[309,1562,1563],{},"  let i = strA.length - 1;\n",[309,1565,1566],{"class":311,"line":386},[309,1567,1568],{},"  let j = strB.length - 1;\n",[309,1570,1571],{"class":311,"line":407},[309,1572,1573],{},"  let carry = 0;\n",[309,1575,1576],{"class":311,"line":414},[309,1577,1578],{},"  const result = [];\n",[309,1580,1581],{"class":311,"line":432},[309,1582,411],{"emptyLinePlaceholder":410},[309,1584,1585],{"class":311,"line":452},[309,1586,1587],{},"  while (i >= 0 || j >= 0 || carry > 0) {\n",[309,1589,1590],{"class":311,"line":477},[309,1591,1592],{},"    const digitA = i >= 0 ? Number(strA[i--]) : 0;\n",[309,1594,1595],{"class":311,"line":483},[309,1596,1597],{},"    const digitB = j >= 0 ? Number(strB[j--]) : 0;\n",[309,1599,1600],{"class":311,"line":488},[309,1601,1602],{},"    const sum = digitA + digitB + carry;\n",[309,1604,1605],{"class":311,"line":505},[309,1606,1607],{},"    result.push(sum % 10);\n",[309,1609,1610],{"class":311,"line":517},[309,1611,1612],{},"    carry = Math.floor(sum \u002F 10);\n",[309,1614,1615],{"class":311,"line":530},[309,1616,480],{},[309,1618,1619],{"class":311,"line":535},[309,1620,411],{"emptyLinePlaceholder":410},[309,1622,1623],{"class":311,"line":782},[309,1624,1625],{},"  return result.reverse().join(\"\");\n",[309,1627,1628],{"class":311,"line":788},[309,1629,785],{},[15,1631,1632,1633,1636,1637,1640,1641,1644],{},"注意 ",[211,1634,1635],{},"while"," 条件里的 ",[211,1638,1639],{},"carry > 0","：最高位相加后若产生进位（如 ",[211,1642,1643],{},"999 + 1","），循环需要多跑一轮。",[31,1646,1648],{"id":1647},"实现二左对齐补零高位拼接","实现二：左对齐补零，高位拼接",[15,1650,1651,1652,1655,1656,143],{},"另一种写法是先去掉前导零、用 ",[211,1653,1654],{},"padStart"," 对齐长度，再从右向左遍历，把每位结果",[19,1657,1658],{},"拼到字符串前面",[296,1660,1662],{"lang":1288,"title":1661},"addBigIntegers（对齐版）",[301,1663,1665],{"className":1292,"code":1664,"language":1288,"meta":305,"style":305},"function addBigIntegers(a, b) {\n  a = String(a).replace(\u002F^0+\u002F, \"\") || \"0\";\n  b = String(b).replace(\u002F^0+\u002F, \"\") || \"0\";\n  const lth = Math.max(a.length, b.length);\n  a = a.padStart(lth, \"0\");\n  b = b.padStart(lth, \"0\");\n  let carry = 0;\n  let result = \"\";\n  for (let i = lth - 1; i >= 0; i--) {\n    const sum = Number(a[i]) + Number(b[i]) + carry;\n    carry = Math.floor(sum \u002F 10);\n    result = String(sum % 10) + result;\n  }\n  if (carry > 0) result = String(carry) + result;\n  return result;\n}\n",[211,1666,1667,1671,1676,1681,1686,1691,1696,1700,1705,1710,1715,1719,1724,1728,1733,1738],{"__ignoreMap":305},[309,1668,1669],{"class":311,"line":312},[309,1670,1548],{},[309,1672,1673],{"class":311,"line":330},[309,1674,1675],{},"  a = String(a).replace(\u002F^0+\u002F, \"\") || \"0\";\n",[309,1677,1678],{"class":311,"line":345},[309,1679,1680],{},"  b = String(b).replace(\u002F^0+\u002F, \"\") || \"0\";\n",[309,1682,1683],{"class":311,"line":355},[309,1684,1685],{},"  const lth = Math.max(a.length, b.length);\n",[309,1687,1688],{"class":311,"line":386},[309,1689,1690],{},"  a = a.padStart(lth, \"0\");\n",[309,1692,1693],{"class":311,"line":407},[309,1694,1695],{},"  b = b.padStart(lth, \"0\");\n",[309,1697,1698],{"class":311,"line":414},[309,1699,1573],{},[309,1701,1702],{"class":311,"line":432},[309,1703,1704],{},"  let result = \"\";\n",[309,1706,1707],{"class":311,"line":452},[309,1708,1709],{},"  for (let i = lth - 1; i >= 0; i--) {\n",[309,1711,1712],{"class":311,"line":477},[309,1713,1714],{},"    const sum = Number(a[i]) + Number(b[i]) + carry;\n",[309,1716,1717],{"class":311,"line":483},[309,1718,1612],{},[309,1720,1721],{"class":311,"line":488},[309,1722,1723],{},"    result = String(sum % 10) + result;\n",[309,1725,1726],{"class":311,"line":505},[309,1727,480],{},[309,1729,1730],{"class":311,"line":517},[309,1731,1732],{},"  if (carry > 0) result = String(carry) + result;\n",[309,1734,1735],{"class":311,"line":530},[309,1736,1737],{},"  return result;\n",[309,1739,1740],{"class":311,"line":535},[309,1741,785],{},[15,1743,1744,1745,1748,1749,1752,1753,1756],{},"相比实现一，这版额外处理了",[19,1746,1747],{},"前导零","（",[211,1750,1751],{},"\"0123\" + \"456\""," → ",[211,1754,1755],{},"\"579\"","），对小整数也适用。",[31,1758,1759],{"id":1759},"测试用例",[301,1761,1766],{"className":1762,"code":1764,"language":1765,"meta":305},[1763],"language-text","addBigIntegers(\"999999999999999999999\", \"1\")\n\u002F\u002F → \"1000000000000000000000\"\n\naddBigIntegers(\"12345678901234567890\", \"98765432109876543210\")\n\u002F\u002F → \"111111111011111111100\"\n\naddBigIntegers(123, 456)\n\u002F\u002F → \"579\"\n\naddBigIntegers(\"0123\", \"456\")\n\u002F\u002F → \"579\"\n","text",[211,1767,1764],{"__ignoreMap":305},[31,1769,1770],{"id":1770},"复杂度与边界",[1217,1772,1773,1783],{},[1220,1774,1775],{},[1223,1776,1777,1780],{},[1226,1778,1779],{},"项目",[1226,1781,1782],{},"说明",[1236,1784,1785,1793,1801,1813],{},[1223,1786,1787,1790],{},[1241,1788,1789],{},"时间复杂度",[1241,1791,1792],{},"O(max(m, n))，m、n 为两数位数",[1223,1794,1795,1798],{},[1241,1796,1797],{},"空间复杂度",[1241,1799,1800],{},"O(max(m, n))，存放结果",[1223,1802,1803,1806],{},[1241,1804,1805],{},"负数",[1241,1807,1808,1809,1812],{},"本题通常只考大",[19,1810,1811],{},"正","整数；若需支持负数，要先处理符号与绝对值大小比较",[1223,1814,1815,1818],{},[1241,1816,1817],{},"小数",[1241,1819,1820],{},"需先对齐小数点，再分别处理整数与小数部分",[31,1822,1824],{"id":1823},"延伸bigint","延伸：BigInt",[15,1826,1827,1828,252],{},"现代环境可直接用 ",[211,1829,1830],{},"BigInt",[301,1832,1834],{"className":1292,"code":1833,"language":1288,"meta":305,"style":305},"String(BigInt(\"999999999999999999999\") + BigInt(\"1\"))\n\u002F\u002F → \"1000000000000000000000\"\n",[211,1835,1836,1841],{"__ignoreMap":305},[309,1837,1838],{"class":311,"line":312},[309,1839,1840],{},"String(BigInt(\"999999999999999999999\") + BigInt(\"1\"))\n",[309,1842,1843],{"class":311,"line":330},[309,1844,1845],{},"\u002F\u002F → \"1000000000000000000000\"\n",[15,1847,1848],{},"面试手写时仍建议掌握字符串模拟竖式加法——考察的是对进位、边界和字符串操作的理解，而非 API 记忆。",[31,1850,1385],{"id":1385},[116,1852,1853,1859,1865,1868],{},[119,1854,1855,1856,1858],{},"大整数相加不能依赖 ",[211,1857,1461],{},"，应逐位模拟竖式加法",[119,1860,1861,1862,1864],{},"双指针从末尾向前扫，或 ",[211,1863,1654],{}," 对齐后从右向左拼结果",[119,1866,1867],{},"循环条件要覆盖「最后一位仍有进位」的情况",[119,1869,1870],{},"对齐版实现还能统一处理前导零与小整数输入",[850,1872,1873],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":305,"searchDepth":345,"depth":345,"links":1875},[1876,1877,1878,1879,1880,1881,1882],{"id":1491,"depth":330,"text":1491},{"id":1524,"depth":330,"text":1525},{"id":1647,"depth":330,"text":1648},{"id":1759,"depth":330,"text":1759},{"id":1770,"depth":330,"text":1770},{"id":1823,"depth":330,"text":1824},{"id":1385,"depth":330,"text":1385},"2026-05-10","用字符串模拟竖式加法，处理超出 Number 精度的大正整数求和，常见前端面试手写题。",{},"\u002Fblog\u002Fbig-int-add",{"title":1451,"description":1884},"blog\u002Fbig-int-add",[1890,1447],"algorithm","AzJ9a1SGNrgKuH7mn3OfWWWrm1n9SCy5pVODYzyS9W4",1782309644795]