[{"data":1,"prerenderedAt":691},["ShallowReactive",2],{"content:\u002F2023\u002Fasync-and-defer":3,"surround:\u002F2023\u002Fasync-and-defer":680},{"id":4,"title":5,"body":6,"categories":640,"date":642,"description":643,"draft":644,"extension":645,"image":25,"meta":646,"navigation":660,"path":661,"permalink":662,"published":662,"readingTime":663,"recommend":662,"references":662,"seo":668,"sitemap":669,"stem":676,"tags":677,"type":678,"updated":662,"__hash__":679},"content\u002Fposts\u002F2023\u002Fasync-and-defer.md","script 标签的 defer 与 async 属性（转载）",{"type":7,"value":8,"toc":631},"minimark",[9,19,26,30,33,39,42,47,50,53,63,66,102,107,124,136,149,159,162,176,182,185,190,193,199,201,223,228,237,243,246,254,262,268,271,283,288,318,324,327,332,335,341,343,382,387,396,398,406,417,421,424,428,439,442,448,484,490,519,527,534,545,557,560,572,584,598,605,608,613,617],[10,11,12,13],"p",{},"原文戳这里👉：",[14,15,16],"a",{"href":16,"rel":17},"https:\u002F\u002Fgithub.com\u002Fmrlmx\u002Fblogs\u002Fissues\u002F4",[18],"nofollow",[10,20,21],{},[22,23],"img",{"alt":24,"src":25},"","\u002Fimages\u002Fasync-and-defer\u002Fasync_and_defer_01.png",[27,28,29],"h2",{"id":29},"前言",[10,31,32],{},"在面试的时候，经常会遇到一道经典的面试题：",[34,35,36],"blockquote",{},[10,37,38],{},"如何优化网页加载速度？",[10,40,41],{},"常规的回答中总会有一条：",[34,43,44],{},[10,45,46],{},"把 css 文件放在页面顶部，把 js 文件放在页面底部。",[10,48,49],{},"那么，为什么要把 js 文件放在页面的最底部呢？",[10,51,52],{},"我们先来看下这段代码：",[54,55,60],"pre",{"className":56,"code":58,"language":59,"meta":24},[57],"language-html","\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh\">\n  \u003Chead>\n    \u003Ctitle>Hi\u003C\u002Ftitle>\n    \u003Cscript>\n        console.log(\"Howdy ~\");\n    \u003C\u002Fscript>\n    \u003Cscript src=\"https:\u002F\u002Funpkg.com\u002Fvue@3.2.41\u002Fdist\u002Fvue.global.js\">\u003C\u002Fscript>\n    \u003Cscript src=\"https:\u002F\u002Funpkg.com\u002Fvue-router@4.1.5\u002Fdist\u002Fvue-router.global.js\">\u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    Hello 👋🏻 ~\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n","html",[61,62,58],"code",{"__ignoreMap":24},[10,64,65],{},"他的执行顺序是：",[67,68,69,76,82,87,93],"ol",{},[70,71,72,73],"li",{},"在控制台打印：",[61,74,75],{"code":75},"Howdy ~",[70,77,78,79],{},"请求并执行 ",[61,80,81],{"code":81},"vue.global.js",[70,83,78,84],{},[61,85,86],{"code":86},"vue-router.global.js",[70,88,89,90],{},"在页面中展示：",[61,91,92],{"code":92},"Hello 👋🏻 ~",[70,94,95,96,101],{},"触发 ",[14,97,100],{"href":98,"rel":99},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWindow\u002FDOMContentLoaded_event",[18],"DOMContentLoaded"," 事件",[10,103,104],{},[22,105],{"alt":24,"src":106},"\u002Fimages\u002Fasync-and-defer\u002Fasync_and_defer_02.png",[10,108,109,110,113,114,117,118,120,121,123],{},"浏览器的解析规则是：如果遇到 ",[61,111,112],{"code":112},"script"," 标签，则暂停构建 ",[61,115,116],{"code":116},"DOM","，转而开始执行 ",[61,119,112],{"code":112}," 标签，如果是外部 ",[61,122,112],{"code":112},"，那么浏览器还需要一直等待其「下载」并「执行」后，再继续解析后面的 HTML。",[10,125,126,127,129,130,132,133,135],{},"如果请求并执行「",[61,128,81],{"code":81},"」需要 3 秒，「",[61,131,86],{"code":86},"」需要 2 秒，那么页面中的 ",[61,134,92],{"code":92},"，则至少需要 5 秒以上才会展示出来。",[10,137,138,139,141,142,144,145,148],{},"可以看到，",[61,140,112],{"code":112}," 标签会阻塞浏览器解析 HTML，如果把 ",[61,143,112],{"code":112}," 都放在 ",[61,146,147],{"code":147},"head"," 中，在网络不佳的情况下，就会导致页面长期处于白屏状态。",[10,150,151,152,155,156,158],{},"在很久以前，一般都是将这些外联脚本，放在 ",[61,153,154],{"code":154},"body"," 标签的最后面，确保先解析展示 ",[61,157,154],{"code":154}," 中的内容，然后再一个个请求执行这些外联脚本。",[10,160,161],{},"那有没有其他更优雅的解决方案呢？",[10,163,164,165,167,168,171,172,175],{},"答案是肯定的，现在 ",[61,166,112],{"code":112}," 标签新增了 2 个属性：",[61,169,170],{"code":170},"defer"," 和 ",[61,173,174],{"code":174},"async","，就是为了解决此类问题，提升页面性能的。",[27,177,179],{"id":178},"script-defer",[61,180,181],{"code":181},"\u003Cscript defer>",[10,183,184],{},"先看一下 MDN 上的解释：",[34,186,187],{},[10,188,189],{},"这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后，触发 DOMContentLoaded 事件前执行。有 defer 属性的脚本会阻止 DOMContentLoaded 事件，直到脚本被加载并且解析完成。",[10,191,192],{},"文档是直接总结了他的特性，我们先看看下面的代码，展开说说细节，加深一下理解。",[54,194,197],{"className":195,"code":196,"language":59,"meta":24},[57],"\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh\">\n  \u003Chead>\n    \u003Ctitle>Hi\u003C\u002Ftitle>\n    \u003Cscript>\n      console.log(\"Howdy ~\");\n    \u003C\u002Fscript>\n    \u003Cscript defer src=\"https:\u002F\u002Funpkg.com\u002Fvue@3.2.41\u002Fdist\u002Fvue.global.js\">\u003C\u002Fscript>\n    \u003Cscript defer src=\"https:\u002F\u002Funpkg.com\u002Fvue-router@4.1.5\u002Fdist\u002Fvue-router.global.js\">\u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    Hello 👋🏻 ~\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[61,198,196],{"__ignoreMap":24},[10,200,65],{},[67,202,203,207,211,215,219],{},[70,204,72,205],{},[61,206,75],{"code":75},[70,208,89,209],{},[61,210,92],{"code":92},[70,212,78,213],{},[61,214,81],{"code":81},[70,216,78,217],{},[61,218,86],{"code":86},[70,220,95,221,101],{},[61,222,100],{"code":100},[10,224,225],{},[22,226],{"alt":24,"src":227},"\u002Fimages\u002Fasync-and-defer\u002Fasync_and_defer_03.png",[10,229,230,231,233,234,236],{},"如果在 ",[61,232,112],{"code":112}," 标签上设置了 ",[61,235,170],{"code":170}," 属性，那么在浏览器解析到这里时，会默默的在后台开始下载此脚本，并继续解析后面的 HTML，并不会阻塞解析操作。",[10,238,239,240,242],{},"等到 HTML 解析完成之后，浏览器会立即执行后台下载的脚本，脚本执行完成之后，才会触发 ",[61,241,100],{"code":100}," 事件。",[10,244,245],{},"看起来还是蛮好理解的吧？咱们再来讨论 2 个小细节：",[10,247,248,249,251,252,242],{},"**Q1：**如果 HTML 解析完成之后，设置了 ",[61,250,170],{"code":170}," 属性的脚本还没下载完成，会怎样？\n**A1：**浏览器会等脚本下载完成之后，再执行此脚本，执行完成之后，再触发 ",[61,253,100],{"code":100},[10,255,256,257,259,260,242],{},"**Q2：**如果有多个设置了 ",[61,258,170],{"code":170}," 属性的脚本，那浏览器会如何处理？\n**A2：**浏览器会并行的在后台下载这些脚本，等 HTML 解析完成，并且所有脚本下载完成之后，再按照他们在 HTML 中出现的相对顺序执行，等所有脚本执行完成之后，再触发 ",[61,261,100],{"code":100},[10,263,264],{},[265,266,267],"strong",{},"最佳实践：",[10,269,270],{},"建议所有的外联脚本都默认设置此属性，因为他不会阻塞 HTML 解析，可以并行下载 JavaScript 资源，还可以按照他们在 HTML 中的相对顺序执行，确保有依赖关系的脚本运行时，不会缺少依赖。",[10,272,273,274,276,277,279,280,282],{},"在 SPA 的应用中，可以考虑把所有的 ",[61,275,112],{"code":112}," 标签加上 ",[61,278,170],{"code":170}," 属性，并且放到 ",[61,281,154],{"code":154}," 的最后面。在现代浏览器中，可以并行下载提升速度，也可以确保在老浏览器中，不阻塞浏览器解析 HTML，起到降级的作用。",[10,284,285],{},[265,286,287],{},"注意：",[289,290,291,306],"ul",{},[70,292,293,295,296,298,299,302,303,305],{},[61,294,170],{"code":170}," 属性仅适用于外部脚本，如果 ",[61,297,112],{"code":112}," 脚本没有 ",[61,300,301],{"code":301},"src","，则会忽略 ",[61,304,170],{"code":170}," 特性。",[70,307,308,310,311,314,315,317],{},[61,309,170],{"code":170}," 属性对模块脚本（",[61,312,313],{"code":313},"\u003Cscript type='module'>\u003C\u002Fscript>","）无效，因为模块脚本就是以 ",[61,316,170],{"code":170}," 的形式加载的。",[27,319,321],{"id":320},"script-async",[61,322,323],{"code":323},"\u003Cscript async>",[10,325,326],{},"按照惯例，先看一下 MDN 上的解释：",[34,328,329],{},[10,330,331],{},"对于普通脚本，如果存在 async 属性，那么普通脚本会被并行请求，并尽快解析和执行。\n对于模块脚本，如果存在 async 属性，那么脚本及其所有依赖都会在延缓队列中执行，因此它们会被并行请求，并尽快解析和执行。\n该属性能够消除解析阻塞的 Javascript。\n解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本，之后才能继续解析。",[10,333,334],{},"感觉这段描述的已经蛮清晰了，不过咱们还是先看看下面的代码，展开说说细节，加深一下理解。",[54,336,339],{"className":337,"code":338,"language":59,"meta":24},[57],"\u003C!DOCTYPE html>\n\u003Chtml lang=\"zh\">\n  \u003Chead>\n    \u003Ctitle>Hi\u003C\u002Ftitle>\n    \u003Cscript>\n      console.log(\"Howdy ~\");\n    \u003C\u002Fscript>\n    \u003Cscript async src=\"https:\u002F\u002Fgoogle-analytics.com\u002Fanalytics.js\">\u003C\u002Fscript>\n    \u003Cscript async src=\"https:\u002F\u002Fads.google.cn\u002Fad.js\">\u003C\u002Fscript>\n  \u003C\u002Fhead>\n  \u003Cbody>\n    Hello 👋🏻 ~\n  \u003C\u002Fbody>\n\u003C\u002Fhtml>\n",[61,340,338],{"__ignoreMap":24},[10,342,65],{},[67,344,345,349,358,362],{},[70,346,72,347],{},[61,348,75],{"code":75},[70,350,351,352,171,355],{},"并行请求 ",[61,353,354],{"code":354},"analytics.js",[61,356,357],{"code":357},"ad.js",[70,359,89,360],{},[61,361,92],{"code":92},[70,363,364,365],{},"根据网络的实际情况，以下几项会无序执行\n",[289,366,367,373,377],{},[70,368,369,370,372],{},"执行 ",[61,371,354],{"code":354}," （下载完后，立即执行）",[70,374,369,375,372],{},[61,376,357],{"code":357},[70,378,95,379,381],{},[61,380,100],{"code":100}," 事件（可能在在上面 2 个脚本之前，之间，之后触发）",[10,383,384],{},[22,385],{"alt":24,"src":386},"\u002Fimages\u002Fasync-and-defer\u002Fasync_and_defer_04.png",[10,388,389,390,392,393,395],{},"浏览器在解析到带有 ",[61,391,174],{"code":174}," 属性的 ",[61,394,112],{"code":112}," 标签时，也不会阻塞页面，同样是在后台默默下载此脚本。当他下载完后，浏览器会暂停解析 HTML，立马执行此脚本。",[10,397,245],{},[10,399,400,401,392,403,405],{},"**Q1：**如果设置了 ",[61,402,174],{"code":174},[61,404,112],{"code":112}," 下载完之后，浏览器还没解析完 HTML，会怎样？\n**A1：**浏览器会暂停解析 HTML，立马执行此脚本，等执行完之后，再继续解析 HTML。",[10,407,408,409,392,411,413,414,416],{},"**Q2：**如果有多个 ",[61,410,174],{"code":174},[61,412,112],{"code":112}," 标签，那等他们下载完成之后，会按照代码顺序执行吗？\n**A2：**不会。执行顺序是：谁先下载完成，谁先执行。",[61,415,174],{"code":174}," 的特点是「完全独立」，不依赖其他内容。",[10,418,419],{},[265,420,267],{},[10,422,423],{},"当我们的项目，需要集成其他独立的第三方库时，可以使用此属性，他们不依赖我们，我们也不依赖于他们。\n通过设置此属性，让浏览器异步下载并执行他，是个不错的优化方案。",[10,425,426],{},[265,427,287],{},[10,429,430,432,433,298,435,302,437,305],{},[61,431,174],{"code":174}," 特性仅适用于外部脚本，如果 ",[61,434,112],{"code":112},[61,436,301],{"code":301},[61,438,174],{"code":174},[27,440,441],{"id":441},"总结",[34,443,444],{},[10,445,446],{},[265,447,170],{},[289,449,450,456,459,462,467,470,475],{},[70,451,452,453,455],{},"不阻塞浏览器解析 HTML，等解析完 HTML 之后，才会执行 ",[61,454,112],{"code":112},"。",[70,457,458],{},"会并行下载 JavaScript 资源。",[70,460,461],{},"会按照 HTML 中的相对顺序执行脚本。",[70,463,464,465,242],{},"会在脚本下载并执行完成之后，才会触发 ",[61,466,100],{"code":100},[70,468,469],{},"在脚本执行过程中，一定可以获取到 HTML 中已有的元素。",[70,471,472,474],{},[61,473,170],{"code":170}," 属性对模块脚本无效。",[70,476,477,478,480,481,483],{},"适用于：所有外部脚本（通过 ",[61,479,301],{"code":301}," 引用的 ",[61,482,112],{"code":112},"）。",[34,485,486],{},[10,487,488],{},[265,489,174],{},[289,491,492,500,502,505,508,516],{},[70,493,494,495,497,498,455],{},"不阻塞浏览器解析 HTML，但是 ",[61,496,112],{"code":112}," 下载完成后，会立即中断浏览器解析 HTML，并执行此 ",[61,499,112],{"code":112},[70,501,458],{},[70,503,504],{},"互相独立，谁先下载完，谁先执行，没有固定的先后顺序，不可控。",[70,506,507],{},"由于没有确定的执行时机，所以在脚本里面可能会获取不到 HTML 中已有的元素。",[70,509,510,512,513,515],{},[61,511,100],{"code":100}," 事件和 ",[61,514,112],{"code":112}," 脚本无相关性，无法确定他们的先后顺序。",[70,517,518],{},"适用于：独立的第三方脚本。",[10,520,521,522,171,524,526],{},"另外：",[61,523,174],{"code":174},[61,525,170],{"code":170}," 之间最大的区别在于它们的执行时机。",[34,528,529],{},[10,530,531],{},[265,532,533],{},"One More Thing",[10,535,536,537,539,540,171,542,544],{},"你有没有想过，如果一个 ",[61,538,112],{"code":112}," 标签同时设置 ",[61,541,170],{"code":170},[61,543,174],{"code":174},"，浏览器会如何处理？",[10,546,547,548,550,551,553,554,556],{},"先说结论：从表现形式上来说，",[61,549,174],{"code":174}," 的优先级比 ",[61,552,170],{"code":170}," 高，也就是如果同时存在这 2 个属性，那么浏览器将会以 ",[61,555,174],{"code":174}," 的特性去加载此脚本。",[10,558,559],{},"这主要分 2 种情况：",[10,561,562,563,565,566,568,569,571],{},"如果是「普通脚本」，浏览器会优先判断 ",[61,564,174],{"code":174}," 属性是否存在，如果存在，则以 ",[61,567,174],{"code":174}," 特性去加载此脚本，如果不存在，再去判断是否存在 ",[61,570,170],{"code":170}," 属性。",[10,573,574,575,580,581,583],{},"如果是「",[14,576,579],{"href":577,"rel":578},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fzh-CN\u002Fdocs\u002FWeb\u002FHTML\u002FElement\u002Fscript",[18],"模块脚本","」，浏览器会判断 ",[61,582,174],{"code":174}," 属性是否存在：",[289,585,586,589,592],{},[70,587,588],{},"如果存在，浏览器会并行下载此模块和他的所有依赖模块，等全部下载完成之后，会立刻执行此脚本。",[70,590,591],{},"如果不存在，浏览器也会并行下载此模块和他的所有依赖模块，然后等浏览器解析完 HTML 之后，再执行此脚本。",[70,593,594,595,597],{},"另外需要注意的是：在模块脚本上设置 ",[61,596,170],{"code":170}," 属性是无效的。",[34,599,600],{},[10,601,602],{},[265,603,604],{},"一图胜千言",[10,606,607],{},"最后，用一张图概括一下这两个属性的加载模式吧：",[10,609,610],{},[22,611],{"alt":24,"src":612},"\u002Fimages\u002Fasync-and-defer\u002Fasync_and_defer_05.png",[27,614,616],{"id":615},"思考题","思考题🤔",[67,618,619,625],{},[70,620,621,622,624],{},"为什么浏览器在解析到普通的 ",[61,623,112],{"code":112}," 标签时，必须先执行他？",[70,626,627,628,630],{},"普通的 ",[61,629,112],{"code":112}," 标签会阻塞浏览器解析 HTML，这会导致什么问题？",{"title":24,"searchDepth":632,"depth":632,"links":633},4,[634,636,637,638,639],{"id":29,"depth":635,"text":29},2,{"id":178,"depth":635,"text":181},{"id":320,"depth":635,"text":323},{"id":441,"depth":635,"text":441},{"id":615,"depth":635,"text":616},[641],"学习笔记","2023-03-17","原文戳这里👉：https:\u002F\u002Fgithub.com\u002Fmrlmx\u002Fblogs\u002Fissues\u002F4",false,"md",{"comments":644,"slots":647},{"copyright":648},{"props":649,"type":7,"value":651},{"title":650},"转载说明",[652],[10,653,654,655,659],{},"本文转载自 ",[14,656,658],{"href":16,"rel":657},[18],"原文","，版权及许可按原作者说明。",true,"\u002F2023\u002Fasync-and-defer",null,{"text":664,"minutes":665,"time":666,"words":667},"12 min read",11.035,662100,2207,{"title":5,"description":643},{"loc":661,"images":670},[671,672,673,674,675],{"loc":25},{"loc":106},{"loc":227},{"loc":386},{"loc":612},"posts\u002F2023\u002Fasync-and-defer",[174,170],"tech","xE30TX5aGT0ImMfN1HylhgcM9jFrPo-SnBH7RiqeTQE",[681,686],{"title":682,"path":683,"stem":684,"date":685,"type":678,"children":-1},"浅尝类型体操","\u002F2023\u002Ftypescript\u002Fts-14","posts\u002F2023\u002Ftypescript\u002Fts-14","2023-02-22",{"title":687,"path":688,"stem":689,"date":690,"type":678,"children":-1},"消除 Vue 中 Unhandled error during execution of xxx 的警告","\u002F2023\u002Fvue\u002Funhandled-error","posts\u002F2023\u002Fvue\u002Funhandled-error","2023-06-16",1793699838320]