[{"data":1,"prerenderedAt":22668},["ShallowReactive",2],{"page-\u002Frambling\u002Fweb面试笔记":3},{"id":4,"title":5,"body":6,"description":101,"extension":22662,"meta":22663,"navigation":306,"path":22664,"seo":22665,"stem":22666,"__hash__":22667},"content\u002Frambling\u002Fweb面试笔记.md","Web面试笔记",{"type":7,"value":8,"toc":22586},"minimark",[9,13,17,48,51,54,92,95,213,216,223,258,268,271,358,368,387,395,398,401,404,460,463,534,550,557,733,745,765,771,774,822,828,831,834,940,957,963,966,977,1152,1155,1279,1285,1294,1329,1351,1357,1385,1391,1394,1472,1489,1499,1502,1617,1620,1626,1633,1715,1722,1811,1814,1876,1879,1904,1907,1912,1915,1933,1945,1948,1992,2003,2006,2021,2024,2027,2030,2033,2036,2039,2042,2048,2051,2054,2064,2067,2070,2073,2206,2208,2279,2291,2304,2370,2384,2387,2478,2492,2495,2583,2586,2595,2602,2773,2777,2780,2793,2907,2917,2929,2933,2936,2942,2961,3015,3028,3031,3035,3038,3138,3145,3148,3151,3154,3160,3163,3166,3172,3243,3250,3257,3358,3362,3365,3368,3371,3412,3451,3454,3503,3507,3510,3553,3556,3562,3565,3577,3599,3623,3650,3653,3659,3662,3665,4948,4951,4954,4980,4983,5314,5318,5321,5324,5335,5338,5341,5351,5444,5448,5451,5454,5457,5464,5467,5470,5476,5479,5482,5581,5584,5713,5720,5855,5865,5912,5915,6016,6019,6249,6252,6404,6407,6618,6621,6695,6698,6728,6746,6757,7005,7009,7012,7019,7360,7365,7502,7505,7508,7585,7588,7591,7594,7600,7603,7609,7612,7618,7621,7627,7631,7634,7637,7640,7644,7650,7653,7656,7659,7662,7665,7668,7684,7687,7693,7696,7702,7713,7716,7719,7722,7728,7731,7734,7740,7743,7746,7749,7755,7780,7786,7797,7800,7803,7806,7809,7812,7818,7822,7826,7862,7868,7994,8018,8025,8045,8048,8051,8123,8152,8155,8177,8183,8224,8240,8243,8404,8433,8524,8527,8530,8707,8709,8755,8758,8803,8806,8881,8884,8887,8963,8969,9070,9076,9150,9162,9165,9173,9176,9230,9377,9387,9435,9438,9447,9673,9676,9687,9691,9694,9697,9700,9703,9709,9712,9718,9721,9727,9730,9817,9820,9823,9826,9832,9835,9838,9841,9848,9854,9857,9893,9896,9902,9905,9970,9973,9987,9990,10028,10031,10034,10127,10130,10133,10136,10139,10150,10153,10161,10232,10235,10364,10367,10370,10377,10383,10386,10389,10837,10840,10843,10846,10934,10937,10939,10942,11047,11050,11289,11296,11299,11309,11441,11457,11490,11493,11502,11505,11521,11524,11659,11665,11707,11720,11723,11817,13895,13898,13901,13904,13907,13910,13913,13916,13943,13949,13984,13989,13992,14134,14137,14302,14305,14411,14418,14426,14490,14493,14496,14499,14602,14811,14866,14875,15079,15082,15245,15258,15261,15264,15267,15287,15290,15390,15393,15545,15548,15667,15670,15673,15763,15766,15846,15849,16016,16019,16022,16306,16320,16611,16614,16645,16652,17005,17008,17011,17014,17017,17162,17165,17293,17296,17466,17469,17472,17546,17549,17679,17682,17795,17797,17804,17811,17822,17825,17836,17839,17842,17848,17851,17855,17858,17865,17868,17871,17880,17886,17892,17911,17915,17918,17925,18063,18066,18070,18073,18076,18423,18426,18429,19099,19102,19104,19107,19110,19163,19166,19240,19243,19288,19291,19325,19328,19331,19334,19374,19377,19380,19399,19405,19408,19414,19425,19428,19518,19521,19586,19589,19652,19655,19658,19661,19664,19667,19674,19680,19686,19689,19766,19769,19772,19775,19778,19813,19845,19904,19918,19931,19938,19944,19969,19972,19975,19995,20001,20103,20109,20119,20136,20139,20184,20187,20190,20196,20199,20311,20314,20319,20507,20510,20514,20517,20520,20523,20529,20532,20535,20539,20545,20551,20554,20563,20566,20569,20572,20578,20581,20638,20641,20647,20658,20661,20666,20677,20680,20683,20686,20689,20692,20695,20792,20795,20798,20801,20804,20807,20810,20934,20940,20950,20953,20956,20959,20965,20968,20971,20974,20980,20983,20986,20992,21007,21010,21013,21019,21030,21033,21039,21050,21056,21059,21062,21065,21068,21074,21077,21083,21090,21096,21099,21102,21105,21108,21114,21117,21120,21126,21132,21135,21138,21149,21152,21158,21162,21171,21177,21180,21187,21190,21196,21199,21203,21206,21209,21212,21389,21392,21507,21524,21647,21650,21656,21659,21775,21778,21781,21784,21787,21790,22013,22016,22019,22150,22153,22280,22283,22294,22305,22308,22311,22314,22317,22320,22328,22336,22340,22343,22346,22352,22366,22372,22375,22378,22382,22385,22388,22391,22394,22397,22400,22415,22418,22421,22428,22431,22437,22443,22547,22550,22553,22556,22559,22562,22565,22568,22571,22574,22577,22580,22583],[10,11,12],"h2",{"id":12},"样式",[14,15,16],"h3",{"id":16},"postcss部分",[18,19,20,27,28,31,32,35,36,41,42,47],"p",{},[21,22,23],"strong",{},[24,25,26],"code",{},"autoprefixer"," 插件比较常用，可以根据browserslist 自动加浏览器前缀（",[24,29,30],{},"-webkit-","、",[24,33,34],{},"-moz-","），",[21,37,38],{},[24,39,40],{},"postcss-preset-env"," 插件可以帮我们把CSS语法降低为兼容性语法。",[21,43,44],{},[24,45,46],{},"cssnano"," 可以帮我们压缩CSS、去掉空格、注释、合并规则。并且要注意的是postcss的插件是按照数组顺序来配置的·，并且是按照顺序执行，而与之不同的是webpack的loader，是从下往上执行。",[18,49,50],{},"postcss处理的是标准css，把标准css给解析成AST，然后让插件改，sass\u002Fless是预处理器，处理的是他们自己的语法，编译成css之后postcss才接管。",[14,52,53],{"id":53},"tailwind",[18,55,56,57,60,61,67,71,72,75,76,79,80,83,84,87,88,91],{},"tailwind3开始 jit就是默认引擎了，它",[21,58,59],{},"只生成你用到的类","，而不是先生成全部再“清除”未使用的",[62,63],"a",{"href":64,"rel":65},"https:\u002F\u002Fstackoverflow.com\u002Frevisions\u002Fdb144099-3e0d-4d40-ac2c-1c2372c9caff\u002Fview-source",[66],"nofollow",[62,68],{"href":69,"rel":70},"https:\u002F\u002Fgithub.com\u002Ftailwindlabs\u002Ftailwindcss\u002Fdiscussions\u002F17570?sort=top#1",[66],"。所以 ",[24,73,74],{},"purge"," 选项在 v3 里改名叫 ",[24,77,78],{},"content","，而且 ",[24,81,82],{},"mode: 'jit'"," 这个配置项",[21,85,86],{},"已经不需要了","，写了反而会警告",[62,89],{"href":64,"rel":90},[66],"。",[18,93,94],{},"从版本4开始，@theme取代了tailwind.config.js，我们填写主题配置都在css里面直接配置",[96,97,102],"pre",{"className":98,"code":99,"language":100,"meta":101,"style":101},"language-css shiki shiki-themes github-light github-dark","@theme {\n    --color-primary: #3b82f6\n    --breakpoint-sm: 640px;\n    --breakpoint-md: 768px;\n    --breakpoint-lg: 1024px; %%自定义断点%%\n    --breakpoint-xl: 1280px;\n    --breakpoint-2xl: 1536px;\n}\n","css","",[24,103,104,117,131,146,161,177,192,207],{"__ignoreMap":101},[105,106,109,113],"span",{"class":107,"line":108},"line",1,[105,110,112],{"class":111},"szBVR","@theme",[105,114,116],{"class":115},"sVt8B"," {\n",[105,118,120,124,127],{"class":107,"line":119},2,[105,121,123],{"class":122},"s4XuR","    --color-primary",[105,125,126],{"class":115},": ",[105,128,130],{"class":129},"sj4cs","#3b82f6\n",[105,132,134,137,140,143],{"class":107,"line":133},3,[105,135,136],{"class":115},"    --breakpoint-sm: ",[105,138,139],{"class":129},"640",[105,141,142],{"class":111},"px",[105,144,145],{"class":115},";\n",[105,147,149,152,154,157,159],{"class":107,"line":148},4,[105,150,151],{"class":122},"    --breakpoint-md",[105,153,126],{"class":115},[105,155,156],{"class":129},"768",[105,158,142],{"class":111},[105,160,145],{"class":115},[105,162,164,167,169,172,174],{"class":107,"line":163},5,[105,165,166],{"class":122},"    --breakpoint-lg",[105,168,126],{"class":115},[105,170,171],{"class":129},"1024",[105,173,142],{"class":111},[105,175,176],{"class":115},"; %%自定义断点%%\n",[105,178,180,183,185,188,190],{"class":107,"line":179},6,[105,181,182],{"class":122},"    --breakpoint-xl",[105,184,126],{"class":115},[105,186,187],{"class":129},"1280",[105,189,142],{"class":111},[105,191,145],{"class":115},[105,193,195,198,200,203,205],{"class":107,"line":194},7,[105,196,197],{"class":122},"    --breakpoint-2xl",[105,199,126],{"class":115},[105,201,202],{"class":129},"1536",[105,204,142],{"class":111},[105,206,145],{"class":115},[105,208,210],{"class":107,"line":209},8,[105,211,212],{"class":115},"}\n",[18,214,215],{},"到时候会自动生成bg-primary或者text-等这些类。",[18,217,218,219,222],{},"有一点比较重要,tailwind不能动态拼接类名，比如说 ",[24,220,221],{},"text-${color}-600"," 是无效的，因为 JIT 扫描的是静态字符串，它不认识运行时才拼出来的类名。正确做法是用完整的类名做条件判断：",[96,224,228],{"className":225,"code":226,"language":227,"meta":101,"style":101},"language-html shiki shiki-themes github-light github-dark","\u003Cdiv :class=\"error ? 'text-red-600' : 'text-green-600'\">\u003C\u002Fdiv>\n","html",[24,229,230],{"__ignoreMap":101},[105,231,232,235,239,243,246,250,253,255],{"class":107,"line":108},[105,233,234],{"class":115},"\u003C",[105,236,238],{"class":237},"s9eBZ","div",[105,240,242],{"class":241},"sScJk"," :class",[105,244,245],{"class":115},"=",[105,247,249],{"class":248},"sZZnC","\"error ? 'text-red-600' : 'text-green-600'\"",[105,251,252],{"class":115},">\u003C\u002F",[105,254,238],{"class":237},[105,256,257],{"class":115},">\n",[18,259,260,261,264,265],{},"如果需要动态值，用任意值语法 ",[24,262,263],{},"text-[#bada55]","，或者用 CSS 变量 ",[24,266,267],{},"text-(--my-color)",[18,269,270],{},"tailwindv4 通过@import 'tailwindcss'; 一个命令等价于：",[96,272,274],{"className":98,"code":273,"language":100,"meta":101,"style":101},"@layer theme, base, components, utilities;\n\n@import \"tailwindcss\u002Ftheme.css\" layer(theme);\n@import \"tailwindcss\u002Fpreflight.css\" layer(base);\n@import \"tailwindcss\u002Futilities.css\" layer(utilities);\n",[24,275,276,302,308,328,343],{"__ignoreMap":101},[105,277,278,281,284,287,290,292,295,297,300],{"class":107,"line":108},[105,279,280],{"class":111},"@layer",[105,282,283],{"class":122}," theme",[105,285,286],{"class":115},", ",[105,288,289],{"class":122},"base",[105,291,286],{"class":115},[105,293,294],{"class":122},"components",[105,296,286],{"class":115},[105,298,299],{"class":122},"utilities",[105,301,145],{"class":115},[105,303,304],{"class":107,"line":119},[105,305,307],{"emptyLinePlaceholder":306},true,"\n",[105,309,310,313,316,319,322,325],{"class":107,"line":133},[105,311,312],{"class":111},"@import",[105,314,315],{"class":248}," \"tailwindcss\u002Ftheme.css\"",[105,317,318],{"class":129}," layer",[105,320,321],{"class":115},"(",[105,323,324],{"class":122},"theme",[105,326,327],{"class":115},");\n",[105,329,330,332,335,337,339,341],{"class":107,"line":148},[105,331,312],{"class":111},[105,333,334],{"class":248}," \"tailwindcss\u002Fpreflight.css\"",[105,336,318],{"class":129},[105,338,321],{"class":115},[105,340,289],{"class":122},[105,342,327],{"class":115},[105,344,345,347,350,352,354,356],{"class":107,"line":163},[105,346,312],{"class":111},[105,348,349],{"class":248}," \"tailwindcss\u002Futilities.css\"",[105,351,318],{"class":129},[105,353,321],{"class":115},[105,355,299],{"class":122},[105,357,327],{"class":115},[18,359,360,361,31,364,367],{},"我们从这四个层级来说的话，越靠后优先级越高，我们经常用theme来设计颜色、间距、字体、断体等，会自动生成对应的工具类。\nbase用来元素重置和全局基础样式，也可以叫prelight，需要重置 HTML 元素默认样式时，比如去掉标题的默认字号、列表的圆点。\ncomponents是可复用可覆盖的组件类，封装像 ",[24,362,363],{},".btn",[24,365,366],{},".card"," 这样的组件模式，希望被工具类覆盖时\nutilities是原子化工具类比如说flex bg-red-500等，日常写样式的主要方式，优先级最高，能覆盖前面的所有层。css有个规则， @layer，同一层叠层内的规则将层叠在一起，这给予了 web 开发者对层叠机制的更多控制",[18,369,370,375,376,379,380,382,383,386],{},[371,372],"img",{"alt":373,"src":374},"层叠层优先级示意图","https:\u002F\u002Fmdn.github.io\u002Fshared-assets\u002Fimages\u002Fdiagrams\u002Fcss\u002Fat-rules\u002Flayer-cascade.svg","\n带 ",[24,377,378],{},"!important"," 标志的声明，优先于_普通声明_，即无 ",[24,381,378],{}," 标志的常规声明，过渡具有最高的优先级。按 ",[21,384,385],{},"CSS Cascade 5"," 规范，优先级从高到低是：",[96,388,393],{"className":389,"code":391,"language":392},[390],"language-text","1. 过渡（Transition）声明\n2. 重要用户代理声明（!important user agent）\n3. 重要用户声明（!important user）\n4. 重要作者声明（!important author）\n5. 动画（Animation）声明\n6. 普通作者声明（Normal author）\n7. 普通用户声明（Normal user）\n8. 普通用户代理声明（Normal user agent）\n","text",[24,394,391],{"__ignoreMap":101},[18,396,397],{},"看起来像个镜面对称一样，user agent \u002F user\u002F author | author\u002F user\u002F user agent",[18,399,400],{},"这里提一嘴用户代理和过渡声明：",[18,402,403],{},"用户代理是浏览器自带的默认样式表，你写一个没加任何 CSS 的 HTML：",[96,405,407],{"className":225,"code":406,"language":227,"meta":101,"style":101},"\u003Ch1>标题\u003C\u002Fh1>\n\u003Cp>段落\u003C\u002Fp>\n\u003Cul>\u003Cli>列表项\u003C\u002Fli>\u003C\u002Ful>\n",[24,408,409,423,436],{"__ignoreMap":101},[105,410,411,413,416,419,421],{"class":107,"line":108},[105,412,234],{"class":115},[105,414,415],{"class":237},"h1",[105,417,418],{"class":115},">标题\u003C\u002F",[105,420,415],{"class":237},[105,422,257],{"class":115},[105,424,425,427,429,432,434],{"class":107,"line":119},[105,426,234],{"class":115},[105,428,18],{"class":237},[105,430,431],{"class":115},">段落\u003C\u002F",[105,433,18],{"class":237},[105,435,257],{"class":115},[105,437,438,440,443,446,449,452,454,456,458],{"class":107,"line":133},[105,439,234],{"class":115},[105,441,442],{"class":237},"ul",[105,444,445],{"class":115},">\u003C",[105,447,448],{"class":237},"li",[105,450,451],{"class":115},">列表项\u003C\u002F",[105,453,448],{"class":237},[105,455,252],{"class":115},[105,457,442],{"class":237},[105,459,257],{"class":115},[18,461,462],{},"浏览器会自己加上h1的字号、字重、上下margin，p的上下margin，ul的左padding和原点，body的8px margin，因此我们引入@base的时候就会移除默认样式，当然，我们可以在@layer base中加入一些自己的基础样式，比如说：",[96,464,466],{"className":98,"code":465,"language":100,"meta":101,"style":101},"@import \"tailwindcss\";\n\n@layer base {\n  h1 {\n    @apply text-3xl font-bold;\n  }\n  body {\n    @apply bg-gray-50 text-gray-900;\n  }\n}\n",[24,467,468,477,481,490,497,505,510,517,524,529],{"__ignoreMap":101},[105,469,470,472,475],{"class":107,"line":108},[105,471,312],{"class":111},[105,473,474],{"class":248}," \"tailwindcss\"",[105,476,145],{"class":115},[105,478,479],{"class":107,"line":119},[105,480,307],{"emptyLinePlaceholder":306},[105,482,483,485,488],{"class":107,"line":133},[105,484,280],{"class":111},[105,486,487],{"class":122}," base",[105,489,116],{"class":115},[105,491,492,495],{"class":107,"line":148},[105,493,494],{"class":237},"  h1",[105,496,116],{"class":115},[105,498,499,502],{"class":107,"line":163},[105,500,501],{"class":111},"    @apply",[105,503,504],{"class":115}," text-3xl font-bold;\n",[105,506,507],{"class":107,"line":179},[105,508,509],{"class":115},"  }\n",[105,511,512,515],{"class":107,"line":194},[105,513,514],{"class":237},"  body",[105,516,116],{"class":115},[105,518,519,521],{"class":107,"line":209},[105,520,501],{"class":111},[105,522,523],{"class":115}," bg-gray-50 text-gray-900;\n",[105,525,527],{"class":107,"line":526},9,[105,528,509],{"class":115},[105,530,532],{"class":107,"line":531},10,[105,533,212],{"class":115},[18,535,536,537,540,541,544,545],{},"然后呢，过渡优先级最高这个说法是来自CSS级联里面一个比较特殊的设计，和常规的优先级不是一个维度，过渡不是样式，是状态间插值，color:red -> color:blue 的情况，比如说你加了 transition: color 0.3s，浏览器会在0.3s内持续计算中间值，这个中间值在某一瞬间是 ",[24,538,539],{},"rgb(128, 0, 128)"," 之类的，它",[21,542,543],{},"既不是 red 也不是 blue","。如果这个中间值能被其他 CSS 规则覆盖，过渡就会“抖动”或“卡顿”。所以规范规定：",[21,546,547,548,91],{},"过渡期间计算出来的值，优先级高于任何其他声明，包括 ",[24,549,378],{},[18,551,552,553,556],{},"如果说想要在css里模拟wpf的动画控制，可以使用web Animation API WAAPI来实现，WAAPI 是浏览器提供的",[21,554,555],{},"命令式动画 API","，更接近 WPF：",[96,558,562],{"className":559,"code":560,"language":561,"meta":101,"style":101},"language-js shiki shiki-themes github-light github-dark","const animation = element.animate(\n  [{ transform: 'translateX(0)' }, { transform: 'translateX(100px)' }],\n  { duration: 300, easing: 'ease' }\n);\n\n\u002F\u002F 检测\nconsole.log(animation.playState);  \u002F\u002F 'running' | 'paused' | 'finished'\n\n\u002F\u002F 打断\nanimation.cancel();\n\n\u002F\u002F 暂停、继续\nanimation.pause();\nanimation.play();\n\n\u002F\u002F 监听\nanimation.onfinish = () => { \u002F* ... *\u002F };\n","js",[24,563,564,584,601,618,622,626,632,646,650,655,666,671,677,687,697,702,708],{"__ignoreMap":101},[105,565,566,569,572,575,578,581],{"class":107,"line":108},[105,567,568],{"class":111},"const",[105,570,571],{"class":129}," animation",[105,573,574],{"class":111}," =",[105,576,577],{"class":115}," element.",[105,579,580],{"class":241},"animate",[105,582,583],{"class":115},"(\n",[105,585,586,589,592,595,598],{"class":107,"line":119},[105,587,588],{"class":115},"  [{ transform: ",[105,590,591],{"class":248},"'translateX(0)'",[105,593,594],{"class":115}," }, { transform: ",[105,596,597],{"class":248},"'translateX(100px)'",[105,599,600],{"class":115}," }],\n",[105,602,603,606,609,612,615],{"class":107,"line":133},[105,604,605],{"class":115},"  { duration: ",[105,607,608],{"class":129},"300",[105,610,611],{"class":115},", easing: ",[105,613,614],{"class":248},"'ease'",[105,616,617],{"class":115}," }\n",[105,619,620],{"class":107,"line":148},[105,621,327],{"class":115},[105,623,624],{"class":107,"line":163},[105,625,307],{"emptyLinePlaceholder":306},[105,627,628],{"class":107,"line":179},[105,629,631],{"class":630},"sJ8bj","\u002F\u002F 检测\n",[105,633,634,637,640,643],{"class":107,"line":194},[105,635,636],{"class":115},"console.",[105,638,639],{"class":241},"log",[105,641,642],{"class":115},"(animation.playState);  ",[105,644,645],{"class":630},"\u002F\u002F 'running' | 'paused' | 'finished'\n",[105,647,648],{"class":107,"line":209},[105,649,307],{"emptyLinePlaceholder":306},[105,651,652],{"class":107,"line":526},[105,653,654],{"class":630},"\u002F\u002F 打断\n",[105,656,657,660,663],{"class":107,"line":531},[105,658,659],{"class":115},"animation.",[105,661,662],{"class":241},"cancel",[105,664,665],{"class":115},"();\n",[105,667,669],{"class":107,"line":668},11,[105,670,307],{"emptyLinePlaceholder":306},[105,672,674],{"class":107,"line":673},12,[105,675,676],{"class":630},"\u002F\u002F 暂停、继续\n",[105,678,680,682,685],{"class":107,"line":679},13,[105,681,659],{"class":115},[105,683,684],{"class":241},"pause",[105,686,665],{"class":115},[105,688,690,692,695],{"class":107,"line":689},14,[105,691,659],{"class":115},[105,693,694],{"class":241},"play",[105,696,665],{"class":115},[105,698,700],{"class":107,"line":699},15,[105,701,307],{"emptyLinePlaceholder":306},[105,703,705],{"class":107,"line":704},16,[105,706,707],{"class":630},"\u002F\u002F 监听\n",[105,709,711,713,716,718,721,724,727,730],{"class":107,"line":710},17,[105,712,659],{"class":115},[105,714,715],{"class":241},"onfinish",[105,717,574],{"class":111},[105,719,720],{"class":115}," () ",[105,722,723],{"class":111},"=>",[105,725,726],{"class":115}," { ",[105,728,729],{"class":630},"\u002F* ... *\u002F",[105,731,732],{"class":115}," };\n",[18,734,735,737,738,741,742,744],{},[24,736,280],{}," 定义的是",[21,739,740],{},"作者样式内部","的优先级。同一层级的作者样式里，",[24,743,280],{}," 的规则是：",[442,746,747,756,762],{},[448,748,749,752,753,755],{},[21,750,751],{},"未分层的样式优先级最高","（也就是没加 ",[24,754,280],{}," 的普通 CSS）。",[448,757,758,761],{},[21,759,760],{},"分层样式按声明的顺序","，后声明的层优先级更高。",[448,763,764],{},"层级内部的样式，再按常规的选择器优先级比较。",[96,766,769],{"className":767,"code":768,"language":392},[390],"@layer base, components, utilities;\n\n@layer utilities {\n  .text-red { color: red; }\n}\n\n@layer components {\n  .text-red { color: blue; }\n}\n\n.text-red { color: green; }  \u002F* 无层级，优先级最高 *\u002F\n",[24,770,768],{"__ignoreMap":101},[18,772,773],{},"所以，如果想要两个一起使用，我们就得把css也一起放入@layer中，而如果是手写的css组件样式，希望被tailwind工具类覆盖，就得放入@layer components中。",[96,775,777],{"className":98,"code":776,"language":100,"meta":101,"style":101},"@import \"tailwindcss\";\n\n@layer components {\n  .card {\n    @apply rounded-lg bg-white p-4 shadow;\n  }\n}\n",[24,778,779,787,791,800,807,814,818],{"__ignoreMap":101},[105,780,781,783,785],{"class":107,"line":108},[105,782,312],{"class":111},[105,784,474],{"class":248},[105,786,145],{"class":115},[105,788,789],{"class":107,"line":119},[105,790,307],{"emptyLinePlaceholder":306},[105,792,793,795,798],{"class":107,"line":133},[105,794,280],{"class":111},[105,796,797],{"class":122}," components",[105,799,116],{"class":115},[105,801,802,805],{"class":107,"line":148},[105,803,804],{"class":241},"  .card",[105,806,116],{"class":115},[105,808,809,811],{"class":107,"line":163},[105,810,501],{"class":111},[105,812,813],{"class":115}," rounded-lg bg-white p-4 shadow;\n",[105,815,816],{"class":107,"line":179},[105,817,509],{"class":115},[105,819,820],{"class":107,"line":194},[105,821,212],{"class":115},[18,823,824,825,91],{},"有个很有趣的争议。叫做@apply的到底该不该用，@apply会把原子类的样式内联到自定义css内， 如果到处用，CSS 体积会膨胀，而且失去了原子化“组合灵活”的优势。所以我们只会在比较高频使用的重复类组合比如.btn来使用，或者需要",[21,826,827],{},"给第三方库的类注入样式",[14,829,830],{"id":100},"CSS",[18,832,833],{},"普通作者声明内部优先级从高到低是：",[835,836,837,853],"table",{},[838,839,840],"thead",{},[841,842,843,847,850],"tr",{},[844,845,846],"th",{},"优先级",[844,848,849],{},"来源",[844,851,852],{},"例子",[854,855,856,872,887,907,924],"tbody",{},[841,857,858,862,867],{},[859,860,861],"td",{},"1",[859,863,864],{},[21,865,866],{},"行内 style",[859,868,869],{},[24,870,871],{},"\u003Cdiv style=\"color: red\">",[841,873,874,877,882],{},[859,875,876],{},"2",[859,878,879],{},[21,880,881],{},"ID 选择器",[859,883,884],{},[24,885,886],{},"#header",[841,888,889,892,897],{},[859,890,891],{},"3",[859,893,894],{},[21,895,896],{},"类 \u002F 属性 \u002F 伪类",[859,898,899,31,901,31,904],{},[24,900,363],{},[24,902,903],{},"[type=\"text\"]",[24,905,906],{},":hover",[841,908,909,912,917],{},[859,910,911],{},"4",[859,913,914],{},[21,915,916],{},"元素 \u002F 伪元素",[859,918,919,31,921],{},[24,920,238],{},[24,922,923],{},"::before",[841,925,926,929,934],{},[859,927,928],{},"5",[859,930,931],{},[21,932,933],{},"通配符 \u002F 继承",[859,935,936,939],{},[24,937,938],{},"*","、继承来的值",[18,941,942,943,946,947,949,950,953,954,91],{},"CSS有一个叫做(a, b, c) 三元组表示法来表示选择器优先级，行内 style 的优先级是 ",[24,944,945],{},"(1, 0, 0, 0)","，比任何选择器都高，",[24,948,378],{}," 的优先级",[21,951,952],{},"高于所有普通声明","，包括行内 style。比较规则：",[21,955,956],{},"从左到右逐位比，大的赢",[96,958,961],{"className":959,"code":960,"language":392},[390],"#header .nav a { }     \u002F* (1, 1, 1) *\u002F\n.nav a { }             \u002F* (0, 1, 1) *\u002F\na { }                  \u002F* (0, 0, 1) *\u002F\n",[24,962,960],{"__ignoreMap":101},[18,964,965],{},"常用选择器：",[18,967,968,969,972,973,976],{},"后代选择器： 空格\n直接子选择器： >\n相邻兄弟： +\n通用兄弟： ~\n父选择器： ",[970,971],"has",{}," ，它本质是",[21,974,975],{},"关系选择器","，可以做很多以前需要 JS 才能做的事：",[96,978,980],{"className":98,"code":979,"language":100,"meta":101,"style":101},"\u002F* 选中「内部包含 img 的 div」 *\u002F\ndiv:has(img) {\n  border: 1px solid #ccc;\n}\n\n\u002F* 表单里有 input 聚焦时，给表单加样式 *\u002F\nform:has(input:focus) {\n  border-color: blue;\n}\n\n\u002F* 选中「后面跟着 p 的 h2」 *\u002F\nh2:has(+ p) {\n  margin-bottom: 0;\n}\n\n\u002F* 选中「不包含任何子元素的空 div」 *\u002F\ndiv:not(:has(*)) {\n  display: none;\n}\n",[24,981,982,987,1001,1020,1024,1028,1033,1050,1062,1066,1070,1075,1091,1103,1107,1111,1116,1134,1147],{"__ignoreMap":101},[105,983,984],{"class":107,"line":108},[105,985,986],{"class":630},"\u002F* 选中「内部包含 img 的 div」 *\u002F\n",[105,988,989,991,994,996,998],{"class":107,"line":119},[105,990,238],{"class":237},[105,992,993],{"class":241},":has",[105,995,321],{"class":115},[105,997,371],{"class":237},[105,999,1000],{"class":115},") {\n",[105,1002,1003,1006,1008,1010,1012,1015,1018],{"class":107,"line":133},[105,1004,1005],{"class":129},"  border",[105,1007,126],{"class":115},[105,1009,861],{"class":129},[105,1011,142],{"class":111},[105,1013,1014],{"class":129}," solid",[105,1016,1017],{"class":129}," #ccc",[105,1019,145],{"class":115},[105,1021,1022],{"class":107,"line":148},[105,1023,212],{"class":115},[105,1025,1026],{"class":107,"line":163},[105,1027,307],{"emptyLinePlaceholder":306},[105,1029,1030],{"class":107,"line":179},[105,1031,1032],{"class":630},"\u002F* 表单里有 input 聚焦时，给表单加样式 *\u002F\n",[105,1034,1035,1038,1040,1042,1045,1048],{"class":107,"line":194},[105,1036,1037],{"class":237},"form",[105,1039,993],{"class":241},[105,1041,321],{"class":115},[105,1043,1044],{"class":237},"input",[105,1046,1047],{"class":241},":focus",[105,1049,1000],{"class":115},[105,1051,1052,1055,1057,1060],{"class":107,"line":209},[105,1053,1054],{"class":129},"  border-color",[105,1056,126],{"class":115},[105,1058,1059],{"class":129},"blue",[105,1061,145],{"class":115},[105,1063,1064],{"class":107,"line":526},[105,1065,212],{"class":115},[105,1067,1068],{"class":107,"line":531},[105,1069,307],{"emptyLinePlaceholder":306},[105,1071,1072],{"class":107,"line":668},[105,1073,1074],{"class":630},"\u002F* 选中「后面跟着 p 的 h2」 *\u002F\n",[105,1076,1077,1079,1081,1083,1086,1089],{"class":107,"line":673},[105,1078,10],{"class":237},[105,1080,993],{"class":241},[105,1082,321],{"class":115},[105,1084,1085],{"class":111},"+",[105,1087,1088],{"class":237}," p",[105,1090,1000],{"class":115},[105,1092,1093,1096,1098,1101],{"class":107,"line":679},[105,1094,1095],{"class":129},"  margin-bottom",[105,1097,126],{"class":115},[105,1099,1100],{"class":129},"0",[105,1102,145],{"class":115},[105,1104,1105],{"class":107,"line":689},[105,1106,212],{"class":115},[105,1108,1109],{"class":107,"line":699},[105,1110,307],{"emptyLinePlaceholder":306},[105,1112,1113],{"class":107,"line":704},[105,1114,1115],{"class":630},"\u002F* 选中「不包含任何子元素的空 div」 *\u002F\n",[105,1117,1118,1120,1123,1125,1127,1129,1131],{"class":107,"line":710},[105,1119,238],{"class":237},[105,1121,1122],{"class":241},":not",[105,1124,321],{"class":115},[105,1126,993],{"class":241},[105,1128,321],{"class":115},[105,1130,938],{"class":237},[105,1132,1133],{"class":115},")) {\n",[105,1135,1137,1140,1142,1145],{"class":107,"line":1136},18,[105,1138,1139],{"class":129},"  display",[105,1141,126],{"class":115},[105,1143,1144],{"class":129},"none",[105,1146,145],{"class":115},[105,1148,1150],{"class":107,"line":1149},19,[105,1151,212],{"class":115},[18,1153,1154],{},"伪类和伪元素（常和关系选择器配合）",[835,1156,1157,1170],{},[838,1158,1159],{},[841,1160,1161,1164,1167],{},[844,1162,1163],{},"类型",[844,1165,1166],{},"写法",[844,1168,1169],{},"含义",[854,1171,1172,1184,1195,1207,1219,1231,1243,1255,1267],{},[841,1173,1174,1177,1181],{},[859,1175,1176],{},"伪类",[859,1178,1179],{},[24,1180,906],{},[859,1182,1183],{},"鼠标悬停",[841,1185,1186,1188,1192],{},[859,1187,1176],{},[859,1189,1190],{},[24,1191,1047],{},[859,1193,1194],{},"获得焦点",[841,1196,1197,1199,1204],{},[859,1198,1176],{},[859,1200,1201],{},[24,1202,1203],{},":first-child",[859,1205,1206],{},"第一个子元素",[841,1208,1209,1211,1216],{},[859,1210,1176],{},[859,1212,1213],{},[24,1214,1215],{},":last-child",[859,1217,1218],{},"最后一个子元素",[841,1220,1221,1223,1228],{},[859,1222,1176],{},[859,1224,1225],{},[24,1226,1227],{},":nth-child(n)",[859,1229,1230],{},"第 n 个子元素",[841,1232,1233,1235,1240],{},[859,1234,1176],{},[859,1236,1237],{},[24,1238,1239],{},":not(selector)",[859,1241,1242],{},"排除某个选择器",[841,1244,1245,1248,1252],{},[859,1246,1247],{},"伪元素",[859,1249,1250],{},[24,1251,923],{},[859,1253,1254],{},"元素前插入内容",[841,1256,1257,1259,1264],{},[859,1258,1247],{},[859,1260,1261],{},[24,1262,1263],{},"::after",[859,1265,1266],{},"元素后插入内容",[841,1268,1269,1271,1276],{},[859,1270,1247],{},[859,1272,1273],{},[24,1274,1275],{},"::placeholder",[859,1277,1278],{},"输入框占位符",[18,1280,1281,1282,91],{},"@namespace是CSS中一个少用，但是特定场景下需要使用的一个规则，作用是",[21,1283,1284],{},"给选择器里的元素和属性指定 XML 命名空间",[18,1286,1287,1288,1290,1291,1293],{},"在 HTML 里，元素名是全局的，",[24,1289,238],{}," 就是 ",[24,1292,238],{},"。但在 XML 或 SVG 里，不同命名空间可以有同名元素",[96,1295,1299],{"className":1296,"code":1297,"language":1298,"meta":101,"style":101},"language-xml shiki shiki-themes github-light github-dark","\u003Chtml:div>HTML 的 div\u003C\u002Fhtml:div>\n\u003Csvg:div>SVG 的 div\u003C\u002Fsvg:div>\n","xml",[24,1300,1301,1315],{"__ignoreMap":101},[105,1302,1303,1305,1308,1311,1313],{"class":107,"line":108},[105,1304,234],{"class":115},[105,1306,1307],{"class":237},"html:div",[105,1309,1310],{"class":115},">HTML 的 div\u003C\u002F",[105,1312,1307],{"class":237},[105,1314,257],{"class":115},[105,1316,1317,1319,1322,1325,1327],{"class":107,"line":119},[105,1318,234],{"class":115},[105,1320,1321],{"class":237},"svg:div",[105,1323,1324],{"class":115},">SVG 的 div\u003C\u002F",[105,1326,1321],{"class":237},[105,1328,257],{"class":115},[18,1330,1331,1332,1334,1335,91,1337,1340,1341,1343,1344,1347,1348,1350],{},"如果直接用 ",[24,1333,238],{}," 选择，浏览器不知道你要选哪个命名空间下的 ",[24,1336,238],{},[24,1338,1339],{},"@namespace"," 就是用来区分它们的。",[24,1342,1339],{}," 必须写在所有样式规则之前（除了 ",[24,1345,1346],{},"@charset"," 和 ",[24,1349,312],{},"）。",[96,1352,1355],{"className":1353,"code":1354,"language":392},[390],"\u002F* 给某个命名空间起别名 *\u002F\n@namespace svg url(http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg);\n\n\u002F* 默认命名空间，没有前缀的选择器都属于它 *\u002F\n@namespace url(http:\u002F\u002Fwww.w3.org\u002F1999\u002Fxhtml);\n\n\u002F* 之后用 `svg|` 前缀来指定该命名空间 *\u002F\nsvg|circle {\n  fill: red;\n}\n",[24,1356,1354],{"__ignoreMap":101},[18,1358,1359,1360,1363,1364,1347,1367,1370,1371,1374,1375,1378,1379,1381,1382,1384],{},"HTML5 允许在 ",[24,1361,1362],{},"\u003Chtml>"," 里直接写 ",[24,1365,1366],{},"\u003Csvg>",[24,1368,1369],{},"\u003Cmath>","，浏览器会自动给它们分配 SVG 和 MathML 命名空间，这里",[21,1372,1373],{},"不需要","写 ",[24,1376,1377],{},"xmlns","，浏览器知道 ",[24,1380,1366],{}," 是 SVG 命名空间。但在独立 SVG 文件里，",[24,1383,1377],{}," 是必须的。",[96,1386,1389],{"className":1387,"code":1388,"language":392},[390],"\u003Csvg width=\"100\" height=\"100\">\n  \u003Ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"red\" \u002F>\n\u003C\u002Fsvg>\n",[24,1390,1388],{"__ignoreMap":101},[18,1392,1393],{},"这里需要了解一个机制，html其实是有命名空间的，HTML5 和 XML 是两套不同的解析模型，只是语法上有相似之处，HTML5 文档里，元素可以属于三个命名空间：",[835,1395,1396,1409],{},[838,1397,1398],{},[841,1399,1400,1403,1406],{},[844,1401,1402],{},"命名空间 URI",[844,1404,1405],{},"前缀",[844,1407,1408],{},"用在哪些元素",[854,1410,1411,1431,1448,1464],{},[841,1412,1413,1418,1421],{},[859,1414,1415],{},[24,1416,1417],{},"http:\u002F\u002Fwww.w3.org\u002F1999\u002Fxhtml",[859,1419,1420],{},"无（默认）",[859,1422,1423,1424,31,1426,31,1428,1430],{},"所有 HTML 元素（",[24,1425,238],{},[24,1427,18],{},[24,1429,105],{},"...）",[841,1432,1433,1438,1443],{},[859,1434,1435],{},[24,1436,1437],{},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[859,1439,1440],{},[24,1441,1442],{},"svg:",[859,1444,1445,1447],{},[24,1446,1366],{}," 及其子元素",[841,1449,1450,1455,1460],{},[859,1451,1452],{},[24,1453,1454],{},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[859,1456,1457],{},[24,1458,1459],{},"math:",[859,1461,1462,1447],{},[24,1463,1369],{},[841,1465,1466,1468,1470],{},[859,1467],{},[859,1469],{},[859,1471],{},[18,1473,1474,1477,1478,1480,1481,1484,1485,1488],{},[21,1475,1476],{},"HTML 解析器是“预定义”的。"," 规范里明确写了：当解析器遇到 ",[24,1479,1366],{}," 标签时，",[21,1482,1483],{},"自动切换到 SVG 命名空间","，直到遇到 ",[24,1486,1487],{},"\u003C\u002Fsvg>"," 闭合。",[18,1490,1491,1492,31,1495,1498],{},"在DOM中，属性分为两个类型，一个叫做无命名空间属性，一个叫做有命名空间属性。 前者是 属性名就是普通字符串，没有命名空间，",[24,1493,1494],{},"checked",[24,1496,1497],{},"disabled","。 后者属于有命名空间的属性，属性名带前缀，属于某个命名空间，xml:lang，xlink:href。",[18,1500,1501],{},"你可以在devtools中验证：",[96,1503,1505],{"className":559,"code":1504,"language":561,"meta":101,"style":101},"const input = document.querySelector('input')\ninput.checked = true\n\n\u002F\u002F 查看属性节点\nconst attr = input.getAttributeNode('checked')\nconsole.log(attr.namespaceURI)  \u002F\u002F null\nconsole.log(attr.name)          \u002F\u002F \"checked\"\nconsole.log(attr.localName)     \u002F\u002F \"checked\"\nconsole.log(attr.prefix)        \u002F\u002F null\n",[24,1506,1507,1530,1540,1544,1549,1571,1583,1595,1606],{"__ignoreMap":101},[105,1508,1509,1511,1514,1516,1519,1522,1524,1527],{"class":107,"line":108},[105,1510,568],{"class":111},[105,1512,1513],{"class":129}," input",[105,1515,574],{"class":111},[105,1517,1518],{"class":115}," document.",[105,1520,1521],{"class":241},"querySelector",[105,1523,321],{"class":115},[105,1525,1526],{"class":248},"'input'",[105,1528,1529],{"class":115},")\n",[105,1531,1532,1535,1537],{"class":107,"line":119},[105,1533,1534],{"class":115},"input.checked ",[105,1536,245],{"class":111},[105,1538,1539],{"class":129}," true\n",[105,1541,1542],{"class":107,"line":133},[105,1543,307],{"emptyLinePlaceholder":306},[105,1545,1546],{"class":107,"line":148},[105,1547,1548],{"class":630},"\u002F\u002F 查看属性节点\n",[105,1550,1551,1553,1556,1558,1561,1564,1566,1569],{"class":107,"line":163},[105,1552,568],{"class":111},[105,1554,1555],{"class":129}," attr",[105,1557,574],{"class":111},[105,1559,1560],{"class":115}," input.",[105,1562,1563],{"class":241},"getAttributeNode",[105,1565,321],{"class":115},[105,1567,1568],{"class":248},"'checked'",[105,1570,1529],{"class":115},[105,1572,1573,1575,1577,1580],{"class":107,"line":179},[105,1574,636],{"class":115},[105,1576,639],{"class":241},[105,1578,1579],{"class":115},"(attr.namespaceURI)  ",[105,1581,1582],{"class":630},"\u002F\u002F null\n",[105,1584,1585,1587,1589,1592],{"class":107,"line":194},[105,1586,636],{"class":115},[105,1588,639],{"class":241},[105,1590,1591],{"class":115},"(attr.name)          ",[105,1593,1594],{"class":630},"\u002F\u002F \"checked\"\n",[105,1596,1597,1599,1601,1604],{"class":107,"line":209},[105,1598,636],{"class":115},[105,1600,639],{"class":241},[105,1602,1603],{"class":115},"(attr.localName)     ",[105,1605,1594],{"class":630},[105,1607,1608,1610,1612,1615],{"class":107,"line":526},[105,1609,636],{"class":115},[105,1611,639],{"class":241},[105,1613,1614],{"class":115},"(attr.prefix)        ",[105,1616,1582],{"class":630},[18,1618,1619],{},"那么真正没有默认命名空间，需要xmlns的文档是哪些文件，就比如说Avalonia的axaml。",[96,1621,1624],{"className":1622,"code":1623,"language":392},[390],"\u003CWindow xmlns=\"https:\u002F\u002Fgithub.com\u002Favaloniaui\"\n        xmlns:x=\"http:\u002F\u002Fschemas.microsoft.com\u002Fwinfx\u002F2006\u002Fxaml\">\n",[24,1625,1623],{"__ignoreMap":101},[18,1627,1628,1629,1632],{},"CSS自定义属性： 属性名以 ",[24,1630,1631],{},"--"," 开头，值可以是任意 CSS 值。",[96,1634,1636],{"className":98,"code":1635,"language":100,"meta":101,"style":101},"\u003Cdiv class=\"item\" style=\"--i: 0\">\u003C\u002Fdiv>\n\u003Cdiv class=\"item\" style=\"--i: 1\">\u003C\u002Fdiv>\n\u003Cdiv class=\"item\" style=\"--i: 2\">\u003C\u002Fdiv>\n:root {\n  --primary-color: #3b82f6;\n  --spacing: 16px;\n  --i: 1;\n}\n",[24,1637,1638,1649,1658,1667,1674,1686,1700,1711],{"__ignoreMap":101},[105,1639,1640,1643,1646],{"class":107,"line":108},[105,1641,1642],{"class":115},"\u003Cdiv class=\"item\" style=\"--i: 0\"",[105,1644,1645],{"class":111},">",[105,1647,1648],{"class":115},"\u003C\u002Fdiv>\n",[105,1650,1651,1654,1656],{"class":107,"line":119},[105,1652,1653],{"class":115},"\u003Cdiv class=\"item\" style=\"--i: 1\"",[105,1655,1645],{"class":111},[105,1657,1648],{"class":115},[105,1659,1660,1663,1665],{"class":107,"line":133},[105,1661,1662],{"class":115},"\u003Cdiv class=\"item\" style=\"--i: 2\"",[105,1664,1645],{"class":111},[105,1666,1648],{"class":115},[105,1668,1669,1672],{"class":107,"line":148},[105,1670,1671],{"class":241},":root",[105,1673,116],{"class":115},[105,1675,1676,1679,1681,1684],{"class":107,"line":163},[105,1677,1678],{"class":122},"  --primary-color",[105,1680,126],{"class":115},[105,1682,1683],{"class":129},"#3b82f6",[105,1685,145],{"class":115},[105,1687,1688,1691,1693,1696,1698],{"class":107,"line":179},[105,1689,1690],{"class":122},"  --spacing",[105,1692,126],{"class":115},[105,1694,1695],{"class":129},"16",[105,1697,142],{"class":111},[105,1699,145],{"class":115},[105,1701,1702,1705,1707,1709],{"class":107,"line":194},[105,1703,1704],{"class":122},"  --i",[105,1706,126],{"class":115},[105,1708,861],{"class":129},[105,1710,145],{"class":115},[105,1712,1713],{"class":107,"line":209},[105,1714,212],{"class":115},[18,1716,1717,1718,1721],{},"用 ",[24,1719,1720],{},"var()"," 函数。",[96,1723,1725],{"className":98,"code":1724,"language":100,"meta":101,"style":101},"\u003Cdiv class=\"item\" style=\"--i: 0\">\u003C\u002Fdiv>\n\u003Cdiv class=\"item\" style=\"--i: 1\">\u003C\u002Fdiv>\n\u003Cdiv class=\"item\" style=\"--i: 2\">\u003C\u002Fdiv>\n.button {\n  color: var(--primary-color);\n  padding: var(--spacing);\n  z-index: var(--i);\n}\n",[24,1726,1727,1735,1743,1751,1758,1775,1791,1807],{"__ignoreMap":101},[105,1728,1729,1731,1733],{"class":107,"line":108},[105,1730,1642],{"class":115},[105,1732,1645],{"class":111},[105,1734,1648],{"class":115},[105,1736,1737,1739,1741],{"class":107,"line":119},[105,1738,1653],{"class":115},[105,1740,1645],{"class":111},[105,1742,1648],{"class":115},[105,1744,1745,1747,1749],{"class":107,"line":133},[105,1746,1662],{"class":115},[105,1748,1645],{"class":111},[105,1750,1648],{"class":115},[105,1752,1753,1756],{"class":107,"line":148},[105,1754,1755],{"class":241},".button",[105,1757,116],{"class":115},[105,1759,1760,1763,1765,1768,1770,1773],{"class":107,"line":163},[105,1761,1762],{"class":129},"  color",[105,1764,126],{"class":115},[105,1766,1767],{"class":129},"var",[105,1769,321],{"class":115},[105,1771,1772],{"class":122},"--primary-color",[105,1774,327],{"class":115},[105,1776,1777,1780,1782,1784,1786,1789],{"class":107,"line":179},[105,1778,1779],{"class":129},"  padding",[105,1781,126],{"class":115},[105,1783,1767],{"class":129},[105,1785,321],{"class":115},[105,1787,1788],{"class":122},"--spacing",[105,1790,327],{"class":115},[105,1792,1793,1796,1798,1800,1802,1805],{"class":107,"line":194},[105,1794,1795],{"class":129},"  z-index",[105,1797,126],{"class":115},[105,1799,1767],{"class":129},[105,1801,321],{"class":115},[105,1803,1804],{"class":122},"--i",[105,1806,327],{"class":115},[105,1808,1809],{"class":107,"line":209},[105,1810,212],{"class":115},[18,1812,1813],{},"css变量最大优势是运行时可变，可以让主题切换、动态计算、响应式调整变得更加容易：",[96,1815,1817],{"className":559,"code":1816,"language":561,"meta":101,"style":101},"\u002F\u002F 读\nconst value = getComputedStyle(element).getPropertyValue('--i')\n\n\u002F\u002F 写\nelement.style.setProperty('--i', 3)\n",[24,1818,1819,1824,1849,1853,1858],{"__ignoreMap":101},[105,1820,1821],{"class":107,"line":108},[105,1822,1823],{"class":630},"\u002F\u002F 读\n",[105,1825,1826,1828,1831,1833,1836,1839,1842,1844,1847],{"class":107,"line":119},[105,1827,568],{"class":111},[105,1829,1830],{"class":129}," value",[105,1832,574],{"class":111},[105,1834,1835],{"class":241}," getComputedStyle",[105,1837,1838],{"class":115},"(element).",[105,1840,1841],{"class":241},"getPropertyValue",[105,1843,321],{"class":115},[105,1845,1846],{"class":248},"'--i'",[105,1848,1529],{"class":115},[105,1850,1851],{"class":107,"line":133},[105,1852,307],{"emptyLinePlaceholder":306},[105,1854,1855],{"class":107,"line":148},[105,1856,1857],{"class":630},"\u002F\u002F 写\n",[105,1859,1860,1863,1866,1868,1870,1872,1874],{"class":107,"line":163},[105,1861,1862],{"class":115},"element.style.",[105,1864,1865],{"class":241},"setProperty",[105,1867,321],{"class":115},[105,1869,1846],{"class":248},[105,1871,286],{"class":115},[105,1873,891],{"class":129},[105,1875,1529],{"class":115},[18,1877,1878],{},"css的其他重要知识点：",[18,1880,1881,1882,1885,1886,1885,1889,1885,1892,1895,1896,1899,1900,1903],{},"z-index 不是全局的，是在一个层叠上下文里比较，层叠上下文是",[24,1883,1884],{},"relative"," \u002F ",[24,1887,1888],{},"absolute",[24,1890,1891],{},"fixed",[24,1893,1894],{},"sticky","，且 ",[24,1897,1898],{},"z-index"," 不是 ",[24,1901,1902],{},"auto","，父元素有transform，子元素的z-index: 9999 压不过父元素外面元素，子元素在父元素的层叠上下文里。",[18,1905,1906],{},"我们可以通过设置position + z-index来创建一个层叠上下文，比如说position: relative + z-index:1 ，或者你可以直接设置position: fixed\u002Fsticky也是不需要z-index的。或者opacity \u003C 1 ；trnsform != none 比如transform: translateX(0)；Isolation: isolate （专门创建层叠上下文)。",[1908,1909,1911],"h4",{"id":1910},"bfc","BFC",[18,1913,1914],{},"bfc全称块级格式化上下文，是一个独立的渲染区域，内部布局不影响外部，创建bfc的方法有：",[1916,1917,1918,1921,1924,1927,1930],"ol",{},[448,1919,1920],{},"overflow 不是 Visible",[448,1922,1923],{},"display: flow-root",[448,1925,1926],{},"position: absolute\u002Ffixed",[448,1928,1929],{},"float不是none",[448,1931,1932],{},"display: inline-block \u002Fflex\u002F grid",[18,1934,1935,1936,1940,1941,1944],{},"能够解决： 清除浮动，父元素创建bfc，保住浮动子元素。防止margin塌陷，两个相邻的bfc的margin不会合并。阻止元素被浮动元素覆盖。",[21,1937,1938],{},[24,1939,1923],{},"，它是专门为“创建 BFC 且不影响其他布局”设计的，比 ",[24,1942,1943],{},"overflow: hidden"," 干净。",[18,1946,1947],{},"margin塌陷，垂直方向两个margin会合并，取比较大那个：",[96,1949,1951],{"className":98,"code":1950,"language":100,"meta":101,"style":101},".a { margin-bottom: 20px; }\n.b { margin-top: 30px; }\n",[24,1952,1953,1973],{"__ignoreMap":101},[105,1954,1955,1958,1960,1963,1965,1968,1970],{"class":107,"line":108},[105,1956,1957],{"class":241},".a",[105,1959,726],{"class":115},[105,1961,1962],{"class":129},"margin-bottom",[105,1964,126],{"class":115},[105,1966,1967],{"class":129},"20",[105,1969,142],{"class":111},[105,1971,1972],{"class":115},"; }\n",[105,1974,1975,1978,1980,1983,1985,1988,1990],{"class":107,"line":119},[105,1976,1977],{"class":241},".b",[105,1979,726],{"class":115},[105,1981,1982],{"class":129},"margin-top",[105,1984,126],{"class":115},[105,1986,1987],{"class":129},"30",[105,1989,142],{"class":111},[105,1991,1972],{"class":115},[18,1993,1994,1995,1998,1999,2002],{},"父子塌陷，父元素没有padding、border、overflow等元素情况下，子元素的margin-top会溢出到父元素外面。可以让父元素加 ",[24,1996,1997],{},"padding-top"," 或 ",[24,2000,2001],{},"border-top","或者父元素创建bfc，或者在flex\u002Fgrid容器里面用gap来替代margin。",[18,2004,2005],{},"Flex和Grid是两种不同的布局思想，flex是内容驱动，子元素决定怎么排列，grid是容器定义网格，子元素填入。前者适合的是导航、工具栏、卡片列表，后者适合页面整体布局 复杂网格。CSS的正常布局模式中包含以下几种：\nblock布局，叫做块级，inline布局 行内布局， inline-block、Flex布局、Grid布局、定位布局(position)、浮动布局(float)、表格布局(table)\nflex grid会改变元素的外部显示类型和内部格式上下文，我们称元素对外表现叫block还是inline，对内部子元素按什么规则排列。\n比如你想让元素对内部flex，对外部block，可以这么写:",[96,2007,2009],{"className":98,"code":2008,"language":100,"meta":101,"style":101},"display: block flex; %%对外block 对内flex%%\ndisplay: inline-flex %%对外inline，对内flex%%\n",[24,2010,2011,2016],{"__ignoreMap":101},[105,2012,2013],{"class":107,"line":108},[105,2014,2015],{"class":115},"display: block flex; %%对外block 对内flex%%\n",[105,2017,2018],{"class":107,"line":119},[105,2019,2020],{"class":115},"display: inline-flex %%对外inline，对内flex%%\n",[18,2022,2023],{},"这里真的有坑，display: flex 是 display: block flex的简写，表示对外block，对内flex。display: block才是display: block flow的简写，表示对外block，对内flow，子元素会受到float的影响，会发生margin塌陷。",[18,2025,2026],{},"ok 我们来谈谈塌陷问题，display: block表示内部格式化上下文为BFC 块级格式化上下文，display: flex 对内flex，会发生FFC flex格式化上下文。",[18,2028,2029],{},"那么bfc为啥要发生，因为它规则之一是相邻块级元素垂直margin会合并，这个上面讲过，这就是规范行为，目的是让段落之间差距更加自然，ffc呢，flex容器里子元素不是块级元素，而是flex item，这样会让item的margin不会和其他item合并，margin差就是它们的和。",[18,2031,2032],{},"grid也是为ui布局设计的，margin语义是明确间距，因此grid item也不会塌陷。如果你想在bfc里避免margin塌陷，就给父元素加padding、border，或者让父元素创建bfc，display: flow-root、用gap替代，其中gap替代是最推荐的，gap是flex、grid原生属性，不受margin塌陷影响。",[18,2034,2035],{},"最后我们再把inline单独拿出来说，inline对应的是ifc 行内格式化上下文，和bfc并列。inline元素会像文字一样排列在一行里，从左到右排列，一行放不下就换行，并且不能设置width height，宽高由内容决定，垂直margin不生效，水平margin是生效的，只是无法撑开行高。每一行文字，浏览器都会创建一个叫做linebox行盒的东西，行盒的高度由这一行最高的元素决定，行盒的垂直对齐vertical-align只有在ifc中有效，在flex、grid中无效，用align-item替代。",[18,2037,2038],{},"常见的inline元素有span 、a、strong、em、img、button、input。",[18,2040,2041],{},"inline-block是折中的方式，表示对外inline，对内flow-root，能设置inline不能设置的宽高。ifc会合并连续空格、换行。",[96,2043,2046],{"className":2044,"code":2045,"language":392},[390],"\u003Cspan>a\u003C\u002Fspan>\n\u003Cspan>b\u003C\u002Fspan>\n",[24,2047,2045],{"__ignoreMap":101},[18,2049,2050],{},"这样的渲染出来是a b， 中间只有一个空格，因为html源码里的换行被当成一个空格，如果不想要有空隙，可以压缩到一行去，或者用flex布局，flex不处理whitespace，相对来说，flex默认不换行，如果需要换行，就要设置flex-wrap，flex-wrap: wrap。",[18,2052,2053],{},"然后我们来讲脱离文档流的程度，float是半脱离，absolute是完全脱离，float是原来位置不保留，后面的元素会上移，absolute也是，后面的元素会上移。但是float会影响周围文字，比如说文字环绕，但是absolute是不影响的，文字不会环绕，float不积压周围块级元素，但是会被其他块级元素无视，absolute则是完全独立，不积压。所以说float是保留空间影响，absolute是完全去掉影响，完全脱离文档流。",[18,2055,2056,2057,2063],{},"最后，我们以行\u002F列轨道概念结尾，",[21,2058,2059,2062],{},[24,2060,2061],{},"align-content"," 操作的是“行\u002F列轨道”，不是单个元素。"," 在Flex和Grid中，有两个层级，分别是轨道和项目，英文叫做track和item，比如说flex默认一行轨道，当flex-wrap: wrap时候有多行。",[18,2065,2066],{},"flex里有四个对齐属性，一个叫做justify-content，用于所有item整体，作用方向是主轴；一个叫做align-item，用于处理单个item，方向是交叉轴；align-content作用于track，作用方向是交叉轨道；align-self作用于单个item覆盖align-items，方向是交叉轴。",[18,2068,2069],{},"grid中，justify-content处理整个网格，为行轴；align-content处理整个网格，方向为列轴；justify-items作用于每个item在单元格内，行轴；align-items处理每个item在单元格内，方向列轴。",[1908,2071,2072],{"id":2072},"单位",[835,2074,2075,2087],{},[838,2076,2077],{},[841,2078,2079,2081,2084],{},[844,2080,2072],{},[844,2082,2083],{},"参照物",[844,2085,2086],{},"适用",[854,2088,2089,2101,2117,2132,2145,2161,2177,2193],{},[841,2090,2091,2095,2098],{},[859,2092,2093],{},[24,2094,142],{},[859,2096,2097],{},"绝对像素",[859,2099,2100],{},"边框、小间距",[841,2102,2103,2108,2114],{},[859,2104,2105],{},[24,2106,2107],{},"rem",[859,2109,2110,2111],{},"根元素 ",[24,2112,2113],{},"font-size",[859,2115,2116],{},"全局缩放、字号",[841,2118,2119,2124,2129],{},[859,2120,2121],{},[24,2122,2123],{},"em",[859,2125,2126,2127],{},"父元素 ",[24,2128,2113],{},[859,2130,2131],{},"局部相对缩放",[841,2133,2134,2139,2142],{},[859,2135,2136],{},[24,2137,2138],{},"%",[859,2140,2141],{},"父元素对应属性",[859,2143,2144],{},"宽度、高度",[841,2146,2147,2155,2158],{},[859,2148,2149,1885,2152],{},[24,2150,2151],{},"vw",[24,2153,2154],{},"vh",[859,2156,2157],{},"视口宽\u002F高",[859,2159,2160],{},"全屏布局",[841,2162,2163,2171,2174],{},[859,2164,2165,1885,2168],{},[24,2166,2167],{},"vmin",[24,2169,2170],{},"vmax",[859,2172,2173],{},"视口较小\u002F较大边",[859,2175,2176],{},"正方形自适应",[841,2178,2179,2184,2190],{},[859,2180,2181],{},[24,2182,2183],{},"ch",[859,2185,2186,2187,2189],{},"字符 ",[24,2188,1100],{}," 的宽度",[859,2191,2192],{},"输入框宽度",[841,2194,2195,2200,2203],{},[859,2196,2197],{},[24,2198,2199],{},"fr",[859,2201,2202],{},"Grid 剩余空间",[859,2204,2205],{},"Grid 布局",[1908,2207,1247],{"id":1247},[835,2209,2210,2220],{},[838,2211,2212],{},[841,2213,2214,2216,2218],{},[844,2215],{},[844,2217,1176],{},[844,2219,1247],{},[854,2221,2222,2237,2248,2269],{},[841,2223,2224,2227,2232],{},[859,2225,2226],{},"语法",[859,2228,2229,2230],{},"单冒号 ",[24,2231,906],{},[859,2233,2234,2235],{},"双冒号 ",[24,2236,923],{},[841,2238,2239,2242,2245],{},[859,2240,2241],{},"作用",[859,2243,2244],{},"描述元素的状态",[859,2246,2247],{},"创建虚拟元素",[841,2249,2250,2252,2261],{},[859,2251,852],{},[859,2253,2254,31,2256,31,2258],{},[24,2255,906],{},[24,2257,1047],{},[24,2259,2260],{},":nth-child",[859,2262,2263,31,2265,31,2267],{},[24,2264,923],{},[24,2266,1263],{},[24,2268,1275],{},[841,2270,2271,2274,2277],{},[859,2272,2273],{},"是否在 DOM 里",[859,2275,2276],{},"不创建新元素",[859,2278,2247],{},[18,2280,2281,2284,2285,1347,2288,2290],{},[21,2282,2283],{},"面试问","：“",[24,2286,2287],{},":before",[24,2289,923],{}," 有什么区别？”答“单冒号是 CSS2 语法，双冒号是 CSS3 语法，现代写法用双冒号”。",[1908,2292,2294,2297,2298,2297,2301],{"id":2293},"display-none-vs-visibility-hidden-vs-opacity-0",[24,2295,2296],{},"display: none"," vs ",[24,2299,2300],{},"visibility: hidden",[24,2302,2303],{},"opacity: 0",[835,2305,2306,2324],{},[838,2307,2308],{},[841,2309,2310,2312,2315,2318,2321],{},[844,2311],{},[844,2313,2314],{},"占据空间",[844,2316,2317],{},"可交互",[844,2319,2320],{},"触发重排",[844,2322,2323],{},"触发重绘",[854,2325,2326,2342,2356],{},[841,2327,2328,2332,2335,2337,2340],{},[859,2329,2330],{},[24,2331,2296],{},[859,2333,2334],{},"❌",[859,2336,2334],{},[859,2338,2339],{},"✅",[859,2341,2339],{},[841,2343,2344,2348,2350,2352,2354],{},[859,2345,2346],{},[24,2347,2300],{},[859,2349,2339],{},[859,2351,2334],{},[859,2353,2334],{},[859,2355,2339],{},[841,2357,2358,2362,2364,2366,2368],{},[859,2359,2360],{},[24,2361,2303],{},[859,2363,2339],{},[859,2365,2339],{},[859,2367,2334],{},[859,2369,2339],{},[18,2371,2372,2374,2375,2377,2378,2380,2381,2383],{},[21,2373,2283],{},"：“三者区别？”答“",[24,2376,2296],{}," 完全移除，",[24,2379,2300],{}," 保留空间但不可交互，",[24,2382,2303],{}," 保留空间且可交互”。",[1908,2385,2386],{"id":2386},"重排和重绘",[835,2388,2389,2401],{},[838,2390,2391],{},[841,2392,2393,2395,2398],{},[844,2394],{},[844,2396,2397],{},"重排（Reflow）",[844,2399,2400],{},"重绘（Repaint）",[854,2402,2403,2414,2425,2461],{},[841,2404,2405,2408,2411],{},[859,2406,2407],{},"触发",[859,2409,2410],{},"几何属性变化",[859,2412,2413],{},"颜色、背景变化",[841,2415,2416,2419,2422],{},[859,2417,2418],{},"代价",[859,2420,2421],{},"高",[859,2423,2424],{},"低",[841,2426,2427,2430,2450],{},[859,2428,2429],{},"属性",[859,2431,2432,31,2435,31,2438,31,2441,31,2444,31,2447],{},[24,2433,2434],{},"width",[24,2436,2437],{},"height",[24,2439,2440],{},"margin",[24,2442,2443],{},"padding",[24,2445,2446],{},"display",[24,2448,2449],{},"position",[859,2451,2452,31,2455,31,2458],{},[24,2453,2454],{},"color",[24,2456,2457],{},"background-color",[24,2459,2460],{},"visibility",[841,2462,2463,2466,2475],{},[859,2464,2465],{},"优化",[859,2467,1717,2468,31,2471,2474],{},[24,2469,2470],{},"transform",[24,2472,2473],{},"opacity","（只触发合成）",[859,2476,2477],{},"—",[18,2479,2480,2482,2483,1885,2485,2487,2488,2491],{},[21,2481,2283],{},"：“怎么优化动画？”答“用 ",[24,2484,2470],{},[24,2486,2473],{},"，加 ",[24,2489,2490],{},"will-change","，避免触发重排”。",[1908,2493,2494],{"id":2494},"滚动相关",[835,2496,2497,2505],{},[838,2498,2499],{},[841,2500,2501,2503],{},[844,2502,2429],{},[844,2504,2241],{},[854,2506,2507,2517,2527,2537,2547,2557,2567],{},[841,2508,2509,2514],{},[859,2510,2511],{},[24,2512,2513],{},"overflow",[859,2515,2516],{},"控制溢出",[841,2518,2519,2524],{},[859,2520,2521],{},[24,2522,2523],{},"overscroll-behavior",[859,2525,2526],{},"控制滚动边界（防止滚动穿透）",[841,2528,2529,2534],{},[859,2530,2531],{},[24,2532,2533],{},"scroll-snap-type",[859,2535,2536],{},"滚动吸附",[841,2538,2539,2544],{},[859,2540,2541],{},[24,2542,2543],{},"scroll-behavior: smooth",[859,2545,2546],{},"平滑滚动",[841,2548,2549,2554],{},[859,2550,2551],{},[24,2552,2553],{},"position: sticky",[859,2555,2556],{},"粘性定位",[841,2558,2559,2564],{},[859,2560,2561],{},[24,2562,2563],{},"scrollbar-gutter",[859,2565,2566],{},"预留滚动条空间，避免布局抖动",[841,2568,2569,2581],{},[859,2570,2571,2573,2574,2576,2577,2580],{},[21,2572,2283],{},"：“怎么做滚动吸附？”答“",[24,2575,2533],{}," + ",[24,2578,2579],{},"scroll-snap-align","”。没有滚动吸附时，用户滑动轮播图，可能停在两张图中间，一半露一张、一半露另一张，视觉上很别扭。",[859,2582],{},[18,2584,2585],{},"怎么处理移动端上，1px变成2px的问题，根源是因为设备像素比DPR，高DPR屏幕上，1CSS像素可能对应着2、3个甚至更多的物理像素，导致1px的边框在物理上渲染成了2px或者3px宽。问题根源：",[96,2587,2589],{"className":98,"code":2588,"language":100,"meta":101,"style":101},"border: 1px solid #ccc;\n",[24,2590,2591],{"__ignoreMap":101},[105,2592,2593],{"class":107,"line":108},[105,2594,2588],{"class":115},[18,2596,2597,2598,2601],{},"DPR = 1 : 1个CSS像素=1个物理像素\n",[21,2599,2600],{},"DPR = 2","：1 个 CSS 像素 = 2×2 个物理像素，1px 边框在物理上占 2 个像素\n这个时候可以试试伪元素绘制边框，再缩放：",[96,2603,2605],{"className":98,"code":2604,"language":100,"meta":101,"style":101},".border-1px {\n  position: relative;\n}\n\n.border-1px::after {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 200%;\n  height: 200%;\n  border: 1px solid #ccc;\n  transform: scale(0.5);\n  transform-origin: 0 0;\n  pointer-events: none;\n}\n",[24,2606,2607,2614,2625,2629,2633,2640,2652,2662,2673,2684,2698,2711,2727,2744,2758,2769],{"__ignoreMap":101},[105,2608,2609,2612],{"class":107,"line":108},[105,2610,2611],{"class":241},".border-1px",[105,2613,116],{"class":115},[105,2615,2616,2619,2621,2623],{"class":107,"line":119},[105,2617,2618],{"class":129},"  position",[105,2620,126],{"class":115},[105,2622,1884],{"class":129},[105,2624,145],{"class":115},[105,2626,2627],{"class":107,"line":133},[105,2628,212],{"class":115},[105,2630,2631],{"class":107,"line":148},[105,2632,307],{"emptyLinePlaceholder":306},[105,2634,2635,2638],{"class":107,"line":163},[105,2636,2637],{"class":241},".border-1px::after",[105,2639,116],{"class":115},[105,2641,2642,2645,2647,2650],{"class":107,"line":179},[105,2643,2644],{"class":129},"  content",[105,2646,126],{"class":115},[105,2648,2649],{"class":248},"''",[105,2651,145],{"class":115},[105,2653,2654,2656,2658,2660],{"class":107,"line":194},[105,2655,2618],{"class":129},[105,2657,126],{"class":115},[105,2659,1888],{"class":129},[105,2661,145],{"class":115},[105,2663,2664,2667,2669,2671],{"class":107,"line":209},[105,2665,2666],{"class":129},"  top",[105,2668,126],{"class":115},[105,2670,1100],{"class":129},[105,2672,145],{"class":115},[105,2674,2675,2678,2680,2682],{"class":107,"line":526},[105,2676,2677],{"class":129},"  left",[105,2679,126],{"class":115},[105,2681,1100],{"class":129},[105,2683,145],{"class":115},[105,2685,2686,2689,2691,2694,2696],{"class":107,"line":531},[105,2687,2688],{"class":129},"  width",[105,2690,126],{"class":115},[105,2692,2693],{"class":129},"200",[105,2695,2138],{"class":111},[105,2697,145],{"class":115},[105,2699,2700,2703,2705,2707,2709],{"class":107,"line":668},[105,2701,2702],{"class":129},"  height",[105,2704,126],{"class":115},[105,2706,2693],{"class":129},[105,2708,2138],{"class":111},[105,2710,145],{"class":115},[105,2712,2713,2715,2717,2719,2721,2723,2725],{"class":107,"line":673},[105,2714,1005],{"class":129},[105,2716,126],{"class":115},[105,2718,861],{"class":129},[105,2720,142],{"class":111},[105,2722,1014],{"class":129},[105,2724,1017],{"class":129},[105,2726,145],{"class":115},[105,2728,2729,2732,2734,2737,2739,2742],{"class":107,"line":679},[105,2730,2731],{"class":129},"  transform",[105,2733,126],{"class":115},[105,2735,2736],{"class":129},"scale",[105,2738,321],{"class":115},[105,2740,2741],{"class":129},"0.5",[105,2743,327],{"class":115},[105,2745,2746,2749,2751,2753,2756],{"class":107,"line":689},[105,2747,2748],{"class":129},"  transform-origin",[105,2750,126],{"class":115},[105,2752,1100],{"class":129},[105,2754,2755],{"class":129}," 0",[105,2757,145],{"class":115},[105,2759,2760,2763,2765,2767],{"class":107,"line":699},[105,2761,2762],{"class":129},"  pointer-events",[105,2764,126],{"class":115},[105,2766,1144],{"class":129},[105,2768,145],{"class":115},[105,2770,2771],{"class":107,"line":704},[105,2772,212],{"class":115},[10,2774,2776],{"id":2775},"pinia","Pinia",[18,2778,2779],{},"pinia的核心原理是使用vue的核心响应式系统包装一个状态对象，用闭包管理store实例，这个和composable的思想是一致的，用插件机制扩展能力。",[18,2781,2782,2785,2786,2792],{},[24,2783,2784],{},"defineStore(id, options)"," 返回一个 ",[21,2787,2788,2791],{},[24,2789,2790],{},"useStore"," 函数","，不是 store 本身。",[96,2794,2796],{"className":559,"code":2795,"language":561,"meta":101,"style":101},"const useCounter = defineStore('counter', {\n  state: () => ({ count: 0 }),\n  getters: {\n    double: (state) => state.count * 2,\n  },\n  actions: {\n    increment() { this.count++ },\n  },\n})\n",[24,2797,2798,2818,2836,2841,2868,2873,2878,2898,2902],{"__ignoreMap":101},[105,2799,2800,2802,2805,2807,2810,2812,2815],{"class":107,"line":108},[105,2801,568],{"class":111},[105,2803,2804],{"class":129}," useCounter",[105,2806,574],{"class":111},[105,2808,2809],{"class":241}," defineStore",[105,2811,321],{"class":115},[105,2813,2814],{"class":248},"'counter'",[105,2816,2817],{"class":115},", {\n",[105,2819,2820,2823,2826,2828,2831,2833],{"class":107,"line":119},[105,2821,2822],{"class":241},"  state",[105,2824,2825],{"class":115},": () ",[105,2827,723],{"class":111},[105,2829,2830],{"class":115}," ({ count: ",[105,2832,1100],{"class":129},[105,2834,2835],{"class":115}," }),\n",[105,2837,2838],{"class":107,"line":133},[105,2839,2840],{"class":115},"  getters: {\n",[105,2842,2843,2846,2849,2852,2855,2857,2860,2862,2865],{"class":107,"line":148},[105,2844,2845],{"class":241},"    double",[105,2847,2848],{"class":115},": (",[105,2850,2851],{"class":122},"state",[105,2853,2854],{"class":115},") ",[105,2856,723],{"class":111},[105,2858,2859],{"class":115}," state.count ",[105,2861,938],{"class":111},[105,2863,2864],{"class":129}," 2",[105,2866,2867],{"class":115},",\n",[105,2869,2870],{"class":107,"line":163},[105,2871,2872],{"class":115},"  },\n",[105,2874,2875],{"class":107,"line":179},[105,2876,2877],{"class":115},"  actions: {\n",[105,2879,2880,2883,2886,2889,2892,2895],{"class":107,"line":194},[105,2881,2882],{"class":241},"    increment",[105,2884,2885],{"class":115},"() { ",[105,2887,2888],{"class":129},"this",[105,2890,2891],{"class":115},".count",[105,2893,2894],{"class":111},"++",[105,2896,2897],{"class":115}," },\n",[105,2899,2900],{"class":107,"line":209},[105,2901,2872],{"class":115},[105,2903,2904],{"class":107,"line":526},[105,2905,2906],{"class":115},"})\n",[18,2908,2909,2912,2913,2916],{},[24,2910,2911],{},"useStore()"," 内部做的事，会检查pinia实例上有没有这个id的store，有的话就返回，没有就创建，它会把state变成reactive，把getters变成computed，把actions挂载到store上，然后存进一个叫做",[24,2914,2915],{},"pinia._s"," 的Map，key是store id。所以store是单例的，同一个pinia实例里，useCounter()多次调用都只返回同一个对象。",[18,2918,2919,2921,2922,2928],{},[24,2920,2851],{}," 是一个 ",[21,2923,2924,2927],{},[24,2925,2926],{},"reactive"," 对象","。所有对 state 的读写都经过 Vue 的 Proxy，触发依赖收集和更新，注意这里state是一个Function，而非一个obj，因为store可能被多个组件实例创建，如果state是对象，那么多个store会共享同一个引用，函数每次调用返回新对象，保证每个store都有独立的状态。",[14,2930,2932],{"id":2931},"ssr隔离","SSR隔离",[18,2934,2935],{},"前面我们提到每个请求都有独立的pinia实例，这个是ssr安全的关键：",[96,2937,2940],{"className":2938,"code":2939,"language":392},[390],"\u002F\u002F 服务端每次请求\nconst pinia = createPinia()\napp.use(pinia)\n\n\u002F\u002F 客户端激活时\nconst pinia = createPinia()\napp.use(pinia)\n",[24,2941,2939],{"__ignoreMap":101},[18,2943,2944,2945,2948,2949,2951,2952,2957,2958,2960],{},"在nuxt中，每个请求都会创建新的pinia实例，客户端从payload中恢复store状态。Nuxt 渲染页面时，如果组件在 ",[24,2946,2947],{},"setup"," 里调用了 ",[24,2950,2911],{},"，",[21,2953,2954,2955],{},"服务端会执行这个 ",[24,2956,2947],{},"，于是 ",[24,2959,2911],{}," 也会在服务端执行，store 实例就被创建了。",[96,2962,2966],{"className":2963,"code":2964,"language":2965,"meta":101,"style":101},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nconst userStore = useUserStore()  \u002F\u002F 服务端和客户端都会执行\n\u003C\u002Fscript>\n","vue",[24,2967,2968,2988,3006],{"__ignoreMap":101},[105,2969,2970,2972,2975,2978,2981,2983,2986],{"class":107,"line":108},[105,2971,234],{"class":115},[105,2973,2974],{"class":237},"script",[105,2976,2977],{"class":241}," setup",[105,2979,2980],{"class":241}," lang",[105,2982,245],{"class":115},[105,2984,2985],{"class":248},"\"ts\"",[105,2987,257],{"class":115},[105,2989,2990,2992,2995,2997,3000,3003],{"class":107,"line":119},[105,2991,568],{"class":111},[105,2993,2994],{"class":129}," userStore",[105,2996,574],{"class":111},[105,2998,2999],{"class":241}," useUserStore",[105,3001,3002],{"class":115},"()  ",[105,3004,3005],{"class":630},"\u002F\u002F 服务端和客户端都会执行\n",[105,3007,3008,3011,3013],{"class":107,"line":133},[105,3009,3010],{"class":115},"\u003C\u002F",[105,3012,2974],{"class":237},[105,3014,257],{"class":115},[18,3016,3017,3018,3021,3022,3024,3025,3027],{},"如果store里面有state初始化，getter计算，甚至action里的异步请求，这这些都会在服务端里跑一次。",[24,3019,3020],{},"@pinia\u002Fnuxt"," 模块会自动处理，让每个 SSR 请求有独立的 ",[24,3023,2775],{}," 实例。",[24,3026,2915],{}," 是请求级的 Map，不同请求的 store 不会互相污染。",[10,3029,3030],{"id":227},"HTML",[14,3032,3034],{"id":3033},"src-和-href-的区别","Src 和 href 的区别",[18,3036,3037],{},"src是替换当前元素，href是建立关联，src会把外部资源嵌入到当前的元素的位置，替换掉这个元素，它的加载行为：浏览器会阻塞解析，等资源下载完再继续，因为资源是当前文档的一部分，必须等到位。",[96,3039,3041],{"className":225,"code":3040,"language":227,"meta":101,"style":101},"\u003Cimg src=\"a.jpg\">\n\u003Cscript src=\"a.js\">\u003C\u002Fscript>\n\u003Ciframe src=\"page.html\">\u003C\u002Fiframe>\n\u003Cvideo src=\"a.mp4\">\u003C\u002Fvideo>\n\u003Caudio src=\"a.mp3\">\u003C\u002Faudio>\n",[24,3042,3043,3059,3078,3098,3118],{"__ignoreMap":101},[105,3044,3045,3047,3049,3052,3054,3057],{"class":107,"line":108},[105,3046,234],{"class":115},[105,3048,371],{"class":237},[105,3050,3051],{"class":241}," src",[105,3053,245],{"class":115},[105,3055,3056],{"class":248},"\"a.jpg\"",[105,3058,257],{"class":115},[105,3060,3061,3063,3065,3067,3069,3072,3074,3076],{"class":107,"line":119},[105,3062,234],{"class":115},[105,3064,2974],{"class":237},[105,3066,3051],{"class":241},[105,3068,245],{"class":115},[105,3070,3071],{"class":248},"\"a.js\"",[105,3073,252],{"class":115},[105,3075,2974],{"class":237},[105,3077,257],{"class":115},[105,3079,3080,3082,3085,3087,3089,3092,3094,3096],{"class":107,"line":133},[105,3081,234],{"class":115},[105,3083,3084],{"class":237},"iframe",[105,3086,3051],{"class":241},[105,3088,245],{"class":115},[105,3090,3091],{"class":248},"\"page.html\"",[105,3093,252],{"class":115},[105,3095,3084],{"class":237},[105,3097,257],{"class":115},[105,3099,3100,3102,3105,3107,3109,3112,3114,3116],{"class":107,"line":148},[105,3101,234],{"class":115},[105,3103,3104],{"class":237},"video",[105,3106,3051],{"class":241},[105,3108,245],{"class":115},[105,3110,3111],{"class":248},"\"a.mp4\"",[105,3113,252],{"class":115},[105,3115,3104],{"class":237},[105,3117,257],{"class":115},[105,3119,3120,3122,3125,3127,3129,3132,3134,3136],{"class":107,"line":163},[105,3121,234],{"class":115},[105,3123,3124],{"class":237},"audio",[105,3126,3051],{"class":241},[105,3128,245],{"class":115},[105,3130,3131],{"class":248},"\"a.mp3\"",[105,3133,252],{"class":115},[105,3135,3124],{"class":237},[105,3137,257],{"class":115},[18,3139,3140,3141,3144],{},"href作用是",[21,3142,3143],{},"建立当前文档和尾部资源的关联","，不会替换元素本身，加载行为：浏览器不阻塞解析，并行下载，资源是关联，非当前文档一部分。",[14,3146,3147],{"id":3147},"iframe标签的优缺点",[18,3149,3150],{},"这个标签作用是创建一块独立的文档区域，有自己的DOM、js上下文（不同全局对象），css作用域，cookie和存储。",[18,3152,3153],{},"父页面的css不会污染iframe内容，反之亦然，这在嵌入第三方内容时非常关键。",[96,3155,3158],{"className":3156,"code":3157,"language":392},[390],"\u003C!-- 第三方广告、地图、视频播放器 -->\n\u003Ciframe src=\"https:\u002F\u002Fmaps.example.com\">\u003C\u002Fiframe>\n",[24,3159,3157],{"__ignoreMap":101},[18,3161,3162],{},"跨域部分，想在页面中显示另一个域的内容，iframe就是唯一的直接支持，其他方案会受到CORS限制，而且脚本不会执行。",[18,3164,3165],{},"sandbox属性可以精确控制iframe能力：",[96,3167,3170],{"className":3168,"code":3169,"language":392},[390],"\u003Ciframe\n  src=\"https:\u002F\u002Funtrusted.com\"\n  sandbox=\"allow-scripts\"\n>\u003C\u002Fiframe>\n",[24,3171,3169],{"__ignoreMap":101},[835,3173,3174,3187],{},[838,3175,3176],{},[841,3177,3178,3184],{},[844,3179,3180,3183],{},[24,3181,3182],{},"sandbox"," 值",[844,3185,3186],{},"允许的能力",[854,3188,3189,3199,3209,3223,3233],{},[841,3190,3191,3196],{},[859,3192,3193],{},[24,3194,3195],{},"allow-scripts",[859,3197,3198],{},"执行 JS",[841,3200,3201,3206],{},[859,3202,3203],{},[24,3204,3205],{},"allow-forms",[859,3207,3208],{},"提交表单",[841,3210,3211,3216],{},[859,3212,3213],{},[24,3214,3215],{},"allow-same-origin",[859,3217,3218,3219,3222],{},"保持同源（",[21,3220,3221],{},"慎用","）",[841,3224,3225,3230],{},[859,3226,3227],{},[24,3228,3229],{},"allow-popups",[859,3231,3232],{},"打开弹窗",[841,3234,3235,3240],{},[859,3236,3237],{},[24,3238,3239],{},"allow-top-navigation",[859,3241,3242],{},"跳转顶层页面",[18,3244,3245,3246,3249],{},"iframe的加载和主页面解耦，不会拖慢主页面渲染，如果iframe脚本崩溃，不会影响父页面。但是每一个iframe是一个独立的浏览上下文，是独立的js引擎实例，独立的dom树，独立事件循环，内存开销大，多个iframe会显著增加内存占用。搜索引擎",[21,3247,3248],{},"通常不索引 iframe 内容","。如果关键内容在 iframe 里，SEO 基本失效。",[18,3251,3252,3253,3256],{},"父页面和 iframe 通信只能通过 ",[24,3254,3255],{},"postMessage"," ，这里一定要和BroadcastChannel分开，这是两个不同技术。",[96,3258,3262],{"className":3259,"code":3260,"language":3261,"meta":101,"style":101},"language-ts shiki shiki-themes github-light github-dark","\u002F\u002F 父页面发消息\niframe.contentWindow.postMessage('hello', 'https:\u002F\u002Fchild.com')\n\n\u002F\u002F 子页面接收\nwindow.addEventListener('message', (e) => {\n  if (e.origin !== 'https:\u002F\u002Fparent.com') return  \u002F\u002F 必须校验 origin\n  console.log(e.data)\n})\n","ts",[24,3263,3264,3269,3288,3292,3297,3322,3344,3354],{"__ignoreMap":101},[105,3265,3266],{"class":107,"line":108},[105,3267,3268],{"class":630},"\u002F\u002F 父页面发消息\n",[105,3270,3271,3274,3276,3278,3281,3283,3286],{"class":107,"line":119},[105,3272,3273],{"class":115},"iframe.contentWindow.",[105,3275,3255],{"class":241},[105,3277,321],{"class":115},[105,3279,3280],{"class":248},"'hello'",[105,3282,286],{"class":115},[105,3284,3285],{"class":248},"'https:\u002F\u002Fchild.com'",[105,3287,1529],{"class":115},[105,3289,3290],{"class":107,"line":133},[105,3291,307],{"emptyLinePlaceholder":306},[105,3293,3294],{"class":107,"line":148},[105,3295,3296],{"class":630},"\u002F\u002F 子页面接收\n",[105,3298,3299,3302,3305,3307,3310,3313,3316,3318,3320],{"class":107,"line":163},[105,3300,3301],{"class":115},"window.",[105,3303,3304],{"class":241},"addEventListener",[105,3306,321],{"class":115},[105,3308,3309],{"class":248},"'message'",[105,3311,3312],{"class":115},", (",[105,3314,3315],{"class":122},"e",[105,3317,2854],{"class":115},[105,3319,723],{"class":111},[105,3321,116],{"class":115},[105,3323,3324,3327,3330,3333,3336,3338,3341],{"class":107,"line":179},[105,3325,3326],{"class":111},"  if",[105,3328,3329],{"class":115}," (e.origin ",[105,3331,3332],{"class":111},"!==",[105,3334,3335],{"class":248}," 'https:\u002F\u002Fparent.com'",[105,3337,2854],{"class":115},[105,3339,3340],{"class":111},"return",[105,3342,3343],{"class":630},"  \u002F\u002F 必须校验 origin\n",[105,3345,3346,3349,3351],{"class":107,"line":194},[105,3347,3348],{"class":115},"  console.",[105,3350,639],{"class":241},[105,3352,3353],{"class":115},"(e.data)\n",[105,3355,3356],{"class":107,"line":209},[105,3357,2906],{"class":115},[14,3359,3361],{"id":3360},"canvas和svg","Canvas和SVG",[18,3363,3364],{},"canvas是位图，svg是矢量图，canvas是立即模式渲染，逐像素绘制，svg是保留模式绘制，DOM节点描述。canvas本质是像素数组，svg本质是dom树。",[18,3366,3367],{},"稍微触底来看看，canvas像素数据在js层面使用的是uint8ClampedArray，不同于wasm里的uint8heap，都是字节数组，但是约束和用途不同。",[18,3369,3370],{},"使用getImageData()获取canvas像素的时候，返回的是ImageData对象，里面有一个data属性，类型就是Uint8ClampedArray。",[96,3372,3374],{"className":559,"code":3373,"language":561,"meta":101,"style":101},"const imageData = ctx.getImageData(0, 0, width, height);\n\u002F\u002F imageData.data 是 Uint8ClampedArray\n\u002F\u002F 顺序是 RGBA：R, G, B, A, R, G, B, A...\n",[24,3375,3376,3402,3407],{"__ignoreMap":101},[105,3377,3378,3380,3383,3385,3388,3391,3393,3395,3397,3399],{"class":107,"line":108},[105,3379,568],{"class":111},[105,3381,3382],{"class":129}," imageData",[105,3384,574],{"class":111},[105,3386,3387],{"class":115}," ctx.",[105,3389,3390],{"class":241},"getImageData",[105,3392,321],{"class":115},[105,3394,1100],{"class":129},[105,3396,286],{"class":115},[105,3398,1100],{"class":129},[105,3400,3401],{"class":115},", width, height);\n",[105,3403,3404],{"class":107,"line":119},[105,3405,3406],{"class":630},"\u002F\u002F imageData.data 是 Uint8ClampedArray\n",[105,3408,3409],{"class":107,"line":133},[105,3410,3411],{"class":630},"\u002F\u002F 顺序是 RGBA：R, G, B, A, R, G, B, A...\n",[18,3413,3414,3415,3418,3419,3422,3423,3426,3427,3429,3430,3433,3434,3437,3438,3440,3441,3444,3445,3447,3448,91],{},"这个数组的每个元素是一个 ",[21,3416,3417],{},"0 到 255 之间的整数","，每 4 个一组（红、绿、蓝、透明度），",[21,3420,3421],{},"“Clamped”就是“钳制”的意思。"," 如果你给 ",[24,3424,3425],{},"Uint8ClampedArray"," 的元素赋值 ",[24,3428,608],{},"，它会被自动截断为 ",[24,3431,3432],{},"255","；赋值 ",[24,3435,3436],{},"-10","，会变成 ",[24,3439,1100],{},"。而 ",[24,3442,3443],{},"Uint8Array"," 会做模运算，",[24,3446,608],{}," 会变成 ",[24,3449,3450],{},"44",[18,3452,3453],{},"而wasm里面的线性内存WebAssembly.Memory中，在js侧是通过ArrayBuffer暴露的，我们可以用不同的TypedArray视图去查看：",[96,3455,3457],{"className":559,"code":3456,"language":561,"meta":101,"style":101},"\u002F\u002F WebAssembly 的内存\nconst waMemory = waInstance.exports.memory;\n\n\u002F\u002F 用 Uint8Array 视图看这块内存\nconst u8Heap = new Uint8Array(waMemory.buffer);\n",[24,3458,3459,3464,3476,3480,3485],{"__ignoreMap":101},[105,3460,3461],{"class":107,"line":108},[105,3462,3463],{"class":630},"\u002F\u002F WebAssembly 的内存\n",[105,3465,3466,3468,3471,3473],{"class":107,"line":119},[105,3467,568],{"class":111},[105,3469,3470],{"class":129}," waMemory",[105,3472,574],{"class":111},[105,3474,3475],{"class":115}," waInstance.exports.memory;\n",[105,3477,3478],{"class":107,"line":133},[105,3479,307],{"emptyLinePlaceholder":306},[105,3481,3482],{"class":107,"line":148},[105,3483,3484],{"class":630},"\u002F\u002F 用 Uint8Array 视图看这块内存\n",[105,3486,3487,3489,3492,3494,3497,3500],{"class":107,"line":163},[105,3488,568],{"class":111},[105,3490,3491],{"class":129}," u8Heap",[105,3493,574],{"class":111},[105,3495,3496],{"class":111}," new",[105,3498,3499],{"class":241}," Uint8Array",[105,3501,3502],{"class":115},"(waMemory.buffer);\n",[14,3504,3506],{"id":3505},"typescript的dom类型定义","TypeScript的DOM类型定义",[18,3508,3509],{},"ts中的dom类型定义里，Element继承链是EventTarget-> Node-> element，",[96,3511,3513],{"className":3259,"code":3512,"language":3261,"meta":101,"style":101},"interface EventTarget { }\ninterface Node extends EventTarget { }\ninterface Element extends Node { }\n",[24,3514,3515,3526,3540],{"__ignoreMap":101},[105,3516,3517,3520,3523],{"class":107,"line":108},[105,3518,3519],{"class":111},"interface",[105,3521,3522],{"class":241}," EventTarget",[105,3524,3525],{"class":115}," { }\n",[105,3527,3528,3530,3533,3536,3538],{"class":107,"line":119},[105,3529,3519],{"class":111},[105,3531,3532],{"class":241}," Node",[105,3534,3535],{"class":111}," extends",[105,3537,3522],{"class":241},[105,3539,3525],{"class":115},[105,3541,3542,3544,3547,3549,3551],{"class":107,"line":133},[105,3543,3519],{"class":111},[105,3545,3546],{"class":241}," Element",[105,3548,3535],{"class":111},[105,3550,3532],{"class":241},[105,3552,3525],{"class":115},[18,3554,3555],{},"再往下的话，就是具体元素HTMLElement、SVGElement、MathElement 都是继承自Element了。",[96,3557,3560],{"className":3558,"code":3559,"language":392},[390],"EventTarget\n  └── Node\n        └── Element\n              ├── HTMLElement\n              │     ├── HTMLDivElement\n              │     ├── HTMLInputElement\n              │     └── ...\n              ├── SVGElement\n              └── MathMLElement\n",[24,3561,3559],{"__ignoreMap":101},[18,3563,3564],{},"然后接下来可以看看每一层都提供了什么，这也是DOM的原型设计，记了有很多好处，如果后续想要自己开发跨平台组件的话，也可以参考看看。",[18,3566,3567,3568,31,3570,31,3573,3576],{},"EventTarget提供了 ",[24,3569,3304],{},[24,3571,3572],{},"removeEventListener",[24,3574,3575],{},"dispatchEvent"," ，让DOM组件有了事件能力。",[18,3578,3579,3580,31,3583,31,3586,31,3589,31,3592,31,3595,3598],{},"Node提供了 ",[24,3581,3582],{},"parentNode",[24,3584,3585],{},"childNodes",[24,3587,3588],{},"appendChild",[24,3590,3591],{},"removeChild",[24,3593,3594],{},"nodeType",[24,3596,3597],{},"textContent","，可以让我们随意增删节点和设置文本内容。",[18,3600,3601,3602,31,3605,31,3608,31,3611,31,3614,31,3616,31,3619,3622],{},"Element提供了",[24,3603,3604],{},"id",[24,3606,3607],{},"className",[24,3609,3610],{},"tagName",[24,3612,3613],{},"attributes",[24,3615,1521],{},[24,3617,3618],{},"getAttribute",[24,3620,3621],{},"innerHTML","，让我们可以在DOM组件上设置标识、查询后代、读写属性。",[18,3624,3625,3630,3631,31,3634,31,3637,31,3640,31,3643,31,3646,3649],{},[21,3626,3627],{},[24,3628,3629],{},"HTMLElement"," 提供了 ",[24,3632,3633],{},"style",[24,3635,3636],{},"dataset",[24,3638,3639],{},"offsetWidth",[24,3641,3642],{},"click()",[24,3644,3645],{},"focus()",[24,3647,3648],{},"hidden","，让DOM组件可以控制样式，读写data属性，测量尺寸，触发交互、控制可见性。",[14,3651,3652],{"id":3652},"虚拟列表",[18,3654,3655,3656,91],{},"这是一种前端优化的性能技术，用于高效渲染超长列表，和WPF的虚拟滚动是一种意思，",[21,3657,3658],{},"只渲染视口内可见的列表项，而不是一次性渲染全部数据",[18,3660,3661],{},"在遇到超多DOM的情况下可以做到小占用，滚动流程。只不过虚拟列表可能更常用，因为在前端UI组件中更加常用，组件叫做VirtualList。虚拟滚动在技术文章和浏览器API里讨论常见，描述滚动时动态更新内容这个行为。",[18,3663,3664],{},"我们需要保留视口内的容器，让新的数据替换掉，重新渲染。",[96,3666,3668],{"className":2963,"code":3667,"language":2965,"meta":101,"style":101},"\u003Ctemplate>\n  \u003Cdiv\n    ref=\"viewportRef\"\n    class=\"virtual-list\"\n    @scroll.passive=\"onScroll\"\n  >\n    \u003C!-- 占位层：撑起总高度，让滚动条正确 -->\n    \u003Cdiv class=\"phantom\" :style=\"{ height: totalHeight + 'px' }\">\u003C\u002Fdiv>\n\n    \u003C!-- 渲染层：只放可见项 -->\n    \u003Cdiv class=\"render-layer\" :style=\"{ transform: `translateY(${offsetY}px)` }\">\n      \u003Cdiv\n        v-for=\"item in visibleItems\"\n        :key=\"item.id\"\n        class=\"list-item\"\n        :style=\"{ height: itemHeight + 'px' }\"\n      >\n        {{ item.name }}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref, computed, onMounted, onBeforeUnmount } from 'vue';\n\nconst props = defineProps({\n  list: { type: Array, default: () => [] },\n  itemHeight: { type: Number, default: 50 },\n  buffer: { type: Number, default: 3 } \u002F\u002F 上下缓冲区，防止快速滚动白屏\n});\n\nconst viewportRef = ref(null);\nconst scrollTop = ref(0);\nconst viewportHeight = ref(0);\n\n\u002F\u002F 总高度：撑起滚动条\nconst totalHeight = computed(() => props.list.length * props.itemHeight);\n\n\u002F\u002F 可见起始索引（含缓冲区）\nconst startIndex = computed(() => {\n  const raw = Math.floor(scrollTop.value \u002F props.itemHeight);\n  return Math.max(0, raw - props.buffer);\n});\n\n\u002F\u002F 可见结束索引（含缓冲区）\nconst endIndex = computed(() => {\n  const raw = Math.ceil((scrollTop.value + viewportHeight.value) \u002F props.itemHeight);\n  return Math.min(props.list.length - 1, raw + props.buffer);\n});\n\n\u002F\u002F 当前可见的数据切片\nconst visibleItems = computed(() =>\n  props.list.slice(startIndex.value, endIndex.value + 1)\n);\n\n\u002F\u002F 渲染层的偏移量\nconst offsetY = computed(() => startIndex.value * props.itemHeight);\n\n\u002F\u002F 滚动处理（用 rAF 节流）\nlet ticking = false;\nconst onScroll = (e) => {\n  if (ticking) return;\n  ticking = true;\n  requestAnimationFrame(() => {\n    scrollTop.value = e.target.scrollTop;\n    ticking = false;\n  });\n};\n\n\u002F\u002F 初始化视口高度\nconst updateViewportHeight = () => {\n  if (viewportRef.value) {\n    viewportHeight.value = viewportRef.value.clientHeight;\n  }\n};\n\nonMounted(() => {\n  updateViewportHeight();\n  window.addEventListener('resize', updateViewportHeight);\n});\n\nonBeforeUnmount(() => {\n  window.removeEventListener('resize', updateViewportHeight);\n});\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.virtual-list {\n  height: 500px;          \u002F* 固定视口高度 *\u002F\n  overflow-y: auto;\n  position: relative;\n  border: 1px solid #ddd;\n}\n\n.phantom {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: -1;            \u002F* 不遮挡内容，只撑高度 *\u002F\n}\n\n.render-layer {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  will-change: transform; \u002F* 提示浏览器优化 *\u002F\n}\n\n.list-item {\n  box-sizing: border-box;\n  border-bottom: 1px solid #eee;\n  display: flex;\n  align-items: center;\n  padding: 0 12px;\n}\n\u003C\u002Fstyle>\n",[24,3669,3670,3679,3687,3697,3707,3717,3722,3727,3756,3760,3765,3787,3794,3804,3814,3824,3834,3839,3844,3853,3863,3873,3882,3887,3898,3915,3920,3936,3952,3963,3977,3983,3988,4008,4026,4044,4049,4055,4085,4090,4096,4114,4139,4163,4168,4173,4179,4197,4223,4250,4255,4260,4266,4283,4301,4306,4311,4317,4340,4345,4351,4367,4388,4400,4413,4425,4436,4448,4454,4460,4465,4471,4487,4495,4506,4511,4516,4521,4533,4541,4557,4562,4567,4579,4592,4597,4606,4611,4623,4631,4649,4661,4672,4690,4695,4700,4708,4719,4730,4741,4753,4769,4774,4779,4787,4798,4809,4820,4831,4843,4848,4853,4861,4874,4893,4905,4918,4934,4939],{"__ignoreMap":101},[105,3671,3672,3674,3677],{"class":107,"line":108},[105,3673,234],{"class":115},[105,3675,3676],{"class":237},"template",[105,3678,257],{"class":115},[105,3680,3681,3684],{"class":107,"line":119},[105,3682,3683],{"class":115},"  \u003C",[105,3685,3686],{"class":237},"div\n",[105,3688,3689,3692,3694],{"class":107,"line":133},[105,3690,3691],{"class":241},"    ref",[105,3693,245],{"class":115},[105,3695,3696],{"class":248},"\"viewportRef\"\n",[105,3698,3699,3702,3704],{"class":107,"line":148},[105,3700,3701],{"class":241},"    class",[105,3703,245],{"class":115},[105,3705,3706],{"class":248},"\"virtual-list\"\n",[105,3708,3709,3712,3714],{"class":107,"line":163},[105,3710,3711],{"class":241},"    @scroll.passive",[105,3713,245],{"class":115},[105,3715,3716],{"class":248},"\"onScroll\"\n",[105,3718,3719],{"class":107,"line":179},[105,3720,3721],{"class":115},"  >\n",[105,3723,3724],{"class":107,"line":194},[105,3725,3726],{"class":630},"    \u003C!-- 占位层：撑起总高度，让滚动条正确 -->\n",[105,3728,3729,3732,3734,3737,3739,3742,3745,3747,3750,3752,3754],{"class":107,"line":209},[105,3730,3731],{"class":115},"    \u003C",[105,3733,238],{"class":237},[105,3735,3736],{"class":241}," class",[105,3738,245],{"class":115},[105,3740,3741],{"class":248},"\"phantom\"",[105,3743,3744],{"class":241}," :style",[105,3746,245],{"class":115},[105,3748,3749],{"class":248},"\"{ height: totalHeight + 'px' }\"",[105,3751,252],{"class":115},[105,3753,238],{"class":237},[105,3755,257],{"class":115},[105,3757,3758],{"class":107,"line":526},[105,3759,307],{"emptyLinePlaceholder":306},[105,3761,3762],{"class":107,"line":531},[105,3763,3764],{"class":630},"    \u003C!-- 渲染层：只放可见项 -->\n",[105,3766,3767,3769,3771,3773,3775,3778,3780,3782,3785],{"class":107,"line":668},[105,3768,3731],{"class":115},[105,3770,238],{"class":237},[105,3772,3736],{"class":241},[105,3774,245],{"class":115},[105,3776,3777],{"class":248},"\"render-layer\"",[105,3779,3744],{"class":241},[105,3781,245],{"class":115},[105,3783,3784],{"class":248},"\"{ transform: `translateY(${offsetY}px)` }\"",[105,3786,257],{"class":115},[105,3788,3789,3792],{"class":107,"line":673},[105,3790,3791],{"class":115},"      \u003C",[105,3793,3686],{"class":237},[105,3795,3796,3799,3801],{"class":107,"line":679},[105,3797,3798],{"class":241},"        v-for",[105,3800,245],{"class":115},[105,3802,3803],{"class":248},"\"item in visibleItems\"\n",[105,3805,3806,3809,3811],{"class":107,"line":689},[105,3807,3808],{"class":241},"        :key",[105,3810,245],{"class":115},[105,3812,3813],{"class":248},"\"item.id\"\n",[105,3815,3816,3819,3821],{"class":107,"line":699},[105,3817,3818],{"class":241},"        class",[105,3820,245],{"class":115},[105,3822,3823],{"class":248},"\"list-item\"\n",[105,3825,3826,3829,3831],{"class":107,"line":704},[105,3827,3828],{"class":241},"        :style",[105,3830,245],{"class":115},[105,3832,3833],{"class":248},"\"{ height: itemHeight + 'px' }\"\n",[105,3835,3836],{"class":107,"line":710},[105,3837,3838],{"class":115},"      >\n",[105,3840,3841],{"class":107,"line":1136},[105,3842,3843],{"class":115},"        {{ item.name }}\n",[105,3845,3846,3849,3851],{"class":107,"line":1149},[105,3847,3848],{"class":115},"      \u003C\u002F",[105,3850,238],{"class":237},[105,3852,257],{"class":115},[105,3854,3856,3859,3861],{"class":107,"line":3855},20,[105,3857,3858],{"class":115},"    \u003C\u002F",[105,3860,238],{"class":237},[105,3862,257],{"class":115},[105,3864,3866,3869,3871],{"class":107,"line":3865},21,[105,3867,3868],{"class":115},"  \u003C\u002F",[105,3870,238],{"class":237},[105,3872,257],{"class":115},[105,3874,3876,3878,3880],{"class":107,"line":3875},22,[105,3877,3010],{"class":115},[105,3879,3676],{"class":237},[105,3881,257],{"class":115},[105,3883,3885],{"class":107,"line":3884},23,[105,3886,307],{"emptyLinePlaceholder":306},[105,3888,3890,3892,3894,3896],{"class":107,"line":3889},24,[105,3891,234],{"class":115},[105,3893,2974],{"class":237},[105,3895,2977],{"class":241},[105,3897,257],{"class":115},[105,3899,3901,3904,3907,3910,3913],{"class":107,"line":3900},25,[105,3902,3903],{"class":111},"import",[105,3905,3906],{"class":115}," { ref, computed, onMounted, onBeforeUnmount } ",[105,3908,3909],{"class":111},"from",[105,3911,3912],{"class":248}," 'vue'",[105,3914,145],{"class":115},[105,3916,3918],{"class":107,"line":3917},26,[105,3919,307],{"emptyLinePlaceholder":306},[105,3921,3923,3925,3928,3930,3933],{"class":107,"line":3922},27,[105,3924,568],{"class":111},[105,3926,3927],{"class":129}," props",[105,3929,574],{"class":111},[105,3931,3932],{"class":241}," defineProps",[105,3934,3935],{"class":115},"({\n",[105,3937,3939,3942,3945,3947,3949],{"class":107,"line":3938},28,[105,3940,3941],{"class":115},"  list: { type: Array, ",[105,3943,3944],{"class":241},"default",[105,3946,2825],{"class":115},[105,3948,723],{"class":111},[105,3950,3951],{"class":115}," [] },\n",[105,3953,3955,3958,3961],{"class":107,"line":3954},29,[105,3956,3957],{"class":115},"  itemHeight: { type: Number, default: ",[105,3959,3960],{"class":129},"50",[105,3962,2897],{"class":115},[105,3964,3966,3969,3971,3974],{"class":107,"line":3965},30,[105,3967,3968],{"class":115},"  buffer: { type: Number, default: ",[105,3970,891],{"class":129},[105,3972,3973],{"class":115}," } ",[105,3975,3976],{"class":630},"\u002F\u002F 上下缓冲区，防止快速滚动白屏\n",[105,3978,3980],{"class":107,"line":3979},31,[105,3981,3982],{"class":115},"});\n",[105,3984,3986],{"class":107,"line":3985},32,[105,3987,307],{"emptyLinePlaceholder":306},[105,3989,3991,3993,3996,3998,4001,4003,4006],{"class":107,"line":3990},33,[105,3992,568],{"class":111},[105,3994,3995],{"class":129}," viewportRef",[105,3997,574],{"class":111},[105,3999,4000],{"class":241}," ref",[105,4002,321],{"class":115},[105,4004,4005],{"class":129},"null",[105,4007,327],{"class":115},[105,4009,4011,4013,4016,4018,4020,4022,4024],{"class":107,"line":4010},34,[105,4012,568],{"class":111},[105,4014,4015],{"class":129}," scrollTop",[105,4017,574],{"class":111},[105,4019,4000],{"class":241},[105,4021,321],{"class":115},[105,4023,1100],{"class":129},[105,4025,327],{"class":115},[105,4027,4029,4031,4034,4036,4038,4040,4042],{"class":107,"line":4028},35,[105,4030,568],{"class":111},[105,4032,4033],{"class":129}," viewportHeight",[105,4035,574],{"class":111},[105,4037,4000],{"class":241},[105,4039,321],{"class":115},[105,4041,1100],{"class":129},[105,4043,327],{"class":115},[105,4045,4047],{"class":107,"line":4046},36,[105,4048,307],{"emptyLinePlaceholder":306},[105,4050,4052],{"class":107,"line":4051},37,[105,4053,4054],{"class":630},"\u002F\u002F 总高度：撑起滚动条\n",[105,4056,4058,4060,4063,4065,4068,4071,4073,4076,4079,4082],{"class":107,"line":4057},38,[105,4059,568],{"class":111},[105,4061,4062],{"class":129}," totalHeight",[105,4064,574],{"class":111},[105,4066,4067],{"class":241}," computed",[105,4069,4070],{"class":115},"(() ",[105,4072,723],{"class":111},[105,4074,4075],{"class":115}," props.list.",[105,4077,4078],{"class":129},"length",[105,4080,4081],{"class":111}," *",[105,4083,4084],{"class":115}," props.itemHeight);\n",[105,4086,4088],{"class":107,"line":4087},39,[105,4089,307],{"emptyLinePlaceholder":306},[105,4091,4093],{"class":107,"line":4092},40,[105,4094,4095],{"class":630},"\u002F\u002F 可见起始索引（含缓冲区）\n",[105,4097,4099,4101,4104,4106,4108,4110,4112],{"class":107,"line":4098},41,[105,4100,568],{"class":111},[105,4102,4103],{"class":129}," startIndex",[105,4105,574],{"class":111},[105,4107,4067],{"class":241},[105,4109,4070],{"class":115},[105,4111,723],{"class":111},[105,4113,116],{"class":115},[105,4115,4117,4120,4123,4125,4128,4131,4134,4137],{"class":107,"line":4116},42,[105,4118,4119],{"class":111},"  const",[105,4121,4122],{"class":129}," raw",[105,4124,574],{"class":111},[105,4126,4127],{"class":115}," Math.",[105,4129,4130],{"class":241},"floor",[105,4132,4133],{"class":115},"(scrollTop.value ",[105,4135,4136],{"class":111},"\u002F",[105,4138,4084],{"class":115},[105,4140,4142,4145,4147,4150,4152,4154,4157,4160],{"class":107,"line":4141},43,[105,4143,4144],{"class":111},"  return",[105,4146,4127],{"class":115},[105,4148,4149],{"class":241},"max",[105,4151,321],{"class":115},[105,4153,1100],{"class":129},[105,4155,4156],{"class":115},", raw ",[105,4158,4159],{"class":111},"-",[105,4161,4162],{"class":115}," props.buffer);\n",[105,4164,4166],{"class":107,"line":4165},44,[105,4167,3982],{"class":115},[105,4169,4171],{"class":107,"line":4170},45,[105,4172,307],{"emptyLinePlaceholder":306},[105,4174,4176],{"class":107,"line":4175},46,[105,4177,4178],{"class":630},"\u002F\u002F 可见结束索引（含缓冲区）\n",[105,4180,4182,4184,4187,4189,4191,4193,4195],{"class":107,"line":4181},47,[105,4183,568],{"class":111},[105,4185,4186],{"class":129}," endIndex",[105,4188,574],{"class":111},[105,4190,4067],{"class":241},[105,4192,4070],{"class":115},[105,4194,723],{"class":111},[105,4196,116],{"class":115},[105,4198,4200,4202,4204,4206,4208,4211,4214,4216,4219,4221],{"class":107,"line":4199},48,[105,4201,4119],{"class":111},[105,4203,4122],{"class":129},[105,4205,574],{"class":111},[105,4207,4127],{"class":115},[105,4209,4210],{"class":241},"ceil",[105,4212,4213],{"class":115},"((scrollTop.value ",[105,4215,1085],{"class":111},[105,4217,4218],{"class":115}," viewportHeight.value) ",[105,4220,4136],{"class":111},[105,4222,4084],{"class":115},[105,4224,4226,4228,4230,4233,4236,4238,4241,4244,4246,4248],{"class":107,"line":4225},49,[105,4227,4144],{"class":111},[105,4229,4127],{"class":115},[105,4231,4232],{"class":241},"min",[105,4234,4235],{"class":115},"(props.list.",[105,4237,4078],{"class":129},[105,4239,4240],{"class":111}," -",[105,4242,4243],{"class":129}," 1",[105,4245,4156],{"class":115},[105,4247,1085],{"class":111},[105,4249,4162],{"class":115},[105,4251,4253],{"class":107,"line":4252},50,[105,4254,3982],{"class":115},[105,4256,4258],{"class":107,"line":4257},51,[105,4259,307],{"emptyLinePlaceholder":306},[105,4261,4263],{"class":107,"line":4262},52,[105,4264,4265],{"class":630},"\u002F\u002F 当前可见的数据切片\n",[105,4267,4269,4271,4274,4276,4278,4280],{"class":107,"line":4268},53,[105,4270,568],{"class":111},[105,4272,4273],{"class":129}," visibleItems",[105,4275,574],{"class":111},[105,4277,4067],{"class":241},[105,4279,4070],{"class":115},[105,4281,4282],{"class":111},"=>\n",[105,4284,4286,4289,4292,4295,4297,4299],{"class":107,"line":4285},54,[105,4287,4288],{"class":115},"  props.list.",[105,4290,4291],{"class":241},"slice",[105,4293,4294],{"class":115},"(startIndex.value, endIndex.value ",[105,4296,1085],{"class":111},[105,4298,4243],{"class":129},[105,4300,1529],{"class":115},[105,4302,4304],{"class":107,"line":4303},55,[105,4305,327],{"class":115},[105,4307,4309],{"class":107,"line":4308},56,[105,4310,307],{"emptyLinePlaceholder":306},[105,4312,4314],{"class":107,"line":4313},57,[105,4315,4316],{"class":630},"\u002F\u002F 渲染层的偏移量\n",[105,4318,4320,4322,4325,4327,4329,4331,4333,4336,4338],{"class":107,"line":4319},58,[105,4321,568],{"class":111},[105,4323,4324],{"class":129}," offsetY",[105,4326,574],{"class":111},[105,4328,4067],{"class":241},[105,4330,4070],{"class":115},[105,4332,723],{"class":111},[105,4334,4335],{"class":115}," startIndex.value ",[105,4337,938],{"class":111},[105,4339,4084],{"class":115},[105,4341,4343],{"class":107,"line":4342},59,[105,4344,307],{"emptyLinePlaceholder":306},[105,4346,4348],{"class":107,"line":4347},60,[105,4349,4350],{"class":630},"\u002F\u002F 滚动处理（用 rAF 节流）\n",[105,4352,4354,4357,4360,4362,4365],{"class":107,"line":4353},61,[105,4355,4356],{"class":111},"let",[105,4358,4359],{"class":115}," ticking ",[105,4361,245],{"class":111},[105,4363,4364],{"class":129}," false",[105,4366,145],{"class":115},[105,4368,4370,4372,4375,4377,4380,4382,4384,4386],{"class":107,"line":4369},62,[105,4371,568],{"class":111},[105,4373,4374],{"class":241}," onScroll",[105,4376,574],{"class":111},[105,4378,4379],{"class":115}," (",[105,4381,3315],{"class":122},[105,4383,2854],{"class":115},[105,4385,723],{"class":111},[105,4387,116],{"class":115},[105,4389,4391,4393,4396,4398],{"class":107,"line":4390},63,[105,4392,3326],{"class":111},[105,4394,4395],{"class":115}," (ticking) ",[105,4397,3340],{"class":111},[105,4399,145],{"class":115},[105,4401,4403,4406,4408,4411],{"class":107,"line":4402},64,[105,4404,4405],{"class":115},"  ticking ",[105,4407,245],{"class":111},[105,4409,4410],{"class":129}," true",[105,4412,145],{"class":115},[105,4414,4416,4419,4421,4423],{"class":107,"line":4415},65,[105,4417,4418],{"class":241},"  requestAnimationFrame",[105,4420,4070],{"class":115},[105,4422,723],{"class":111},[105,4424,116],{"class":115},[105,4426,4428,4431,4433],{"class":107,"line":4427},66,[105,4429,4430],{"class":115},"    scrollTop.value ",[105,4432,245],{"class":111},[105,4434,4435],{"class":115}," e.target.scrollTop;\n",[105,4437,4439,4442,4444,4446],{"class":107,"line":4438},67,[105,4440,4441],{"class":115},"    ticking ",[105,4443,245],{"class":111},[105,4445,4364],{"class":129},[105,4447,145],{"class":115},[105,4449,4451],{"class":107,"line":4450},68,[105,4452,4453],{"class":115},"  });\n",[105,4455,4457],{"class":107,"line":4456},69,[105,4458,4459],{"class":115},"};\n",[105,4461,4463],{"class":107,"line":4462},70,[105,4464,307],{"emptyLinePlaceholder":306},[105,4466,4468],{"class":107,"line":4467},71,[105,4469,4470],{"class":630},"\u002F\u002F 初始化视口高度\n",[105,4472,4474,4476,4479,4481,4483,4485],{"class":107,"line":4473},72,[105,4475,568],{"class":111},[105,4477,4478],{"class":241}," updateViewportHeight",[105,4480,574],{"class":111},[105,4482,720],{"class":115},[105,4484,723],{"class":111},[105,4486,116],{"class":115},[105,4488,4490,4492],{"class":107,"line":4489},73,[105,4491,3326],{"class":111},[105,4493,4494],{"class":115}," (viewportRef.value) {\n",[105,4496,4498,4501,4503],{"class":107,"line":4497},74,[105,4499,4500],{"class":115},"    viewportHeight.value ",[105,4502,245],{"class":111},[105,4504,4505],{"class":115}," viewportRef.value.clientHeight;\n",[105,4507,4509],{"class":107,"line":4508},75,[105,4510,509],{"class":115},[105,4512,4514],{"class":107,"line":4513},76,[105,4515,4459],{"class":115},[105,4517,4519],{"class":107,"line":4518},77,[105,4520,307],{"emptyLinePlaceholder":306},[105,4522,4524,4527,4529,4531],{"class":107,"line":4523},78,[105,4525,4526],{"class":241},"onMounted",[105,4528,4070],{"class":115},[105,4530,723],{"class":111},[105,4532,116],{"class":115},[105,4534,4536,4539],{"class":107,"line":4535},79,[105,4537,4538],{"class":241},"  updateViewportHeight",[105,4540,665],{"class":115},[105,4542,4544,4547,4549,4551,4554],{"class":107,"line":4543},80,[105,4545,4546],{"class":115},"  window.",[105,4548,3304],{"class":241},[105,4550,321],{"class":115},[105,4552,4553],{"class":248},"'resize'",[105,4555,4556],{"class":115},", updateViewportHeight);\n",[105,4558,4560],{"class":107,"line":4559},81,[105,4561,3982],{"class":115},[105,4563,4565],{"class":107,"line":4564},82,[105,4566,307],{"emptyLinePlaceholder":306},[105,4568,4570,4573,4575,4577],{"class":107,"line":4569},83,[105,4571,4572],{"class":241},"onBeforeUnmount",[105,4574,4070],{"class":115},[105,4576,723],{"class":111},[105,4578,116],{"class":115},[105,4580,4582,4584,4586,4588,4590],{"class":107,"line":4581},84,[105,4583,4546],{"class":115},[105,4585,3572],{"class":241},[105,4587,321],{"class":115},[105,4589,4553],{"class":248},[105,4591,4556],{"class":115},[105,4593,4595],{"class":107,"line":4594},85,[105,4596,3982],{"class":115},[105,4598,4600,4602,4604],{"class":107,"line":4599},86,[105,4601,3010],{"class":115},[105,4603,2974],{"class":237},[105,4605,257],{"class":115},[105,4607,4609],{"class":107,"line":4608},87,[105,4610,307],{"emptyLinePlaceholder":306},[105,4612,4614,4616,4618,4621],{"class":107,"line":4613},88,[105,4615,234],{"class":115},[105,4617,3633],{"class":237},[105,4619,4620],{"class":241}," scoped",[105,4622,257],{"class":115},[105,4624,4626,4629],{"class":107,"line":4625},89,[105,4627,4628],{"class":241},".virtual-list",[105,4630,116],{"class":115},[105,4632,4634,4636,4638,4641,4643,4646],{"class":107,"line":4633},90,[105,4635,2702],{"class":129},[105,4637,126],{"class":115},[105,4639,4640],{"class":129},"500",[105,4642,142],{"class":111},[105,4644,4645],{"class":115},";          ",[105,4647,4648],{"class":630},"\u002F* 固定视口高度 *\u002F\n",[105,4650,4652,4655,4657,4659],{"class":107,"line":4651},91,[105,4653,4654],{"class":129},"  overflow-y",[105,4656,126],{"class":115},[105,4658,1902],{"class":129},[105,4660,145],{"class":115},[105,4662,4664,4666,4668,4670],{"class":107,"line":4663},92,[105,4665,2618],{"class":129},[105,4667,126],{"class":115},[105,4669,1884],{"class":129},[105,4671,145],{"class":115},[105,4673,4675,4677,4679,4681,4683,4685,4688],{"class":107,"line":4674},93,[105,4676,1005],{"class":129},[105,4678,126],{"class":115},[105,4680,861],{"class":129},[105,4682,142],{"class":111},[105,4684,1014],{"class":129},[105,4686,4687],{"class":129}," #ddd",[105,4689,145],{"class":115},[105,4691,4693],{"class":107,"line":4692},94,[105,4694,212],{"class":115},[105,4696,4698],{"class":107,"line":4697},95,[105,4699,307],{"emptyLinePlaceholder":306},[105,4701,4703,4706],{"class":107,"line":4702},96,[105,4704,4705],{"class":241},".phantom",[105,4707,116],{"class":115},[105,4709,4711,4713,4715,4717],{"class":107,"line":4710},97,[105,4712,2618],{"class":129},[105,4714,126],{"class":115},[105,4716,1888],{"class":129},[105,4718,145],{"class":115},[105,4720,4722,4724,4726,4728],{"class":107,"line":4721},98,[105,4723,2666],{"class":129},[105,4725,126],{"class":115},[105,4727,1100],{"class":129},[105,4729,145],{"class":115},[105,4731,4733,4735,4737,4739],{"class":107,"line":4732},99,[105,4734,2677],{"class":129},[105,4736,126],{"class":115},[105,4738,1100],{"class":129},[105,4740,145],{"class":115},[105,4742,4744,4747,4749,4751],{"class":107,"line":4743},100,[105,4745,4746],{"class":129},"  right",[105,4748,126],{"class":115},[105,4750,1100],{"class":129},[105,4752,145],{"class":115},[105,4754,4756,4758,4760,4763,4766],{"class":107,"line":4755},101,[105,4757,1795],{"class":129},[105,4759,126],{"class":115},[105,4761,4762],{"class":129},"-1",[105,4764,4765],{"class":115},";            ",[105,4767,4768],{"class":630},"\u002F* 不遮挡内容，只撑高度 *\u002F\n",[105,4770,4772],{"class":107,"line":4771},102,[105,4773,212],{"class":115},[105,4775,4777],{"class":107,"line":4776},103,[105,4778,307],{"emptyLinePlaceholder":306},[105,4780,4782,4785],{"class":107,"line":4781},104,[105,4783,4784],{"class":241},".render-layer",[105,4786,116],{"class":115},[105,4788,4790,4792,4794,4796],{"class":107,"line":4789},105,[105,4791,2618],{"class":129},[105,4793,126],{"class":115},[105,4795,1888],{"class":129},[105,4797,145],{"class":115},[105,4799,4801,4803,4805,4807],{"class":107,"line":4800},106,[105,4802,2666],{"class":129},[105,4804,126],{"class":115},[105,4806,1100],{"class":129},[105,4808,145],{"class":115},[105,4810,4812,4814,4816,4818],{"class":107,"line":4811},107,[105,4813,2677],{"class":129},[105,4815,126],{"class":115},[105,4817,1100],{"class":129},[105,4819,145],{"class":115},[105,4821,4823,4825,4827,4829],{"class":107,"line":4822},108,[105,4824,4746],{"class":129},[105,4826,126],{"class":115},[105,4828,1100],{"class":129},[105,4830,145],{"class":115},[105,4832,4834,4837,4840],{"class":107,"line":4833},109,[105,4835,4836],{"class":129},"  will-change",[105,4838,4839],{"class":115},": transform; ",[105,4841,4842],{"class":630},"\u002F* 提示浏览器优化 *\u002F\n",[105,4844,4846],{"class":107,"line":4845},110,[105,4847,212],{"class":115},[105,4849,4851],{"class":107,"line":4850},111,[105,4852,307],{"emptyLinePlaceholder":306},[105,4854,4856,4859],{"class":107,"line":4855},112,[105,4857,4858],{"class":241},".list-item",[105,4860,116],{"class":115},[105,4862,4864,4867,4869,4872],{"class":107,"line":4863},113,[105,4865,4866],{"class":129},"  box-sizing",[105,4868,126],{"class":115},[105,4870,4871],{"class":129},"border-box",[105,4873,145],{"class":115},[105,4875,4877,4880,4882,4884,4886,4888,4891],{"class":107,"line":4876},114,[105,4878,4879],{"class":129},"  border-bottom",[105,4881,126],{"class":115},[105,4883,861],{"class":129},[105,4885,142],{"class":111},[105,4887,1014],{"class":129},[105,4889,4890],{"class":129}," #eee",[105,4892,145],{"class":115},[105,4894,4896,4898,4900,4903],{"class":107,"line":4895},115,[105,4897,1139],{"class":129},[105,4899,126],{"class":115},[105,4901,4902],{"class":129},"flex",[105,4904,145],{"class":115},[105,4906,4908,4911,4913,4916],{"class":107,"line":4907},116,[105,4909,4910],{"class":129},"  align-items",[105,4912,126],{"class":115},[105,4914,4915],{"class":129},"center",[105,4917,145],{"class":115},[105,4919,4921,4923,4925,4927,4930,4932],{"class":107,"line":4920},117,[105,4922,1779],{"class":129},[105,4924,126],{"class":115},[105,4926,1100],{"class":129},[105,4928,4929],{"class":129}," 12",[105,4931,142],{"class":111},[105,4933,145],{"class":115},[105,4935,4937],{"class":107,"line":4936},118,[105,4938,212],{"class":115},[105,4940,4942,4944,4946],{"class":107,"line":4941},119,[105,4943,3010],{"class":115},[105,4945,3633],{"class":237},[105,4947,257],{"class":115},[10,4949,4950],{"id":4950},"浏览器",[18,4952,4953],{},"多标签通信办法，在同源策略下，可以使用BroadcastChannel API，这个是专门为了这个场景而设计的，比localStorage事件、SharedWorker这些老方案更加干净。",[96,4955,4957],{"className":559,"code":4956,"language":561,"meta":101,"style":101},"const channel = new BroadcastChannel('my-channel')\n",[24,4958,4959],{"__ignoreMap":101},[105,4960,4961,4963,4966,4968,4970,4973,4975,4978],{"class":107,"line":108},[105,4962,568],{"class":111},[105,4964,4965],{"class":129}," channel",[105,4967,574],{"class":111},[105,4969,3496],{"class":111},[105,4971,4972],{"class":241}," BroadcastChannel",[105,4974,321],{"class":115},[105,4976,4977],{"class":248},"'my-channel'",[105,4979,1529],{"class":115},[18,4981,4982],{},"通用的通信逻辑可以放在composables这种，具体的业务响应可以放在组件里：",[96,4984,4986],{"className":3259,"code":4985,"language":3261,"meta":101,"style":101},"\u002F\u002F composables\u002FuseBroadcast.ts —— 通用封装\nexport function useBroadcast\u003CT>(name: string) {\n  const channel = shallowRef\u003CBroadcastChannel | null>(null)\n  const handlers = new Set\u003C(data: T) => void>()\n\n  onMounted(() => {\n    channel.value = new BroadcastChannel(name)\n    channel.value.onmessage = (e) => {\n      handlers.forEach(h => h(e.data))\n    }\n  })\n\n  onUnmounted(() => {\n    channel.value?.close()\n  })\n\n  function post(data: T) {\n    channel.value?.postMessage(data)\n  }\n\n  function on(handler: (data: T) => void) {\n    handlers.add(handler)\n    onUnmounted(() => handlers.delete(handler))\n  }\n\n  return { post, on }\n}\n",[24,4987,4988,4993,5023,5051,5086,5090,5101,5115,5135,5157,5162,5167,5171,5182,5193,5197,5201,5219,5228,5232,5236,5266,5277,5295,5299,5303,5310],{"__ignoreMap":101},[105,4989,4990],{"class":107,"line":108},[105,4991,4992],{"class":630},"\u002F\u002F composables\u002FuseBroadcast.ts —— 通用封装\n",[105,4994,4995,4998,5001,5004,5006,5009,5012,5015,5018,5021],{"class":107,"line":119},[105,4996,4997],{"class":111},"export",[105,4999,5000],{"class":111}," function",[105,5002,5003],{"class":241}," useBroadcast",[105,5005,234],{"class":115},[105,5007,5008],{"class":241},"T",[105,5010,5011],{"class":115},">(",[105,5013,5014],{"class":122},"name",[105,5016,5017],{"class":111},":",[105,5019,5020],{"class":129}," string",[105,5022,1000],{"class":115},[105,5024,5025,5027,5029,5031,5034,5036,5039,5042,5045,5047,5049],{"class":107,"line":133},[105,5026,4119],{"class":111},[105,5028,4965],{"class":129},[105,5030,574],{"class":111},[105,5032,5033],{"class":241}," shallowRef",[105,5035,234],{"class":115},[105,5037,5038],{"class":241},"BroadcastChannel",[105,5040,5041],{"class":111}," |",[105,5043,5044],{"class":129}," null",[105,5046,5011],{"class":115},[105,5048,4005],{"class":129},[105,5050,1529],{"class":115},[105,5052,5053,5055,5058,5060,5062,5065,5068,5071,5073,5076,5078,5080,5083],{"class":107,"line":148},[105,5054,4119],{"class":111},[105,5056,5057],{"class":129}," handlers",[105,5059,574],{"class":111},[105,5061,3496],{"class":111},[105,5063,5064],{"class":241}," Set",[105,5066,5067],{"class":115},"\u003C(",[105,5069,5070],{"class":122},"data",[105,5072,5017],{"class":111},[105,5074,5075],{"class":241}," T",[105,5077,2854],{"class":115},[105,5079,723],{"class":111},[105,5081,5082],{"class":129}," void",[105,5084,5085],{"class":115},">()\n",[105,5087,5088],{"class":107,"line":163},[105,5089,307],{"emptyLinePlaceholder":306},[105,5091,5092,5095,5097,5099],{"class":107,"line":179},[105,5093,5094],{"class":241},"  onMounted",[105,5096,4070],{"class":115},[105,5098,723],{"class":111},[105,5100,116],{"class":115},[105,5102,5103,5106,5108,5110,5112],{"class":107,"line":194},[105,5104,5105],{"class":115},"    channel.value ",[105,5107,245],{"class":111},[105,5109,3496],{"class":111},[105,5111,4972],{"class":241},[105,5113,5114],{"class":115},"(name)\n",[105,5116,5117,5120,5123,5125,5127,5129,5131,5133],{"class":107,"line":209},[105,5118,5119],{"class":115},"    channel.value.",[105,5121,5122],{"class":241},"onmessage",[105,5124,574],{"class":111},[105,5126,4379],{"class":115},[105,5128,3315],{"class":122},[105,5130,2854],{"class":115},[105,5132,723],{"class":111},[105,5134,116],{"class":115},[105,5136,5137,5140,5143,5145,5148,5151,5154],{"class":107,"line":526},[105,5138,5139],{"class":115},"      handlers.",[105,5141,5142],{"class":241},"forEach",[105,5144,321],{"class":115},[105,5146,5147],{"class":122},"h",[105,5149,5150],{"class":111}," =>",[105,5152,5153],{"class":241}," h",[105,5155,5156],{"class":115},"(e.data))\n",[105,5158,5159],{"class":107,"line":531},[105,5160,5161],{"class":115},"    }\n",[105,5163,5164],{"class":107,"line":668},[105,5165,5166],{"class":115},"  })\n",[105,5168,5169],{"class":107,"line":673},[105,5170,307],{"emptyLinePlaceholder":306},[105,5172,5173,5176,5178,5180],{"class":107,"line":679},[105,5174,5175],{"class":241},"  onUnmounted",[105,5177,4070],{"class":115},[105,5179,723],{"class":111},[105,5181,116],{"class":115},[105,5183,5184,5187,5190],{"class":107,"line":689},[105,5185,5186],{"class":115},"    channel.value?.",[105,5188,5189],{"class":241},"close",[105,5191,5192],{"class":115},"()\n",[105,5194,5195],{"class":107,"line":699},[105,5196,5166],{"class":115},[105,5198,5199],{"class":107,"line":704},[105,5200,307],{"emptyLinePlaceholder":306},[105,5202,5203,5206,5209,5211,5213,5215,5217],{"class":107,"line":710},[105,5204,5205],{"class":111},"  function",[105,5207,5208],{"class":241}," post",[105,5210,321],{"class":115},[105,5212,5070],{"class":122},[105,5214,5017],{"class":111},[105,5216,5075],{"class":241},[105,5218,1000],{"class":115},[105,5220,5221,5223,5225],{"class":107,"line":1136},[105,5222,5186],{"class":115},[105,5224,3255],{"class":241},[105,5226,5227],{"class":115},"(data)\n",[105,5229,5230],{"class":107,"line":1149},[105,5231,509],{"class":115},[105,5233,5234],{"class":107,"line":3855},[105,5235,307],{"emptyLinePlaceholder":306},[105,5237,5238,5240,5243,5245,5248,5250,5252,5254,5256,5258,5260,5262,5264],{"class":107,"line":3865},[105,5239,5205],{"class":111},[105,5241,5242],{"class":241}," on",[105,5244,321],{"class":115},[105,5246,5247],{"class":241},"handler",[105,5249,5017],{"class":111},[105,5251,4379],{"class":115},[105,5253,5070],{"class":122},[105,5255,5017],{"class":111},[105,5257,5075],{"class":241},[105,5259,2854],{"class":115},[105,5261,723],{"class":111},[105,5263,5082],{"class":129},[105,5265,1000],{"class":115},[105,5267,5268,5271,5274],{"class":107,"line":3875},[105,5269,5270],{"class":115},"    handlers.",[105,5272,5273],{"class":241},"add",[105,5275,5276],{"class":115},"(handler)\n",[105,5278,5279,5282,5284,5286,5289,5292],{"class":107,"line":3884},[105,5280,5281],{"class":241},"    onUnmounted",[105,5283,4070],{"class":115},[105,5285,723],{"class":111},[105,5287,5288],{"class":115}," handlers.",[105,5290,5291],{"class":241},"delete",[105,5293,5294],{"class":115},"(handler))\n",[105,5296,5297],{"class":107,"line":3889},[105,5298,509],{"class":115},[105,5300,5301],{"class":107,"line":3900},[105,5302,307],{"emptyLinePlaceholder":306},[105,5304,5305,5307],{"class":107,"line":3917},[105,5306,4144],{"class":111},[105,5308,5309],{"class":115}," { post, on }\n",[105,5311,5312],{"class":107,"line":3922},[105,5313,212],{"class":115},[14,5315,5317],{"id":5316},"localstorage加密邪教技术","localStorage加密邪教技术",[18,5319,5320],{},"cookie和LocalStorage是web开发中最常用的两个存储方式，cookie主要保存在内存中，生命周期和浏览器相关，浏览器关闭之后直接消失（会话cookie），持久cookie是保存在硬盘的特定文件里，生命周期是设置的过期时间。",[18,5322,5323],{},"在Chrome这种浏览器中，持久化cookie存储在用户数据目录里面的cookies SQLITE数据库里，例如 C:\\Users\\用户名\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\cookies",[18,5325,5326,5327,5330,5331,5334],{},"然后看看localStorage，这个始终是存储在硬盘里的独立区域的，Chrome呢，存储在User Data\\Default\\Local Storage\\目录里，- ",[21,5328,5329],{},"Firefox","：存储在 ",[24,5332,5333],{},"webappsstore.sqlite"," 文件中。",[18,5336,5337],{},"cookie的原理是http状态管理，服务器在http响应头里可以加Set-Cookie，浏览器收到后就会保存，每一次向同源服务器发请求的时候浏览器都会自动把Cookie塞进Cookie请求头里，每个Cookie最大4KB，每次请求都会携带，太大会拖慢网络，他有安全属性，HttpOnly让JS读不到，Secure只走HTTPS，SameSite可以控制跨站发送。",[18,5339,5340],{},"localStorage的原理是Web Storage API，是纯客户端，数据只会在浏览器本地，永远不会自动发给服务器，也是按源来隔离的，提供了一些同步API，可以通过setItem、getItem、removeItem等方法直接读写键值对。容量更大，通常为5MB左右，远大于Cookie的4KB，只存字符串，存对象需要先JSON.stringify，取出来再JSON.parse。",[18,5342,5343,5344,31,5347,5350],{},"聊完了之前技术，有一个好玩的东西，有些库",[24,5345,5346],{},"secure-ls",[24,5348,5349],{},"@cyberadityacode\u002Fsecure-ls"," 等可以让我们在localSotrage中存储加密数据，用起来和普通的localStorage差不多，原理是在存入前加密，库会封装setItem和getItem，传入的对象会被AES之类的算法加密成密文，再存进localStorage中，读取的时候自动解密，给我们原始对象。",[96,5352,5354],{"className":3259,"code":5353,"language":3261,"meta":101,"style":101},"\u002F\u002F 普通 localStorage\nlocalStorage.setItem('user', JSON.stringify(user))\n\n\u002F\u002F 加密库的用法（API 类似，但底层自动加解密）\nawait storage.setItem('user', user)  \u002F\u002F 存进去的是密文\nconst user = await storage.getItem('user')  \u002F\u002F 取出来是明文对象\n",[24,5355,5356,5361,5388,5392,5397,5417],{"__ignoreMap":101},[105,5357,5358],{"class":107,"line":108},[105,5359,5360],{"class":630},"\u002F\u002F 普通 localStorage\n",[105,5362,5363,5366,5369,5371,5374,5376,5379,5382,5385],{"class":107,"line":119},[105,5364,5365],{"class":115},"localStorage.",[105,5367,5368],{"class":241},"setItem",[105,5370,321],{"class":115},[105,5372,5373],{"class":248},"'user'",[105,5375,286],{"class":115},[105,5377,5378],{"class":129},"JSON",[105,5380,5381],{"class":115},".",[105,5383,5384],{"class":241},"stringify",[105,5386,5387],{"class":115},"(user))\n",[105,5389,5390],{"class":107,"line":133},[105,5391,307],{"emptyLinePlaceholder":306},[105,5393,5394],{"class":107,"line":148},[105,5395,5396],{"class":630},"\u002F\u002F 加密库的用法（API 类似，但底层自动加解密）\n",[105,5398,5399,5402,5405,5407,5409,5411,5414],{"class":107,"line":163},[105,5400,5401],{"class":111},"await",[105,5403,5404],{"class":115}," storage.",[105,5406,5368],{"class":241},[105,5408,321],{"class":115},[105,5410,5373],{"class":248},[105,5412,5413],{"class":115},", user)  ",[105,5415,5416],{"class":630},"\u002F\u002F 存进去的是密文\n",[105,5418,5419,5421,5424,5426,5429,5431,5434,5436,5438,5441],{"class":107,"line":179},[105,5420,568],{"class":111},[105,5422,5423],{"class":129}," user",[105,5425,574],{"class":111},[105,5427,5428],{"class":111}," await",[105,5430,5404],{"class":115},[105,5432,5433],{"class":241},"getItem",[105,5435,321],{"class":115},[105,5437,5373],{"class":248},[105,5439,5440],{"class":115},")  ",[105,5442,5443],{"class":630},"\u002F\u002F 取出来是明文对象\n",[14,5445,5447],{"id":5446},"service-worker","Service Worker",[18,5449,5450],{},"HTML5的离线存储，也叫做Application Cache，也在已经废弃，继任者叫做Service Worker，ServiceWorker是一个在浏览器后台运行的独立脚本，充当网络请求的代理，可以拦截页面所有请求，并决定是返回缓存资源还是去网络获取，使用分为三步：",[18,5452,5453],{},"注册、安装于缓存、拦截和响应。",[18,5455,5456],{},"注册会在js中注册一个service worker的脚本，安装和缓存会在serviceWorker的install事件中，将需要离线使用的核心资源比如html css js加入到缓存，拦截和响应会在fetch事件中，拦截页面的网络请求，可以制定策略，比如说缓存优先，看看有没有资源，没有再去请求网络，或者网络优先，先请求，失败再回退缓存。",[18,5458,5459,5460,5463],{},"相比 AppCache，Service Worker 提供了",[21,5461,5462],{},"更精细、更灵活的控制能力","。你可以精确管理缓存的版本、更新策略，并且它完全通过 JavaScript 编程实现，不再依赖那个容易出错的 manifest 文件。",[18,5465,5466],{},"service worker不会阻塞主线程，也不能访问dom，靠事件唤醒，空闲时候被终止，能够拦截fetch，再决定怎么响应。注册之后可以长期存在，页面关了也在，必须https或者localhost，只能控制注册路径和子路径。",[18,5468,5469],{},"有service worker之后可以决定",[96,5471,5474],{"className":5472,"code":5473,"language":392},[390],"页面 → 浏览器 → Service Worker（拦截）→ 决定：\n                    ├─ 用缓存响应\n                    ├─ 走网络\n                    └─ 自定义响应\n",[24,5475,5473],{"__ignoreMap":101},[18,5477,5478],{},"Service Worker 的生命周期和普通脚本完全不同，分几个阶段：",[18,5480,5481],{},"首先是注册：",[96,5483,5485],{"className":559,"code":5484,"language":561,"meta":101,"style":101},"\u002F\u002F 主线程\nif ('serviceWorker' in navigator) {\n    navigator.serviceWorker.register('\u002Fsw.js')\n        .then(reg => console.log('注册成功', reg))\n        .catch(err => console.log('注册失败', err));\n}\n",[24,5486,5487,5492,5508,5523,5551,5577],{"__ignoreMap":101},[105,5488,5489],{"class":107,"line":108},[105,5490,5491],{"class":630},"\u002F\u002F 主线程\n",[105,5493,5494,5497,5499,5502,5505],{"class":107,"line":119},[105,5495,5496],{"class":111},"if",[105,5498,4379],{"class":115},[105,5500,5501],{"class":248},"'serviceWorker'",[105,5503,5504],{"class":111}," in",[105,5506,5507],{"class":115}," navigator) {\n",[105,5509,5510,5513,5516,5518,5521],{"class":107,"line":133},[105,5511,5512],{"class":115},"    navigator.serviceWorker.",[105,5514,5515],{"class":241},"register",[105,5517,321],{"class":115},[105,5519,5520],{"class":248},"'\u002Fsw.js'",[105,5522,1529],{"class":115},[105,5524,5525,5528,5531,5533,5536,5538,5541,5543,5545,5548],{"class":107,"line":148},[105,5526,5527],{"class":115},"        .",[105,5529,5530],{"class":241},"then",[105,5532,321],{"class":115},[105,5534,5535],{"class":122},"reg",[105,5537,5150],{"class":111},[105,5539,5540],{"class":115}," console.",[105,5542,639],{"class":241},[105,5544,321],{"class":115},[105,5546,5547],{"class":248},"'注册成功'",[105,5549,5550],{"class":115},", reg))\n",[105,5552,5553,5555,5558,5560,5563,5565,5567,5569,5571,5574],{"class":107,"line":163},[105,5554,5527],{"class":115},[105,5556,5557],{"class":241},"catch",[105,5559,321],{"class":115},[105,5561,5562],{"class":122},"err",[105,5564,5150],{"class":111},[105,5566,5540],{"class":115},[105,5568,639],{"class":241},[105,5570,321],{"class":115},[105,5572,5573],{"class":248},"'注册失败'",[105,5575,5576],{"class":115},", err));\n",[105,5578,5579],{"class":107,"line":179},[105,5580,212],{"class":115},[18,5582,5583],{},"注册的时候文件不会加载，不会立即生效，然后install的时候才会。",[96,5585,5587],{"className":559,"code":5586,"language":561,"meta":101,"style":101},"\u002F\u002F sw.js\nself.addEventListener('install', (event) => {\n    event.waitUntil(\n        caches.open('v1').then(cache => {\n            return cache.addAll([\n                '\u002F',\n                '\u002Findex.html',\n                '\u002Fstyle.css',\n                '\u002Fapp.js'\n            ]);\n        })\n    );\n});\n",[24,5588,5589,5594,5617,5627,5654,5668,5675,5682,5689,5694,5699,5704,5709],{"__ignoreMap":101},[105,5590,5591],{"class":107,"line":108},[105,5592,5593],{"class":630},"\u002F\u002F sw.js\n",[105,5595,5596,5599,5601,5603,5606,5608,5611,5613,5615],{"class":107,"line":119},[105,5597,5598],{"class":115},"self.",[105,5600,3304],{"class":241},[105,5602,321],{"class":115},[105,5604,5605],{"class":248},"'install'",[105,5607,3312],{"class":115},[105,5609,5610],{"class":122},"event",[105,5612,2854],{"class":115},[105,5614,723],{"class":111},[105,5616,116],{"class":115},[105,5618,5619,5622,5625],{"class":107,"line":133},[105,5620,5621],{"class":115},"    event.",[105,5623,5624],{"class":241},"waitUntil",[105,5626,583],{"class":115},[105,5628,5629,5632,5635,5637,5640,5643,5645,5647,5650,5652],{"class":107,"line":148},[105,5630,5631],{"class":115},"        caches.",[105,5633,5634],{"class":241},"open",[105,5636,321],{"class":115},[105,5638,5639],{"class":248},"'v1'",[105,5641,5642],{"class":115},").",[105,5644,5530],{"class":241},[105,5646,321],{"class":115},[105,5648,5649],{"class":122},"cache",[105,5651,5150],{"class":111},[105,5653,116],{"class":115},[105,5655,5656,5659,5662,5665],{"class":107,"line":163},[105,5657,5658],{"class":111},"            return",[105,5660,5661],{"class":115}," cache.",[105,5663,5664],{"class":241},"addAll",[105,5666,5667],{"class":115},"([\n",[105,5669,5670,5673],{"class":107,"line":179},[105,5671,5672],{"class":248},"                '\u002F'",[105,5674,2867],{"class":115},[105,5676,5677,5680],{"class":107,"line":194},[105,5678,5679],{"class":248},"                '\u002Findex.html'",[105,5681,2867],{"class":115},[105,5683,5684,5687],{"class":107,"line":209},[105,5685,5686],{"class":248},"                '\u002Fstyle.css'",[105,5688,2867],{"class":115},[105,5690,5691],{"class":107,"line":526},[105,5692,5693],{"class":248},"                '\u002Fapp.js'\n",[105,5695,5696],{"class":107,"line":531},[105,5697,5698],{"class":115},"            ]);\n",[105,5700,5701],{"class":107,"line":668},[105,5702,5703],{"class":115},"        })\n",[105,5705,5706],{"class":107,"line":673},[105,5707,5708],{"class":115},"    );\n",[105,5710,5711],{"class":107,"line":679},[105,5712,3982],{"class":115},[18,5714,5715,5716,5719],{},"install事件在sw首次注册的时候或者文件内容变化的时候触发，通常在预缓存静态资源。 ",[24,5717,5718],{},"event.waitUntil(promise)","让浏览器\"等这个 Promise 完成再算安装成功\"。然后是activate激活阶段：",[96,5721,5723],{"className":559,"code":5722,"language":561,"meta":101,"style":101},"self.addEventListener('activate', (event) => {\n    event.waitUntil(\n        caches.keys().then(keys => {\n            return Promise.all(\n                keys.filter(k => k !== 'v1')\n                    .map(k => caches.delete(k))   \u002F\u002F 清理旧缓存\n            );\n        })\n    );\n});\n",[24,5724,5725,5746,5754,5774,5788,5813,5838,5843,5847,5851],{"__ignoreMap":101},[105,5726,5727,5729,5731,5733,5736,5738,5740,5742,5744],{"class":107,"line":108},[105,5728,5598],{"class":115},[105,5730,3304],{"class":241},[105,5732,321],{"class":115},[105,5734,5735],{"class":248},"'activate'",[105,5737,3312],{"class":115},[105,5739,5610],{"class":122},[105,5741,2854],{"class":115},[105,5743,723],{"class":111},[105,5745,116],{"class":115},[105,5747,5748,5750,5752],{"class":107,"line":119},[105,5749,5621],{"class":115},[105,5751,5624],{"class":241},[105,5753,583],{"class":115},[105,5755,5756,5758,5761,5764,5766,5768,5770,5772],{"class":107,"line":133},[105,5757,5631],{"class":115},[105,5759,5760],{"class":241},"keys",[105,5762,5763],{"class":115},"().",[105,5765,5530],{"class":241},[105,5767,321],{"class":115},[105,5769,5760],{"class":122},[105,5771,5150],{"class":111},[105,5773,116],{"class":115},[105,5775,5776,5778,5781,5783,5786],{"class":107,"line":148},[105,5777,5658],{"class":111},[105,5779,5780],{"class":129}," Promise",[105,5782,5381],{"class":115},[105,5784,5785],{"class":241},"all",[105,5787,583],{"class":115},[105,5789,5790,5793,5796,5798,5801,5803,5806,5808,5811],{"class":107,"line":163},[105,5791,5792],{"class":115},"                keys.",[105,5794,5795],{"class":241},"filter",[105,5797,321],{"class":115},[105,5799,5800],{"class":122},"k",[105,5802,5150],{"class":111},[105,5804,5805],{"class":115}," k ",[105,5807,3332],{"class":111},[105,5809,5810],{"class":248}," 'v1'",[105,5812,1529],{"class":115},[105,5814,5815,5818,5821,5823,5825,5827,5830,5832,5835],{"class":107,"line":179},[105,5816,5817],{"class":115},"                    .",[105,5819,5820],{"class":241},"map",[105,5822,321],{"class":115},[105,5824,5800],{"class":122},[105,5826,5150],{"class":111},[105,5828,5829],{"class":115}," caches.",[105,5831,5291],{"class":241},[105,5833,5834],{"class":115},"(k))   ",[105,5836,5837],{"class":630},"\u002F\u002F 清理旧缓存\n",[105,5839,5840],{"class":107,"line":194},[105,5841,5842],{"class":115},"            );\n",[105,5844,5845],{"class":107,"line":209},[105,5846,5703],{"class":115},[105,5848,5849],{"class":107,"line":526},[105,5850,5708],{"class":115},[105,5852,5853],{"class":107,"line":531},[105,5854,3982],{"class":115},[18,5856,5857,5860,5861,5864],{},[24,5858,5859],{},"activate"," 在安装成功后、SW 正式接管页面前触发。通常在这里",[21,5862,5863],{},"清理旧版本缓存","。最后是接管：",[96,5866,5868],{"className":559,"code":5867,"language":561,"meta":101,"style":101},"self.addEventListener('activate', (event) => {\n    event.waitUntil(self.clients.claim());   \u002F\u002F 立即接管\n});\n",[24,5869,5870,5890,5908],{"__ignoreMap":101},[105,5871,5872,5874,5876,5878,5880,5882,5884,5886,5888],{"class":107,"line":108},[105,5873,5598],{"class":115},[105,5875,3304],{"class":241},[105,5877,321],{"class":115},[105,5879,5735],{"class":248},[105,5881,3312],{"class":115},[105,5883,5610],{"class":122},[105,5885,2854],{"class":115},[105,5887,723],{"class":111},[105,5889,116],{"class":115},[105,5891,5892,5894,5896,5899,5902,5905],{"class":107,"line":119},[105,5893,5621],{"class":115},[105,5895,5624],{"class":241},[105,5897,5898],{"class":115},"(self.clients.",[105,5900,5901],{"class":241},"claim",[105,5903,5904],{"class":115},"());   ",[105,5906,5907],{"class":630},"\u002F\u002F 立即接管\n",[105,5909,5910],{"class":107,"line":133},[105,5911,3982],{"class":115},[18,5913,5914],{},"默认情况下，sw安装不会立即开始接管已经打开的窗口，需要等待页面刷新，使用这个情况我们可以在这里激活后直接接管。\n之后service worker就可以帮我们来完成一系列工作了，比如说拦截请求：",[96,5916,5918],{"className":559,"code":5917,"language":561,"meta":101,"style":101},"self.addEventListener('fetch', (event) => {\n    event.respondWith(\n        caches.match(event.request).then(cached => {\n            \u002F\u002F 缓存命中 → 用缓存\n            if (cached) return cached;\n            \u002F\u002F 没命中 → 走网络\n            return fetch(event.request);\n        })\n    );\n});\n",[24,5919,5920,5941,5950,5971,5976,5989,5994,6004,6008,6012],{"__ignoreMap":101},[105,5921,5922,5924,5926,5928,5931,5933,5935,5937,5939],{"class":107,"line":108},[105,5923,5598],{"class":115},[105,5925,3304],{"class":241},[105,5927,321],{"class":115},[105,5929,5930],{"class":248},"'fetch'",[105,5932,3312],{"class":115},[105,5934,5610],{"class":122},[105,5936,2854],{"class":115},[105,5938,723],{"class":111},[105,5940,116],{"class":115},[105,5942,5943,5945,5948],{"class":107,"line":119},[105,5944,5621],{"class":115},[105,5946,5947],{"class":241},"respondWith",[105,5949,583],{"class":115},[105,5951,5952,5954,5957,5960,5962,5964,5967,5969],{"class":107,"line":133},[105,5953,5631],{"class":115},[105,5955,5956],{"class":241},"match",[105,5958,5959],{"class":115},"(event.request).",[105,5961,5530],{"class":241},[105,5963,321],{"class":115},[105,5965,5966],{"class":122},"cached",[105,5968,5150],{"class":111},[105,5970,116],{"class":115},[105,5972,5973],{"class":107,"line":148},[105,5974,5975],{"class":630},"            \u002F\u002F 缓存命中 → 用缓存\n",[105,5977,5978,5981,5984,5986],{"class":107,"line":163},[105,5979,5980],{"class":111},"            if",[105,5982,5983],{"class":115}," (cached) ",[105,5985,3340],{"class":111},[105,5987,5988],{"class":115}," cached;\n",[105,5990,5991],{"class":107,"line":179},[105,5992,5993],{"class":630},"            \u002F\u002F 没命中 → 走网络\n",[105,5995,5996,5998,6001],{"class":107,"line":194},[105,5997,5658],{"class":111},[105,5999,6000],{"class":241}," fetch",[105,6002,6003],{"class":115},"(event.request);\n",[105,6005,6006],{"class":107,"line":209},[105,6007,5703],{"class":115},[105,6009,6010],{"class":107,"line":526},[105,6011,5708],{"class":115},[105,6013,6014],{"class":107,"line":531},[105,6015,3982],{"class":115},[18,6017,6018],{},"我们可以在这里设置一个缓存优先策略\u002F网络优先策略，这个是fetch事件的常见写法，适合需要最新数据但是也要离线兜底的接口。",[96,6020,6022],{"className":559,"code":6021,"language":561,"meta":101,"style":101},"self.addEventListener('fetch', (event) => {\n    event.respondWith(\n        caches.match(event.request).then(cached => {\n            return cached || fetch(event.request);\n        })\n    );\n}); \u002F\u002F 缓存\n\nself.addEventListener('fetch', (event) => {\n    event.respondWith(\n        fetch(event.request)\n            .then(response => {\n                \u002F\u002F 网络成功，更新缓存\n                const clone = response.clone();\n                caches.open('v1').then(cache => cache.put(event.request, clone));\n                return response;\n            })\n            .catch(() => caches.match(event.request))   \u002F\u002F 网络失败用缓存\n    );\n});\n",[24,6023,6024,6044,6052,6070,6084,6088,6092,6100,6104,6124,6132,6140,6156,6161,6179,6208,6216,6221,6241,6245],{"__ignoreMap":101},[105,6025,6026,6028,6030,6032,6034,6036,6038,6040,6042],{"class":107,"line":108},[105,6027,5598],{"class":115},[105,6029,3304],{"class":241},[105,6031,321],{"class":115},[105,6033,5930],{"class":248},[105,6035,3312],{"class":115},[105,6037,5610],{"class":122},[105,6039,2854],{"class":115},[105,6041,723],{"class":111},[105,6043,116],{"class":115},[105,6045,6046,6048,6050],{"class":107,"line":119},[105,6047,5621],{"class":115},[105,6049,5947],{"class":241},[105,6051,583],{"class":115},[105,6053,6054,6056,6058,6060,6062,6064,6066,6068],{"class":107,"line":133},[105,6055,5631],{"class":115},[105,6057,5956],{"class":241},[105,6059,5959],{"class":115},[105,6061,5530],{"class":241},[105,6063,321],{"class":115},[105,6065,5966],{"class":122},[105,6067,5150],{"class":111},[105,6069,116],{"class":115},[105,6071,6072,6074,6077,6080,6082],{"class":107,"line":148},[105,6073,5658],{"class":111},[105,6075,6076],{"class":115}," cached ",[105,6078,6079],{"class":111},"||",[105,6081,6000],{"class":241},[105,6083,6003],{"class":115},[105,6085,6086],{"class":107,"line":163},[105,6087,5703],{"class":115},[105,6089,6090],{"class":107,"line":179},[105,6091,5708],{"class":115},[105,6093,6094,6097],{"class":107,"line":194},[105,6095,6096],{"class":115},"}); ",[105,6098,6099],{"class":630},"\u002F\u002F 缓存\n",[105,6101,6102],{"class":107,"line":209},[105,6103,307],{"emptyLinePlaceholder":306},[105,6105,6106,6108,6110,6112,6114,6116,6118,6120,6122],{"class":107,"line":526},[105,6107,5598],{"class":115},[105,6109,3304],{"class":241},[105,6111,321],{"class":115},[105,6113,5930],{"class":248},[105,6115,3312],{"class":115},[105,6117,5610],{"class":122},[105,6119,2854],{"class":115},[105,6121,723],{"class":111},[105,6123,116],{"class":115},[105,6125,6126,6128,6130],{"class":107,"line":531},[105,6127,5621],{"class":115},[105,6129,5947],{"class":241},[105,6131,583],{"class":115},[105,6133,6134,6137],{"class":107,"line":668},[105,6135,6136],{"class":241},"        fetch",[105,6138,6139],{"class":115},"(event.request)\n",[105,6141,6142,6145,6147,6149,6152,6154],{"class":107,"line":673},[105,6143,6144],{"class":115},"            .",[105,6146,5530],{"class":241},[105,6148,321],{"class":115},[105,6150,6151],{"class":122},"response",[105,6153,5150],{"class":111},[105,6155,116],{"class":115},[105,6157,6158],{"class":107,"line":679},[105,6159,6160],{"class":630},"                \u002F\u002F 网络成功，更新缓存\n",[105,6162,6163,6166,6169,6171,6174,6177],{"class":107,"line":689},[105,6164,6165],{"class":111},"                const",[105,6167,6168],{"class":129}," clone",[105,6170,574],{"class":111},[105,6172,6173],{"class":115}," response.",[105,6175,6176],{"class":241},"clone",[105,6178,665],{"class":115},[105,6180,6181,6184,6186,6188,6190,6192,6194,6196,6198,6200,6202,6205],{"class":107,"line":699},[105,6182,6183],{"class":115},"                caches.",[105,6185,5634],{"class":241},[105,6187,321],{"class":115},[105,6189,5639],{"class":248},[105,6191,5642],{"class":115},[105,6193,5530],{"class":241},[105,6195,321],{"class":115},[105,6197,5649],{"class":122},[105,6199,5150],{"class":111},[105,6201,5661],{"class":115},[105,6203,6204],{"class":241},"put",[105,6206,6207],{"class":115},"(event.request, clone));\n",[105,6209,6210,6213],{"class":107,"line":704},[105,6211,6212],{"class":111},"                return",[105,6214,6215],{"class":115}," response;\n",[105,6217,6218],{"class":107,"line":710},[105,6219,6220],{"class":115},"            })\n",[105,6222,6223,6225,6227,6229,6231,6233,6235,6238],{"class":107,"line":1136},[105,6224,6144],{"class":115},[105,6226,5557],{"class":241},[105,6228,4070],{"class":115},[105,6230,723],{"class":111},[105,6232,5829],{"class":115},[105,6234,5956],{"class":241},[105,6236,6237],{"class":115},"(event.request))   ",[105,6239,6240],{"class":630},"\u002F\u002F 网络失败用缓存\n",[105,6242,6243],{"class":107,"line":1149},[105,6244,5708],{"class":115},[105,6246,6247],{"class":107,"line":3855},[105,6248,3982],{"class":115},[18,6250,6251],{},"如果是能够短暂接受旧数据，但是还是想尽快响应的，场景，可以用缓存优先+后台更新：",[96,6253,6255],{"className":559,"code":6254,"language":561,"meta":101,"style":101},"self.addEventListener('fetch', (event) => {\n    event.respondWith(\n        caches.match(event.request).then(cached => {\n            const fetchPromise = fetch(event.request).then(response => {\n                const clone = response.clone();\n                caches.open('v1').then(cache => cache.put(event.request, clone));\n                return response;\n            });\n            return cached || fetchPromise;   \u002F\u002F 立即返回缓存，后台更新\n        })\n    );\n});\n",[24,6256,6257,6277,6285,6303,6327,6341,6367,6373,6378,6392,6396,6400],{"__ignoreMap":101},[105,6258,6259,6261,6263,6265,6267,6269,6271,6273,6275],{"class":107,"line":108},[105,6260,5598],{"class":115},[105,6262,3304],{"class":241},[105,6264,321],{"class":115},[105,6266,5930],{"class":248},[105,6268,3312],{"class":115},[105,6270,5610],{"class":122},[105,6272,2854],{"class":115},[105,6274,723],{"class":111},[105,6276,116],{"class":115},[105,6278,6279,6281,6283],{"class":107,"line":119},[105,6280,5621],{"class":115},[105,6282,5947],{"class":241},[105,6284,583],{"class":115},[105,6286,6287,6289,6291,6293,6295,6297,6299,6301],{"class":107,"line":133},[105,6288,5631],{"class":115},[105,6290,5956],{"class":241},[105,6292,5959],{"class":115},[105,6294,5530],{"class":241},[105,6296,321],{"class":115},[105,6298,5966],{"class":122},[105,6300,5150],{"class":111},[105,6302,116],{"class":115},[105,6304,6305,6308,6311,6313,6315,6317,6319,6321,6323,6325],{"class":107,"line":148},[105,6306,6307],{"class":111},"            const",[105,6309,6310],{"class":129}," fetchPromise",[105,6312,574],{"class":111},[105,6314,6000],{"class":241},[105,6316,5959],{"class":115},[105,6318,5530],{"class":241},[105,6320,321],{"class":115},[105,6322,6151],{"class":122},[105,6324,5150],{"class":111},[105,6326,116],{"class":115},[105,6328,6329,6331,6333,6335,6337,6339],{"class":107,"line":163},[105,6330,6165],{"class":111},[105,6332,6168],{"class":129},[105,6334,574],{"class":111},[105,6336,6173],{"class":115},[105,6338,6176],{"class":241},[105,6340,665],{"class":115},[105,6342,6343,6345,6347,6349,6351,6353,6355,6357,6359,6361,6363,6365],{"class":107,"line":179},[105,6344,6183],{"class":115},[105,6346,5634],{"class":241},[105,6348,321],{"class":115},[105,6350,5639],{"class":248},[105,6352,5642],{"class":115},[105,6354,5530],{"class":241},[105,6356,321],{"class":115},[105,6358,5649],{"class":122},[105,6360,5150],{"class":111},[105,6362,5661],{"class":115},[105,6364,6204],{"class":241},[105,6366,6207],{"class":115},[105,6368,6369,6371],{"class":107,"line":194},[105,6370,6212],{"class":111},[105,6372,6215],{"class":115},[105,6374,6375],{"class":107,"line":209},[105,6376,6377],{"class":115},"            });\n",[105,6379,6380,6382,6384,6386,6389],{"class":107,"line":526},[105,6381,5658],{"class":111},[105,6383,6076],{"class":115},[105,6385,6079],{"class":111},[105,6387,6388],{"class":115}," fetchPromise;   ",[105,6390,6391],{"class":630},"\u002F\u002F 立即返回缓存，后台更新\n",[105,6393,6394],{"class":107,"line":531},[105,6395,5703],{"class":115},[105,6397,6398],{"class":107,"line":668},[105,6399,5708],{"class":115},[105,6401,6402],{"class":107,"line":673},[105,6403,3982],{"class":115},[18,6405,6406],{},"sw里面可以用CacheStorage来管理缓存：",[96,6408,6410],{"className":559,"code":6409,"language":561,"meta":101,"style":101},"\u002F\u002F 打开\u002F创建缓存\nconst cache = await caches.open('v1');\n\n\u002F\u002F 添加\nawait cache.add('\u002Findex.html');\nawait cache.addAll(['\u002Fa.js', '\u002Fb.css']);\nawait cache.put(request, response);   \u002F\u002F 手动放入\n\n\u002F\u002F 查询\nconst response = await cache.match('\u002Findex.html');\nconst responses = await cache.matchAll();\n\n\u002F\u002F 删除\nawait cache.delete('\u002Findex.html');\n\n\u002F\u002F 列出所有缓存\nconst keys = await caches.keys();\n\n\u002F\u002F 删除整个缓存\nawait caches.delete('v1');\n",[24,6411,6412,6417,6438,6442,6447,6462,6484,6498,6502,6507,6528,6546,6550,6555,6569,6573,6578,6595,6599,6604],{"__ignoreMap":101},[105,6413,6414],{"class":107,"line":108},[105,6415,6416],{"class":630},"\u002F\u002F 打开\u002F创建缓存\n",[105,6418,6419,6421,6424,6426,6428,6430,6432,6434,6436],{"class":107,"line":119},[105,6420,568],{"class":111},[105,6422,6423],{"class":129}," cache",[105,6425,574],{"class":111},[105,6427,5428],{"class":111},[105,6429,5829],{"class":115},[105,6431,5634],{"class":241},[105,6433,321],{"class":115},[105,6435,5639],{"class":248},[105,6437,327],{"class":115},[105,6439,6440],{"class":107,"line":133},[105,6441,307],{"emptyLinePlaceholder":306},[105,6443,6444],{"class":107,"line":148},[105,6445,6446],{"class":630},"\u002F\u002F 添加\n",[105,6448,6449,6451,6453,6455,6457,6460],{"class":107,"line":163},[105,6450,5401],{"class":111},[105,6452,5661],{"class":115},[105,6454,5273],{"class":241},[105,6456,321],{"class":115},[105,6458,6459],{"class":248},"'\u002Findex.html'",[105,6461,327],{"class":115},[105,6463,6464,6466,6468,6470,6473,6476,6478,6481],{"class":107,"line":179},[105,6465,5401],{"class":111},[105,6467,5661],{"class":115},[105,6469,5664],{"class":241},[105,6471,6472],{"class":115},"([",[105,6474,6475],{"class":248},"'\u002Fa.js'",[105,6477,286],{"class":115},[105,6479,6480],{"class":248},"'\u002Fb.css'",[105,6482,6483],{"class":115},"]);\n",[105,6485,6486,6488,6490,6492,6495],{"class":107,"line":194},[105,6487,5401],{"class":111},[105,6489,5661],{"class":115},[105,6491,6204],{"class":241},[105,6493,6494],{"class":115},"(request, response);   ",[105,6496,6497],{"class":630},"\u002F\u002F 手动放入\n",[105,6499,6500],{"class":107,"line":209},[105,6501,307],{"emptyLinePlaceholder":306},[105,6503,6504],{"class":107,"line":526},[105,6505,6506],{"class":630},"\u002F\u002F 查询\n",[105,6508,6509,6511,6514,6516,6518,6520,6522,6524,6526],{"class":107,"line":531},[105,6510,568],{"class":111},[105,6512,6513],{"class":129}," response",[105,6515,574],{"class":111},[105,6517,5428],{"class":111},[105,6519,5661],{"class":115},[105,6521,5956],{"class":241},[105,6523,321],{"class":115},[105,6525,6459],{"class":248},[105,6527,327],{"class":115},[105,6529,6530,6532,6535,6537,6539,6541,6544],{"class":107,"line":668},[105,6531,568],{"class":111},[105,6533,6534],{"class":129}," responses",[105,6536,574],{"class":111},[105,6538,5428],{"class":111},[105,6540,5661],{"class":115},[105,6542,6543],{"class":241},"matchAll",[105,6545,665],{"class":115},[105,6547,6548],{"class":107,"line":673},[105,6549,307],{"emptyLinePlaceholder":306},[105,6551,6552],{"class":107,"line":679},[105,6553,6554],{"class":630},"\u002F\u002F 删除\n",[105,6556,6557,6559,6561,6563,6565,6567],{"class":107,"line":689},[105,6558,5401],{"class":111},[105,6560,5661],{"class":115},[105,6562,5291],{"class":241},[105,6564,321],{"class":115},[105,6566,6459],{"class":248},[105,6568,327],{"class":115},[105,6570,6571],{"class":107,"line":699},[105,6572,307],{"emptyLinePlaceholder":306},[105,6574,6575],{"class":107,"line":704},[105,6576,6577],{"class":630},"\u002F\u002F 列出所有缓存\n",[105,6579,6580,6582,6585,6587,6589,6591,6593],{"class":107,"line":710},[105,6581,568],{"class":111},[105,6583,6584],{"class":129}," keys",[105,6586,574],{"class":111},[105,6588,5428],{"class":111},[105,6590,5829],{"class":115},[105,6592,5760],{"class":241},[105,6594,665],{"class":115},[105,6596,6597],{"class":107,"line":1136},[105,6598,307],{"emptyLinePlaceholder":306},[105,6600,6601],{"class":107,"line":1149},[105,6602,6603],{"class":630},"\u002F\u002F 删除整个缓存\n",[105,6605,6606,6608,6610,6612,6614,6616],{"class":107,"line":3855},[105,6607,5401],{"class":111},[105,6609,5829],{"class":115},[105,6611,5291],{"class":241},[105,6613,321],{"class":115},[105,6615,5639],{"class":248},[105,6617,327],{"class":115},[18,6619,6620],{},"sw和页面不能直接通信，需要使用postMessage：",[96,6622,6624],{"className":559,"code":6623,"language":561,"meta":101,"style":101},"\u002F\u002F 主线程\nnavigator.serviceWorker.controller.postMessage({\n    type: 'SKIP_WAITING'\n});\n\nnavigator.serviceWorker.addEventListener('message', (event) => {\n    console.log('收到 SW 消息', event.data);\n});\n",[24,6625,6626,6630,6639,6647,6651,6655,6676,6691],{"__ignoreMap":101},[105,6627,6628],{"class":107,"line":108},[105,6629,5491],{"class":630},[105,6631,6632,6635,6637],{"class":107,"line":119},[105,6633,6634],{"class":115},"navigator.serviceWorker.controller.",[105,6636,3255],{"class":241},[105,6638,3935],{"class":115},[105,6640,6641,6644],{"class":107,"line":133},[105,6642,6643],{"class":115},"    type: ",[105,6645,6646],{"class":248},"'SKIP_WAITING'\n",[105,6648,6649],{"class":107,"line":148},[105,6650,3982],{"class":115},[105,6652,6653],{"class":107,"line":163},[105,6654,307],{"emptyLinePlaceholder":306},[105,6656,6657,6660,6662,6664,6666,6668,6670,6672,6674],{"class":107,"line":179},[105,6658,6659],{"class":115},"navigator.serviceWorker.",[105,6661,3304],{"class":241},[105,6663,321],{"class":115},[105,6665,3309],{"class":248},[105,6667,3312],{"class":115},[105,6669,5610],{"class":122},[105,6671,2854],{"class":115},[105,6673,723],{"class":111},[105,6675,116],{"class":115},[105,6677,6678,6681,6683,6685,6688],{"class":107,"line":194},[105,6679,6680],{"class":115},"    console.",[105,6682,639],{"class":241},[105,6684,321],{"class":115},[105,6686,6687],{"class":248},"'收到 SW 消息'",[105,6689,6690],{"class":115},", event.data);\n",[105,6692,6693],{"class":107,"line":209},[105,6694,3982],{"class":115},[18,6696,6697],{},"这个架构设计其实普遍常见，可以看做后端架构里的Worker，比如说Cloudflare Worker或者 Web Workers、后台任务队列：",[442,6699,6700,6705,6710,6715,6720],{},[448,6701,6702],{},[21,6703,6704],{},"独立执行单元",[448,6706,6707],{},[21,6708,6709],{},"事件驱动",[448,6711,6712],{},[21,6713,6714],{},"无状态或短生命周期",[448,6716,6717],{},[21,6718,6719],{},"通过消息通信",[448,6721,6722,6725,91],{},[21,6723,6724],{},"不直接访问主进程资源",[21,6726,6727],{},"Service Worker 完全符合这个模型",[18,6729,6730,6731,6734,6735,6738,6739,31,6742,6745],{},"提一嘴，",[24,6732,6733],{},"CacheStorage"," 存在",[21,6736,6737],{},"磁盘","上，和 ",[24,6740,6741],{},"IndexedDB",[24,6743,6744],{},"localStorage"," 一样是持久化存储。",[18,6747,6748,6749,6752,6753,6756],{},"在vite项目中，我们可以使用vue专用sw包装的插件vite-plugin-pwa，它通过一个虚拟模块 ",[24,6750,6751],{},"virtual:pwa-register\u002Fvue","，提供了一个专门给 Vue 3 用的 ",[24,6754,6755],{},"useRegisterSW"," 函数：",[96,6758,6760],{"className":2963,"code":6759,"language":2965,"meta":101,"style":101},"\u003Cscript setup lang=\"ts\">\nimport { useRegisterSW } from 'virtual:pwa-register\u002Fvue'\n\nconst {\n  offlineReady, \u002F\u002F 应用准备好离线使用（Ref\u003Cboolean>）\n  needRefresh,  \u002F\u002F 有新版本可用（Ref\u003Cboolean>）\n  updateServiceWorker, \u002F\u002F 更新 SW 并刷新页面（函数）\n} = useRegisterSW({\n  onRegisteredSW(swUrl) {\n    console.log(`SW 已注册: ${swUrl}`)\n  },\n})\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv v-if=\"offlineReady || needRefresh\" class=\"pwa-toast\">\n    \u003Cspan v-if=\"offlineReady\">应用已可离线使用\u003C\u002Fspan>\n    \u003Cspan v-else>有新内容可用，点击刷新\u003C\u002Fspan>\n    \u003Cbutton v-if=\"needRefresh\" @click=\"updateServiceWorker()\">刷新\u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[24,6761,6762,6778,6790,6794,6800,6810,6821,6831,6843,6855,6873,6877,6881,6889,6893,6901,6924,6944,6960,6989,6997],{"__ignoreMap":101},[105,6763,6764,6766,6768,6770,6772,6774,6776],{"class":107,"line":108},[105,6765,234],{"class":115},[105,6767,2974],{"class":237},[105,6769,2977],{"class":241},[105,6771,2980],{"class":241},[105,6773,245],{"class":115},[105,6775,2985],{"class":248},[105,6777,257],{"class":115},[105,6779,6780,6782,6785,6787],{"class":107,"line":119},[105,6781,3903],{"class":111},[105,6783,6784],{"class":115}," { useRegisterSW } ",[105,6786,3909],{"class":111},[105,6788,6789],{"class":248}," 'virtual:pwa-register\u002Fvue'\n",[105,6791,6792],{"class":107,"line":133},[105,6793,307],{"emptyLinePlaceholder":306},[105,6795,6796,6798],{"class":107,"line":148},[105,6797,568],{"class":111},[105,6799,116],{"class":115},[105,6801,6802,6805,6807],{"class":107,"line":163},[105,6803,6804],{"class":129},"  offlineReady",[105,6806,286],{"class":115},[105,6808,6809],{"class":630},"\u002F\u002F 应用准备好离线使用（Ref\u003Cboolean>）\n",[105,6811,6812,6815,6818],{"class":107,"line":179},[105,6813,6814],{"class":129},"  needRefresh",[105,6816,6817],{"class":115},",  ",[105,6819,6820],{"class":630},"\u002F\u002F 有新版本可用（Ref\u003Cboolean>）\n",[105,6822,6823,6826,6828],{"class":107,"line":194},[105,6824,6825],{"class":129},"  updateServiceWorker",[105,6827,286],{"class":115},[105,6829,6830],{"class":630},"\u002F\u002F 更新 SW 并刷新页面（函数）\n",[105,6832,6833,6836,6838,6841],{"class":107,"line":209},[105,6834,6835],{"class":115},"} ",[105,6837,245],{"class":111},[105,6839,6840],{"class":241}," useRegisterSW",[105,6842,3935],{"class":115},[105,6844,6845,6848,6850,6853],{"class":107,"line":526},[105,6846,6847],{"class":241},"  onRegisteredSW",[105,6849,321],{"class":115},[105,6851,6852],{"class":122},"swUrl",[105,6854,1000],{"class":115},[105,6856,6857,6859,6861,6863,6866,6868,6871],{"class":107,"line":531},[105,6858,6680],{"class":115},[105,6860,639],{"class":241},[105,6862,321],{"class":115},[105,6864,6865],{"class":248},"`SW 已注册: ${",[105,6867,6852],{"class":115},[105,6869,6870],{"class":248},"}`",[105,6872,1529],{"class":115},[105,6874,6875],{"class":107,"line":668},[105,6876,2872],{"class":115},[105,6878,6879],{"class":107,"line":673},[105,6880,2906],{"class":115},[105,6882,6883,6885,6887],{"class":107,"line":679},[105,6884,3010],{"class":115},[105,6886,2974],{"class":237},[105,6888,257],{"class":115},[105,6890,6891],{"class":107,"line":689},[105,6892,307],{"emptyLinePlaceholder":306},[105,6894,6895,6897,6899],{"class":107,"line":699},[105,6896,234],{"class":115},[105,6898,3676],{"class":237},[105,6900,257],{"class":115},[105,6902,6903,6905,6907,6910,6912,6915,6917,6919,6922],{"class":107,"line":704},[105,6904,3683],{"class":115},[105,6906,238],{"class":237},[105,6908,6909],{"class":241}," v-if",[105,6911,245],{"class":115},[105,6913,6914],{"class":248},"\"offlineReady || needRefresh\"",[105,6916,3736],{"class":241},[105,6918,245],{"class":115},[105,6920,6921],{"class":248},"\"pwa-toast\"",[105,6923,257],{"class":115},[105,6925,6926,6928,6930,6932,6934,6937,6940,6942],{"class":107,"line":710},[105,6927,3731],{"class":115},[105,6929,105],{"class":237},[105,6931,6909],{"class":241},[105,6933,245],{"class":115},[105,6935,6936],{"class":248},"\"offlineReady\"",[105,6938,6939],{"class":115},">应用已可离线使用\u003C\u002F",[105,6941,105],{"class":237},[105,6943,257],{"class":115},[105,6945,6946,6948,6950,6953,6956,6958],{"class":107,"line":1136},[105,6947,3731],{"class":115},[105,6949,105],{"class":237},[105,6951,6952],{"class":241}," v-else",[105,6954,6955],{"class":115},">有新内容可用，点击刷新\u003C\u002F",[105,6957,105],{"class":237},[105,6959,257],{"class":115},[105,6961,6962,6964,6967,6969,6971,6974,6977,6979,6982,6985,6987],{"class":107,"line":1149},[105,6963,3731],{"class":115},[105,6965,6966],{"class":237},"button",[105,6968,6909],{"class":241},[105,6970,245],{"class":115},[105,6972,6973],{"class":248},"\"needRefresh\"",[105,6975,6976],{"class":241}," @click",[105,6978,245],{"class":115},[105,6980,6981],{"class":248},"\"updateServiceWorker()\"",[105,6983,6984],{"class":115},">刷新\u003C\u002F",[105,6986,6966],{"class":237},[105,6988,257],{"class":115},[105,6990,6991,6993,6995],{"class":107,"line":3855},[105,6992,3868],{"class":115},[105,6994,238],{"class":237},[105,6996,257],{"class":115},[105,6998,6999,7001,7003],{"class":107,"line":3865},[105,7000,3010],{"class":115},[105,7002,3676],{"class":237},[105,7004,257],{"class":115},[14,7006,7008],{"id":7007},"web-worker","Web Worker",[18,7010,7011],{},"刚才提到的service worker常用于网络代理层，web worker常用于后台计算线程，能够把耗时计算移出主线程，生命周期是随着页面的创建和销毁。这个是html5提供的一个api，允许我们在后台线程运行js代码，从而避免主线程，它是浏览器实现多线程的标准方式。",[18,7013,7014,7015,7018],{},"因为Js是单线程，主线程会负责执行js代码，处理dom渲染，响应用户交互。如果主线程执行一个耗时任务比如大量计算、处理大数组，页面就会卡死，用户点击没反应、动画掉帧。Web Worker 把耗时任务放到",[21,7016,7017],{},"独立线程","，主线程继续响应用户，两者互不阻塞。",[96,7020,7022],{"className":559,"code":7021,"language":561,"meta":101,"style":101},"\u002F\u002F 1. 创建 Worker\nconst worker = new Worker('worker.js');\n\n\u002F\u002F 2. 发送数据给 Worker\nworker.postMessage({ type: 'compute', data: [1, 2, 3, 4, 5] });\n\n\u002F\u002F 3. 接收 Worker 的结果\nworker.onmessage = (e) => {\n    console.log('结果:', e.data);\n};\n\n\u002F\u002F 4. 监听错误\nworker.onerror = (err) => {\n    console.error('Worker 错误:', err.message);\n};\n\n\u002F\u002F 5. 终止 Worker\n\u002F\u002F worker.terminate();\n\n\u002F\u002F worker.js\n\u002F\u002F 接收主线程的消息\nself.onmessage = (e) => {\n    const { type, data } = e.data;\n\n    if (type === 'compute') {\n        \u002F\u002F 执行耗时计算\n        const result = data.reduce((sum, n) => sum + n * n, 0);\n\n        \u002F\u002F 把结果发回主线程\n        self.postMessage({ type: 'result', value: result });\n    }\n};\n",[24,7023,7024,7029,7050,7054,7059,7096,7100,7105,7123,7137,7141,7145,7150,7169,7184,7188,7192,7197,7202,7206,7211,7216,7234,7255,7259,7275,7280,7328,7332,7337,7352,7356],{"__ignoreMap":101},[105,7025,7026],{"class":107,"line":108},[105,7027,7028],{"class":630},"\u002F\u002F 1. 创建 Worker\n",[105,7030,7031,7033,7036,7038,7040,7043,7045,7048],{"class":107,"line":119},[105,7032,568],{"class":111},[105,7034,7035],{"class":129}," worker",[105,7037,574],{"class":111},[105,7039,3496],{"class":111},[105,7041,7042],{"class":241}," Worker",[105,7044,321],{"class":115},[105,7046,7047],{"class":248},"'worker.js'",[105,7049,327],{"class":115},[105,7051,7052],{"class":107,"line":133},[105,7053,307],{"emptyLinePlaceholder":306},[105,7055,7056],{"class":107,"line":148},[105,7057,7058],{"class":630},"\u002F\u002F 2. 发送数据给 Worker\n",[105,7060,7061,7064,7066,7069,7072,7075,7077,7079,7081,7083,7085,7087,7089,7091,7093],{"class":107,"line":163},[105,7062,7063],{"class":115},"worker.",[105,7065,3255],{"class":241},[105,7067,7068],{"class":115},"({ type: ",[105,7070,7071],{"class":248},"'compute'",[105,7073,7074],{"class":115},", data: [",[105,7076,861],{"class":129},[105,7078,286],{"class":115},[105,7080,876],{"class":129},[105,7082,286],{"class":115},[105,7084,891],{"class":129},[105,7086,286],{"class":115},[105,7088,911],{"class":129},[105,7090,286],{"class":115},[105,7092,928],{"class":129},[105,7094,7095],{"class":115},"] });\n",[105,7097,7098],{"class":107,"line":179},[105,7099,307],{"emptyLinePlaceholder":306},[105,7101,7102],{"class":107,"line":194},[105,7103,7104],{"class":630},"\u002F\u002F 3. 接收 Worker 的结果\n",[105,7106,7107,7109,7111,7113,7115,7117,7119,7121],{"class":107,"line":209},[105,7108,7063],{"class":115},[105,7110,5122],{"class":241},[105,7112,574],{"class":111},[105,7114,4379],{"class":115},[105,7116,3315],{"class":122},[105,7118,2854],{"class":115},[105,7120,723],{"class":111},[105,7122,116],{"class":115},[105,7124,7125,7127,7129,7131,7134],{"class":107,"line":526},[105,7126,6680],{"class":115},[105,7128,639],{"class":241},[105,7130,321],{"class":115},[105,7132,7133],{"class":248},"'结果:'",[105,7135,7136],{"class":115},", e.data);\n",[105,7138,7139],{"class":107,"line":531},[105,7140,4459],{"class":115},[105,7142,7143],{"class":107,"line":668},[105,7144,307],{"emptyLinePlaceholder":306},[105,7146,7147],{"class":107,"line":673},[105,7148,7149],{"class":630},"\u002F\u002F 4. 监听错误\n",[105,7151,7152,7154,7157,7159,7161,7163,7165,7167],{"class":107,"line":679},[105,7153,7063],{"class":115},[105,7155,7156],{"class":241},"onerror",[105,7158,574],{"class":111},[105,7160,4379],{"class":115},[105,7162,5562],{"class":122},[105,7164,2854],{"class":115},[105,7166,723],{"class":111},[105,7168,116],{"class":115},[105,7170,7171,7173,7176,7178,7181],{"class":107,"line":689},[105,7172,6680],{"class":115},[105,7174,7175],{"class":241},"error",[105,7177,321],{"class":115},[105,7179,7180],{"class":248},"'Worker 错误:'",[105,7182,7183],{"class":115},", err.message);\n",[105,7185,7186],{"class":107,"line":699},[105,7187,4459],{"class":115},[105,7189,7190],{"class":107,"line":704},[105,7191,307],{"emptyLinePlaceholder":306},[105,7193,7194],{"class":107,"line":710},[105,7195,7196],{"class":630},"\u002F\u002F 5. 终止 Worker\n",[105,7198,7199],{"class":107,"line":1136},[105,7200,7201],{"class":630},"\u002F\u002F worker.terminate();\n",[105,7203,7204],{"class":107,"line":1149},[105,7205,307],{"emptyLinePlaceholder":306},[105,7207,7208],{"class":107,"line":3855},[105,7209,7210],{"class":630},"\u002F\u002F worker.js\n",[105,7212,7213],{"class":107,"line":3865},[105,7214,7215],{"class":630},"\u002F\u002F 接收主线程的消息\n",[105,7217,7218,7220,7222,7224,7226,7228,7230,7232],{"class":107,"line":3875},[105,7219,5598],{"class":115},[105,7221,5122],{"class":241},[105,7223,574],{"class":111},[105,7225,4379],{"class":115},[105,7227,3315],{"class":122},[105,7229,2854],{"class":115},[105,7231,723],{"class":111},[105,7233,116],{"class":115},[105,7235,7236,7239,7241,7244,7246,7248,7250,7252],{"class":107,"line":3884},[105,7237,7238],{"class":111},"    const",[105,7240,726],{"class":115},[105,7242,7243],{"class":129},"type",[105,7245,286],{"class":115},[105,7247,5070],{"class":129},[105,7249,3973],{"class":115},[105,7251,245],{"class":111},[105,7253,7254],{"class":115}," e.data;\n",[105,7256,7257],{"class":107,"line":3889},[105,7258,307],{"emptyLinePlaceholder":306},[105,7260,7261,7264,7267,7270,7273],{"class":107,"line":3900},[105,7262,7263],{"class":111},"    if",[105,7265,7266],{"class":115}," (type ",[105,7268,7269],{"class":111},"===",[105,7271,7272],{"class":248}," 'compute'",[105,7274,1000],{"class":115},[105,7276,7277],{"class":107,"line":3917},[105,7278,7279],{"class":630},"        \u002F\u002F 执行耗时计算\n",[105,7281,7282,7285,7288,7290,7293,7296,7299,7302,7304,7307,7309,7311,7314,7316,7319,7321,7324,7326],{"class":107,"line":3922},[105,7283,7284],{"class":111},"        const",[105,7286,7287],{"class":129}," result",[105,7289,574],{"class":111},[105,7291,7292],{"class":115}," data.",[105,7294,7295],{"class":241},"reduce",[105,7297,7298],{"class":115},"((",[105,7300,7301],{"class":122},"sum",[105,7303,286],{"class":115},[105,7305,7306],{"class":122},"n",[105,7308,2854],{"class":115},[105,7310,723],{"class":111},[105,7312,7313],{"class":115}," sum ",[105,7315,1085],{"class":111},[105,7317,7318],{"class":115}," n ",[105,7320,938],{"class":111},[105,7322,7323],{"class":115}," n, ",[105,7325,1100],{"class":129},[105,7327,327],{"class":115},[105,7329,7330],{"class":107,"line":3938},[105,7331,307],{"emptyLinePlaceholder":306},[105,7333,7334],{"class":107,"line":3954},[105,7335,7336],{"class":630},"        \u002F\u002F 把结果发回主线程\n",[105,7338,7339,7342,7344,7346,7349],{"class":107,"line":3965},[105,7340,7341],{"class":115},"        self.",[105,7343,3255],{"class":241},[105,7345,7068],{"class":115},[105,7347,7348],{"class":248},"'result'",[105,7350,7351],{"class":115},", value: result });\n",[105,7353,7354],{"class":107,"line":3979},[105,7355,5161],{"class":115},[105,7357,7358],{"class":107,"line":3985},[105,7359,4459],{"class":115},[18,7361,7362,7364],{},[24,7363,3255],{}," 支持两种传输方式，分别是结构化克隆、转移所有权。默认是结构化克隆：",[96,7366,7368],{"className":559,"code":7367,"language":561,"meta":101,"style":101},"worker.postMessage({\n    num: 1,\n    str: 'hello',\n    arr: [1, 2, 3],\n    obj: { a: 1 },\n    map: new Map([['k', 'v']]),\n    set: new Set([1, 2]),\n    date: new Date(),\n    buffer: new ArrayBuffer(8)\n});\n",[24,7369,7370,7378,7387,7396,7414,7423,7448,7468,7481,7498],{"__ignoreMap":101},[105,7371,7372,7374,7376],{"class":107,"line":108},[105,7373,7063],{"class":115},[105,7375,3255],{"class":241},[105,7377,3935],{"class":115},[105,7379,7380,7383,7385],{"class":107,"line":119},[105,7381,7382],{"class":115},"    num: ",[105,7384,861],{"class":129},[105,7386,2867],{"class":115},[105,7388,7389,7392,7394],{"class":107,"line":133},[105,7390,7391],{"class":115},"    str: ",[105,7393,3280],{"class":248},[105,7395,2867],{"class":115},[105,7397,7398,7401,7403,7405,7407,7409,7411],{"class":107,"line":148},[105,7399,7400],{"class":115},"    arr: [",[105,7402,861],{"class":129},[105,7404,286],{"class":115},[105,7406,876],{"class":129},[105,7408,286],{"class":115},[105,7410,891],{"class":129},[105,7412,7413],{"class":115},"],\n",[105,7415,7416,7419,7421],{"class":107,"line":163},[105,7417,7418],{"class":115},"    obj: { a: ",[105,7420,861],{"class":129},[105,7422,2897],{"class":115},[105,7424,7425,7428,7431,7434,7437,7440,7442,7445],{"class":107,"line":179},[105,7426,7427],{"class":115},"    map: ",[105,7429,7430],{"class":111},"new",[105,7432,7433],{"class":241}," Map",[105,7435,7436],{"class":115},"([[",[105,7438,7439],{"class":248},"'k'",[105,7441,286],{"class":115},[105,7443,7444],{"class":248},"'v'",[105,7446,7447],{"class":115},"]]),\n",[105,7449,7450,7453,7455,7457,7459,7461,7463,7465],{"class":107,"line":194},[105,7451,7452],{"class":115},"    set: ",[105,7454,7430],{"class":111},[105,7456,5064],{"class":241},[105,7458,6472],{"class":115},[105,7460,861],{"class":129},[105,7462,286],{"class":115},[105,7464,876],{"class":129},[105,7466,7467],{"class":115},"]),\n",[105,7469,7470,7473,7475,7478],{"class":107,"line":209},[105,7471,7472],{"class":115},"    date: ",[105,7474,7430],{"class":111},[105,7476,7477],{"class":241}," Date",[105,7479,7480],{"class":115},"(),\n",[105,7482,7483,7486,7488,7491,7493,7496],{"class":107,"line":526},[105,7484,7485],{"class":115},"    buffer: ",[105,7487,7430],{"class":111},[105,7489,7490],{"class":241}," ArrayBuffer",[105,7492,321],{"class":115},[105,7494,7495],{"class":129},"8",[105,7497,1529],{"class":115},[105,7499,7500],{"class":107,"line":531},[105,7501,3982],{"class":115},[18,7503,7504],{},"不支持dom节点、error对象（部分）、函数。",[18,7506,7507],{},"转移所有权有点像c++的移动move概念。对于大块数据比如ArrayBuffer，可以用转移替代复制，实现零拷贝：",[96,7509,7511],{"className":559,"code":7510,"language":561,"meta":101,"style":101},"const buffer = new ArrayBuffer(1024 * 1024 * 100); \u002F\u002F 100MB\n\n\u002F\u002F 转移所有权，主线程不再拥有该 buffer\nworker.postMessage({ buffer }, [buffer]);\n\n\u002F\u002F 此时 buffer 在主线程已不可用\nconsole.log(buffer.byteLength); \u002F\u002F 0\n",[24,7512,7513,7546,7550,7555,7564,7568,7573],{"__ignoreMap":101},[105,7514,7515,7517,7520,7522,7524,7526,7528,7530,7532,7535,7537,7540,7543],{"class":107,"line":108},[105,7516,568],{"class":111},[105,7518,7519],{"class":129}," buffer",[105,7521,574],{"class":111},[105,7523,3496],{"class":111},[105,7525,7490],{"class":241},[105,7527,321],{"class":115},[105,7529,171],{"class":129},[105,7531,4081],{"class":111},[105,7533,7534],{"class":129}," 1024",[105,7536,4081],{"class":111},[105,7538,7539],{"class":129}," 100",[105,7541,7542],{"class":115},"); ",[105,7544,7545],{"class":630},"\u002F\u002F 100MB\n",[105,7547,7548],{"class":107,"line":119},[105,7549,307],{"emptyLinePlaceholder":306},[105,7551,7552],{"class":107,"line":133},[105,7553,7554],{"class":630},"\u002F\u002F 转移所有权，主线程不再拥有该 buffer\n",[105,7556,7557,7559,7561],{"class":107,"line":148},[105,7558,7063],{"class":115},[105,7560,3255],{"class":241},[105,7562,7563],{"class":115},"({ buffer }, [buffer]);\n",[105,7565,7566],{"class":107,"line":163},[105,7567,307],{"emptyLinePlaceholder":306},[105,7569,7570],{"class":107,"line":179},[105,7571,7572],{"class":630},"\u002F\u002F 此时 buffer 在主线程已不可用\n",[105,7574,7575,7577,7579,7582],{"class":107,"line":194},[105,7576,636],{"class":115},[105,7578,639],{"class":241},[105,7580,7581],{"class":115},"(buffer.byteLength); ",[105,7583,7584],{"class":630},"\u002F\u002F 0\n",[18,7586,7587],{},"处理大数组、图像数据、音视频时，转移比复制快得多。典型的使用场景就有wasm，我们一般就是在worker中运行wasm模块的。可以用于图像处理、像素操作、canvas离屏渲染。还能用于轮询和长连接，后台定时请求、websocket心跳，这也是为了主线程不会去做大json解析、二进制处理处理占用主线程。",[14,7589,6741],{"id":7590},"indexeddb",[18,7592,7593],{},"这个是浏览器的嵌入式数据库，用kv存储，支持索引、事务、游标。能存储任意结构化数据，localStorage只能存储5-10MB，但是这个可以存储上GB的信息量。一个indexedDB可以创建多个数据库：",[96,7595,7598],{"className":7596,"code":7597,"language":392},[390],"const request = indexedDB.open('myDB', 1);   \u002F\u002F 名字 + 版本号\n",[24,7599,7597],{"__ignoreMap":101},[18,7601,7602],{},"他可以使用对象仓库，类似于表：",[96,7604,7607],{"className":7605,"code":7606,"language":392},[390],"db.createObjectStore('users', { keyPath: 'id' });\n",[24,7608,7606],{"__ignoreMap":101},[18,7610,7611],{},"可以使用索引加速查询：",[96,7613,7616],{"className":7614,"code":7615,"language":392},[390],"store.createIndex('email', 'email', { unique: true });\nstore.createIndex('age', 'age');\nstore.createIndex('name_age', ['name', 'age']);   \u002F\u002F 复合索引\n",[24,7617,7615],{"__ignoreMap":101},[18,7619,7620],{},"支持事务，能确保原子性和自动提交，出错能自动回滚：",[96,7622,7625],{"className":7623,"code":7624,"language":392},[390],"const tx = db.transaction('users', 'readwrite');\nconst store = tx.objectStore('users');\nstore.add({ id: 1, name: 'Alice' });\ntx.oncomplete = () => console.log('提交成功');\ntx.onerror = () => console.log('失败');\n",[24,7626,7624],{"__ignoreMap":101},[14,7628,7630],{"id":7629},"ajax技术","Ajax技术",[18,7632,7633],{},"Ajax是一组技术的组合，用于在不刷新页面的前提下用js异步向服务器请求数据并且更新页面的方法。",[18,7635,7636],{},"我们已知，HTML\u002FCSS用于展示内容，DOM动态更新页面，JS用于逻辑控制，那么XMLHttpRequest可以用于一步请求，XML\u002FJSON可以用于数据展示。",[18,7638,7639],{},"我们都知道现代ajax技术经过了发展从XMLHttpRequest变成了fetch，又变成了axios。XMLHttpRequest是1999发明，es6之后，发布了fetch。",[14,7641,7643],{"id":7642},"pwa","PWA",[18,7645,7646,7649],{},[21,7647,7648],{},"Progressive Web App"," 是一组能力的集合，核心是Service Worker + Web App Manifest。他可以做到离线可用，断网也可以打开，可安装，桌面\u002F主屏有图标，独立窗口。可以推送通知，可以后台同步。",[18,7651,7652],{},"service worker之前讲过，是一个跑在浏览器后台独立线程的脚本，可以拦截页面的网络请求、缓存资源、离线响应。",[14,7654,7655],{"id":7655},"浏览器缓存技术",[18,7657,7658],{},"浏览器本身给我们提供了一些缓存技术，最基础的缓存是HTTP响应头机制，通过强缓存可以将带内容哈希的静态资源比如app.a1b2c3.js缓存。",[18,7660,7661],{},"Cache-Control: 这个头我们在缓存里最常用，可以设置max-age= 单位s来以高优先级设置缓存资源，也可以用immutable设置资源永不变，刷新也不请求。头部可以设置Expires来设置绝对时间过期，优先级比较低。",[18,7663,7664],{},"Cache-Control: public, max-age=31536000, immutable 比如这样。",[18,7666,7667],{},"浏览器可以发送请求询问服务器是否可用，这个叫做协商缓存：",[18,7669,7670,1885,7673,7676,7677,1885,7680,7683],{},[24,7671,7672],{},"ETag",[24,7674,7675],{},"If-None-Match"," 可以基于内容哈希，精度比较高，",[24,7678,7679],{},"Last-Modified",[24,7681,7682],{},"If-Modified-Since","基于时间戳，精度比较低。响应为304 Not Modified，不传响应体，比较节省带宽。 ETag是服务器为了某个资源生成的唯一标识符，通常基于文件内容计算比如哈希值，这样客户端在下次请求的时候就会通知服务器本地存有这个版本，并判断是否资源最新的。",[18,7685,7686],{},"这是协商的完整过程：",[96,7688,7691],{"className":7689,"code":7690,"language":392},[390],"第一次请求：\n  客户端 ──GET \u002Findex.html──→ 服务器\n  客户端 ←──200 OK────────── 服务器\n              ETag: \"abc123\"\n              Cache-Control: no-cache\n\u002F\u002F - `no-cache` 表示**每次都要向服务器确认**，不能直接用本地缓存。\n\n第二次请求：\n  客户端 ──GET \u002Findex.html──→ 服务器\n              If-None-Match: \"abc123\"\n  客户端 ←──304 Not Modified── 服务器\n              （无响应体）\n\u002F\u002F 如果没变，返回 `304`，**不传响应体**，节省带宽\n\n如果内容变了：\n  客户端 ←──200 OK────────── 服务器\n              ETag: \"def456\"\n              （返回新内容）\n",[24,7692,7690],{"__ignoreMap":101},[18,7694,7695],{},"适用于HTML文件、API响应等需要及时更新的资源：",[96,7697,7700],{"className":7698,"code":7699,"language":392},[390],"Cache-Control: no-cache\nETag: \"abc123\"\n",[24,7701,7699],{"__ignoreMap":101},[18,7703,7704,7705,7708,7709,7712],{},"vary: Origin表示响应内容依赖请求的Origin头，如果同一个URL被不同 ",[24,7706,7707],{},"Origin"," 请求，浏览器会分别缓存。这在开发环境下常见，因为 Vite 的 dev server 需要处理 CORS。生产环境下，如果 CDN 配置不当，",[24,7710,7711],{},"vary: Origin"," 可能导致缓存命中率下降。",[18,7714,7715],{},"缓存策略组合：",[18,7717,7718],{},"HTML这种资源推荐使用no-cache+ ETag每次协商，js\u002Fcss推荐用max-age和immutable进行缓存。图片和字体也可以通过max-age+ETag进行缓存。API响应可以通过no-store或者短max-age + ETag进行缓存。",[18,7720,7721],{},"内存缓存是浏览器自己自动管理的，同一页面重复请求同一资源的时候就会直接从内存读取，关闭标签页就会失效。浏览器在收到一个资源的时候，会根据响应头决定存储在哪里。内存缓存的存储位置是RAM，而非DISK，读取速度很快，像js css 图片资源等高频使用的资源可以存，生命周期是当前标签页会话，关闭就失效。磁盘缓存的位置是硬盘，生命周期是由Cache-Control \u002F Expires 决定，读取速度较快，像大文件，不常变动的资源可以缓存。service worker缓存存储的位置是Cache API，独立于HTTP缓存，需要手动控制生命周期，像PWA、离线资源可以存。",[96,7723,7726],{"className":7724,"code":7725,"language":392},[390],"1. 内存缓存命中？\n   ├─ 是 → 直接用（from memory cache）\n   └─ 否 → 下一步\n\n2. Service Worker 缓存命中？\n   ├─ 是 → 用 SW 缓存\n   └─ 否 → 下一步\n\n3. 磁盘缓存命中？\n   ├─ 强缓存有效？\n   │   ├─ 是 → 直接用（from disk cache）\n   │   └─ 否 → 协商缓存（发请求带 If-None-Match）\n   │              ├─ 304 → 用本地\n   │              └─ 200 → 下载新资源\n   └─ 否 → 发请求下载\n",[24,7727,7725],{"__ignoreMap":101},[18,7729,7730],{},"![[Pasted image 20260930221146.png]]",[18,7732,7733],{},"![[Pasted image 20260930221200.png]]",[18,7735,7736,7737,91],{},"public, max-age=2592000, immutable 会让我们强缓存30天永不变，cf-cache-status: HIT表示这是Cloudflare CDN边缘节点已缓存。etag是协商的缓存标识，last-modified是最后的修改时间。这个组合会让浏览器把图片存入磁盘缓存，下次请求同一URL时候浏览器直接读磁盘，不发网络请求，状态码会显示 ",[24,7738,7739],{},"200 (from disk cache)",[14,7741,7742],{"id":7742},"标签页和v8",[18,7744,7745],{},"每个标签页其实不会分配一个独立的v8实例，v8实例是绑定在渲染进程RendererProcess上的，而不是直接绑定在标签页上。正确的关系是，一个v8实例可能服务多个标签页和iframe。在早期，确实一个标签页一个进程，现在策略已经变了，如果我们打开两个标签都访问同一个网站比如example.com，chrome可能让它们共享一个渲染进程，也就是共享一个v8实例，用来节省内存。如果网站嵌入了跨站iframe，chrome会为了跨站ifame单独创建一个渲染进程也就是OOPIF，跨进程iframe，一个标签页内部可能对于好几个渲染进程，有好几个v8实例。",[18,7747,7748],{},"可以查看chromium.org内看文档：离线处理架构OOPIFs：",[96,7750,7753],{"className":7751,"code":7752,"language":392},[390],"这种技术允许页面的子框架由与父框架不同的进程来渲染。采用独立进程内嵌框架的做法主要是出于安全考虑，比如 Site Isolation 项目所提出的解决方案——通过这种方式，即使存在跨域内嵌框架，也能让某个渲染进程专注于处理某个特定的网站内容。不过，独立进程内嵌框架也是一种通用的机制，其应用范围不仅限于安全相关场景，还可以用于其他功能，例如 Chrome 应用程序中的\u003Cwebview>标签。\n",[24,7754,7752],{"__ignoreMap":101},[18,7756,7757,7758,7761,7762,91,7765,7768,7769,7772,7773,7775,7776,7779],{},"OOPIF 要求 Chromium 的架构从“以标签页为中心”转向“",[21,7759,7760],{},"以 frame 为中心","”，因为一个 frame 在其生命周期内可能被渲染到不同进程中。浏览器进程现在直接追踪",[21,7763,7764],{},"完整的 frame 树",[24,7766,7767],{},"WebContents"," 持有一个 ",[24,7770,7771],{},"FrameTreeNode"," 树，镜像当前页面的 frame 结构。每个 ",[24,7774,7771],{}," 包含该 frame 的元信息（名称、origin 等），其 ",[24,7777,7778],{},"RenderFrameHostManager"," 负责处理该 frame 的跨进程导航，并支持状态复制和消息路由",[96,7781,7784],{"className":7782,"code":7783,"language":392},[390],"OOPIF 的渲染面临一个核心安全问题：**能合成一个纹理，就意味着能读取它的像素**（Cross Site Texture Stealing）。因此 OOPIF 的完整安全优势依赖于 **Übercompositor** 架构[](https:\u002F\u002Fwww.chromium.org\u002Fdevelopers\u002Fdesign-documents\u002Foop-iframes\u002Foop-iframes-rendering\u002F)。\n\n在 Übercompositor 下：\n\n- 每个渲染进程的子合成器生成 **CompositorFrame**，包含 quads 和纹理引用。\n    \n- 渲染进程通过 **IPC 将 CompositorFrame 发给浏览器进程**，而不是直接发给 GPU。\n    \n- 浏览器进程的父合成器与 GPU 进程交互，完成最终合成[](https:\u002F\u002Fwww.chromium.org\u002Fdevelopers\u002Fdesign-documents\u002Foop-iframes\u002Foop-iframes-rendering\u002F)。\n    \n\n纹理在不同进程间通过 **TextureMailbox** 传递所有权：一个进程用 `glProduceTextureCHROMIUM` 生成纹理，另一个进程用 `glConsumeTextureCHROMIUM` 消费它[](https:\u002F\u002Fwww.chromium.org\u002Fdevelopers\u002Fdesign-documents\u002Foop-iframes\u002Foop-iframes-rendering\u002F)。\n",[24,7785,7783],{"__ignoreMap":101},[18,7787,7788,7789,7792,7793,7796],{},"这地方了解着玩玩就行，不用急太深，quad是合成器提交给gpu的最小绘制单元，可以理解为一个带纹理矩形，描述了哪块的纹理，用什么变换，绘制到屏幕哪个位置。渲染进程的合成器不直接操作 GPU，而是把一帧画面",[21,7790,7791],{},"拆解成一组 quad","，打包成 ",[24,7794,7795],{},"CompositorFrame","，通过 IPC 发给浏览器进程或 GPU 进程。GPU 再按顺序把这些 quad 绘制出来，合成最终画面。",[18,7798,7799],{},"再深入挖掘就是架构图形栈知识了，不讲不讲...",[14,7801,7802],{"id":7802},"viz进程架构",[18,7804,7805],{},"viz是chromium渲染管线现代化的核心，主要为了解决GPU进程职责过重，站点隔离下的性能和安全瓶颈，以及图形栈的服务化需求。",[18,7807,7808],{},"在 Spectre 等侧信道攻击的背景下，跨站 iframe 需要严格的进程隔离（OOPIF）。VIZ将核心的合成和光栅化集中到独立进程中，使得来自不同渲染进程的合成器帧可以在一个安全的、收信任环境下统一聚合，从而支持“无额外性能代价的站点隔离”。Viz 是 Chromium 向 Mojo 服务化架构转型的一部分。它将合成、GL、命中测试、媒体等能力封装为标准服务，通过 Mojo 接口对外提供，使得 Browser 进程不再是唯一的核心枢纽，架构上更清晰、可维护性更强。",[18,7810,7811],{},"skia是chromium的底层2D图形库，负责我们几乎我们所有看到的像素绘制，从文字到图形、形状、路径、渐变、阴影、图层混合等。",[96,7813,7816],{"className":7814,"code":7815,"language":392},[390],"Chromium 图形栈（从上层到下层）：\n\n┌─────────────────────────────────┐\n│  Blink（渲染引擎）              │  ← HTML\u002FCSS 解析、布局、绘制指令\n├─────────────────────────────────┤\n│  cc \u002F viz（合成器）             │  ← 图层合成、光栅化调度、GPU 提交\n├─────────────────────────────────┤\n│  Skia                           │  ← 2D 绘制：路径、文字、图片、混合\n├─────────────────────────────────┤\n│  GPU 驱动 \u002F Vulkan \u002F Metal \u002F GL │  ← 硬件加速\n└─────────────────────────────────┘\n",[24,7817,7815],{"__ignoreMap":101},[10,7819,7821],{"id":7820},"javascript","JavaScript",[14,7823,7825],{"id":7824},"_01-02-03-嘛为什么","0.1 + 0.2 === 0.3 嘛？为什么？",[18,7827,7828,7831,7832,7835,7836,7839,7840,7843,7844,7847,7848,31,7851,31,7854,7857,7858,7861],{},[21,7829,7830],{},"不等于。"," ",[24,7833,7834],{},"0.1 + 0.2 === 0.3"," 返回 ",[24,7837,7838],{},"false","，实际结果是 ",[24,7841,7842],{},"0.30000000000000004","，因为 ",[21,7845,7846],{},"JavaScript 的数字用 IEEE 754 双精度浮点数表示","，而 ",[24,7849,7850],{},"0.1",[24,7852,7853],{},"0.2",[24,7855,7856],{},"0.3"," 这些十进制小数，在二进制里都是",[21,7859,7860],{},"无限循环小数","，无法精确表示。",[18,7863,7864,7865,7867],{},"把十进制的 ",[24,7866,7850],{}," 转成二进制：",[96,7869,7871],{"className":559,"code":7870,"language":561,"meta":101,"style":101},"0.1 × 2 = 0.2    → 0\n0.2 × 2 = 0.4    → 0\n0.4 × 2 = 0.8    → 0\n0.8 × 2 = 1.6    → 1\n0.6 × 2 = 1.2    → 1\n0.2 × 2 = 0.4    → 0  ← 回到 0.2，开始循环\n...\n",[24,7872,7873,7893,7910,7928,7947,7965,7989],{"__ignoreMap":101},[105,7874,7875,7877,7880,7882,7884,7887,7890],{"class":107,"line":108},[105,7876,7850],{"class":129},[105,7878,7879],{"class":115}," × ",[105,7881,876],{"class":129},[105,7883,574],{"class":111},[105,7885,7886],{"class":129}," 0.2",[105,7888,7889],{"class":115},"    → ",[105,7891,7892],{"class":129},"0\n",[105,7894,7895,7897,7899,7901,7903,7906,7908],{"class":107,"line":119},[105,7896,7853],{"class":129},[105,7898,7879],{"class":115},[105,7900,876],{"class":129},[105,7902,574],{"class":111},[105,7904,7905],{"class":129}," 0.4",[105,7907,7889],{"class":115},[105,7909,7892],{"class":129},[105,7911,7912,7915,7917,7919,7921,7924,7926],{"class":107,"line":133},[105,7913,7914],{"class":129},"0.4",[105,7916,7879],{"class":115},[105,7918,876],{"class":129},[105,7920,574],{"class":111},[105,7922,7923],{"class":129}," 0.8",[105,7925,7889],{"class":115},[105,7927,7892],{"class":129},[105,7929,7930,7933,7935,7937,7939,7942,7944],{"class":107,"line":148},[105,7931,7932],{"class":129},"0.8",[105,7934,7879],{"class":115},[105,7936,876],{"class":129},[105,7938,574],{"class":111},[105,7940,7941],{"class":129}," 1.6",[105,7943,7889],{"class":115},[105,7945,7946],{"class":129},"1\n",[105,7948,7949,7952,7954,7956,7958,7961,7963],{"class":107,"line":163},[105,7950,7951],{"class":129},"0.6",[105,7953,7879],{"class":115},[105,7955,876],{"class":129},[105,7957,574],{"class":111},[105,7959,7960],{"class":129}," 1.2",[105,7962,7889],{"class":115},[105,7964,7946],{"class":129},[105,7966,7967,7969,7971,7973,7975,7977,7979,7981,7984,7986],{"class":107,"line":179},[105,7968,7853],{"class":129},[105,7970,7879],{"class":115},[105,7972,876],{"class":129},[105,7974,574],{"class":111},[105,7976,7905],{"class":129},[105,7978,7889],{"class":115},[105,7980,1100],{"class":129},[105,7982,7983],{"class":115},"  ← 回到 ",[105,7985,7853],{"class":129},[105,7987,7988],{"class":115},"，开始循环\n",[105,7990,7991],{"class":107,"line":194},[105,7992,7993],{"class":111},"...\n",[18,7995,7996,7997,8000,8001,8004,8005,8008,8009,8011,8012,8014,8015,91],{},"结果是 ",[24,7998,7999],{},"0.000110011001100110011...","，无限循环。",[21,8002,8003],{},"双精度浮点数只有 52 位尾数","，存不下无限循环，所以只能",[21,8006,8007],{},"截断或舍入","。存进去的 ",[24,8010,7850],{}," 已经不是精确的 ",[24,8013,7850],{},"，而是一个",[21,8016,8017],{},"非常接近但略有偏差的值",[18,8019,8020,8021,8024],{},"JavaScript 的设计目标之一是",[21,8022,8023],{},"简单","。Brendan Eich 在 1995 年设计 JS 时，选择了“一种数字类型”的方案：",[442,8026,8027,8036,8039],{},[448,8028,8029,8030,1347,8033,91],{},"不用区分 ",[24,8031,8032],{},"int",[24,8034,8035],{},"float",[448,8037,8038],{},"所有数字都是 64 位双精度浮点数。",[448,8040,8041,8042,91],{},"整数的最大安全范围是 ",[24,8043,8044],{},"2^53 - 1",[14,8046,8047],{"id":8047},"运行时类型检测",[18,8049,8050],{},"在js中，在运行时检测对象不能仅仅凭借typeof，因为typeof只能区分原始类型和对象，无法区分具体的对象类型，要判断是不是 Map 这种类型，就需要使用instanceof 或者Object.prototype.toString。",[96,8052,8054],{"className":559,"code":8053,"language":561,"meta":101,"style":101},"typeof new Map()      \u002F\u002F \"object\"\ntypeof {}             \u002F\u002F \"object\"\ntypeof []             \u002F\u002F \"object\"\ntypeof new Date()     \u002F\u002F \"object\"\ntypeof new Set()      \u002F\u002F \"object\"\ntypeof null           \u002F\u002F \"object\"\n",[24,8055,8056,8071,8080,8089,8102,8114],{"__ignoreMap":101},[105,8057,8058,8061,8063,8065,8068],{"class":107,"line":108},[105,8059,8060],{"class":111},"typeof",[105,8062,3496],{"class":111},[105,8064,7433],{"class":241},[105,8066,8067],{"class":115},"()      ",[105,8069,8070],{"class":630},"\u002F\u002F \"object\"\n",[105,8072,8073,8075,8078],{"class":107,"line":119},[105,8074,8060],{"class":111},[105,8076,8077],{"class":115}," {}             ",[105,8079,8070],{"class":630},[105,8081,8082,8084,8087],{"class":107,"line":133},[105,8083,8060],{"class":111},[105,8085,8086],{"class":115}," []             ",[105,8088,8070],{"class":630},[105,8090,8091,8093,8095,8097,8100],{"class":107,"line":148},[105,8092,8060],{"class":111},[105,8094,3496],{"class":111},[105,8096,7477],{"class":241},[105,8098,8099],{"class":115},"()     ",[105,8101,8070],{"class":630},[105,8103,8104,8106,8108,8110,8112],{"class":107,"line":163},[105,8105,8060],{"class":111},[105,8107,3496],{"class":111},[105,8109,5064],{"class":241},[105,8111,8067],{"class":115},[105,8113,8070],{"class":630},[105,8115,8116,8118,8120],{"class":107,"line":179},[105,8117,8060],{"class":111},[105,8119,5044],{"class":129},[105,8121,8122],{"class":630},"           \u002F\u002F \"object\"\n",[18,8124,8125,8126,8136,8137,8139,8140,31,8143,31,8146,31,8149,91],{},"typeof只会返回几种字符串，分别是undefined、object、boolean、number、bigint、string、symbol、function，",[21,8127,8128,8129,8132,8133,91],{},"除了 ",[24,8130,8131],{},"function","，所有引用类型都是 ",[24,8134,8135],{},"\"object\""," 所以 ",[24,8138,8060],{}," 无法区分 ",[24,8141,8142],{},"Map",[24,8144,8145],{},"Set",[24,8147,8148],{},"Array",[24,8150,8151],{},"Date",[18,8153,8154],{},"instanceof检查的是原型链，也就是对象的原型链上有没有这个构造函数的prototype。",[96,8156,8158],{"className":559,"code":8157,"language":561,"meta":101,"style":101},"new Map() instanceof Map  \u002F\u002F true\n",[24,8159,8160],{"__ignoreMap":101},[105,8161,8162,8164,8166,8169,8172,8174],{"class":107,"line":108},[105,8163,7430],{"class":111},[105,8165,7433],{"class":241},[105,8167,8168],{"class":115},"() ",[105,8170,8171],{"class":111},"instanceof",[105,8173,7433],{"class":241},[105,8175,8176],{"class":630},"  \u002F\u002F true\n",[18,8178,8179,8182],{},[21,8180,8181],{},"跨 iframe \u002F 跨 window 失效"," ：",[96,8184,8186],{"className":559,"code":8185,"language":561,"meta":101,"style":101},"\u002F\u002F iframe 里的 Map\nconst iframeMap = iframe.contentWindow.Map\n\nnew Map() instanceof iframeMap  \u002F\u002F false ❌\n",[24,8187,8188,8193,8205,8209],{"__ignoreMap":101},[105,8189,8190],{"class":107,"line":108},[105,8191,8192],{"class":630},"\u002F\u002F iframe 里的 Map\n",[105,8194,8195,8197,8200,8202],{"class":107,"line":119},[105,8196,568],{"class":111},[105,8198,8199],{"class":129}," iframeMap",[105,8201,574],{"class":111},[105,8203,8204],{"class":115}," iframe.contentWindow.Map\n",[105,8206,8207],{"class":107,"line":133},[105,8208,307],{"emptyLinePlaceholder":306},[105,8210,8211,8213,8215,8217,8219,8221],{"class":107,"line":148},[105,8212,7430],{"class":111},[105,8214,7433],{"class":241},[105,8216,8168],{"class":115},[105,8218,8171],{"class":111},[105,8220,8199],{"class":241},[105,8222,8223],{"class":630},"  \u002F\u002F false ❌\n",[18,8225,8226,8227,8229,8230,8233,8234],{},"因为 iframe 有自己的 ",[24,8228,8142],{}," 构造函数，",[24,8231,8232],{},"Map.prototype"," 不是同一个对象。",[21,8235,8236,8237,8239],{},"原型链上找的是当前 window 的 ",[24,8238,8232],{},"，不是 iframe 的。",[18,8241,8242],{},"更加可靠的是 使用Object.prototype.toString方法：",[96,8244,8246],{"className":559,"code":8245,"language":561,"meta":101,"style":101},"Object.prototype.toString.call(new Map())      \u002F\u002F \"[object Map]\"\nObject.prototype.toString.call(new Set())      \u002F\u002F \"[object Set]\"\nObject.prototype.toString.call([])             \u002F\u002F \"[object Array]\"\nObject.prototype.toString.call(new Date())     \u002F\u002F \"[object Date]\"\nObject.prototype.toString.call({})             \u002F\u002F \"[object Object]\"\nObject.prototype.toString.call(null)           \u002F\u002F \"[object Null]\"\nObject.prototype.toString.call(undefined)      \u002F\u002F \"[object Undefined]\"\n",[24,8247,8248,8276,8299,8317,8341,8359,8381],{"__ignoreMap":101},[105,8249,8250,8253,8255,8258,8261,8264,8266,8268,8270,8273],{"class":107,"line":108},[105,8251,8252],{"class":129},"Object",[105,8254,5381],{"class":115},[105,8256,8257],{"class":129},"prototype",[105,8259,8260],{"class":115},".toString.",[105,8262,8263],{"class":241},"call",[105,8265,321],{"class":115},[105,8267,7430],{"class":111},[105,8269,7433],{"class":241},[105,8271,8272],{"class":115},"())      ",[105,8274,8275],{"class":630},"\u002F\u002F \"[object Map]\"\n",[105,8277,8278,8280,8282,8284,8286,8288,8290,8292,8294,8296],{"class":107,"line":119},[105,8279,8252],{"class":129},[105,8281,5381],{"class":115},[105,8283,8257],{"class":129},[105,8285,8260],{"class":115},[105,8287,8263],{"class":241},[105,8289,321],{"class":115},[105,8291,7430],{"class":111},[105,8293,5064],{"class":241},[105,8295,8272],{"class":115},[105,8297,8298],{"class":630},"\u002F\u002F \"[object Set]\"\n",[105,8300,8301,8303,8305,8307,8309,8311,8314],{"class":107,"line":133},[105,8302,8252],{"class":129},[105,8304,5381],{"class":115},[105,8306,8257],{"class":129},[105,8308,8260],{"class":115},[105,8310,8263],{"class":241},[105,8312,8313],{"class":115},"([])             ",[105,8315,8316],{"class":630},"\u002F\u002F \"[object Array]\"\n",[105,8318,8319,8321,8323,8325,8327,8329,8331,8333,8335,8338],{"class":107,"line":148},[105,8320,8252],{"class":129},[105,8322,5381],{"class":115},[105,8324,8257],{"class":129},[105,8326,8260],{"class":115},[105,8328,8263],{"class":241},[105,8330,321],{"class":115},[105,8332,7430],{"class":111},[105,8334,7477],{"class":241},[105,8336,8337],{"class":115},"())     ",[105,8339,8340],{"class":630},"\u002F\u002F \"[object Date]\"\n",[105,8342,8343,8345,8347,8349,8351,8353,8356],{"class":107,"line":163},[105,8344,8252],{"class":129},[105,8346,5381],{"class":115},[105,8348,8257],{"class":129},[105,8350,8260],{"class":115},[105,8352,8263],{"class":241},[105,8354,8355],{"class":115},"({})             ",[105,8357,8358],{"class":630},"\u002F\u002F \"[object Object]\"\n",[105,8360,8361,8363,8365,8367,8369,8371,8373,8375,8378],{"class":107,"line":179},[105,8362,8252],{"class":129},[105,8364,5381],{"class":115},[105,8366,8257],{"class":129},[105,8368,8260],{"class":115},[105,8370,8263],{"class":241},[105,8372,321],{"class":115},[105,8374,4005],{"class":129},[105,8376,8377],{"class":115},")           ",[105,8379,8380],{"class":630},"\u002F\u002F \"[object Null]\"\n",[105,8382,8383,8385,8387,8389,8391,8393,8395,8398,8401],{"class":107,"line":194},[105,8384,8252],{"class":129},[105,8386,5381],{"class":115},[105,8388,8257],{"class":129},[105,8390,8260],{"class":115},[105,8392,8263],{"class":241},[105,8394,321],{"class":115},[105,8396,8397],{"class":129},"undefined",[105,8399,8400],{"class":115},")      ",[105,8402,8403],{"class":630},"\u002F\u002F \"[object Undefined]\"\n",[18,8405,8406,8409,8410,1998,8413,8416,8417,91,8420,8423,8424,8426,8427,8430,8431,91],{},[24,8407,8408],{},"Object.prototype.toString"," 会读取对象内部的 ",[24,8411,8412],{},"[[Class]]",[24,8414,8415],{},"Symbol.toStringTag"," 属性，返回 ",[24,8418,8419],{},"[object Xxx]",[21,8421,8422],{},"跨 iframe 有效"," ， 因为 ",[24,8425,8412],{}," 是引擎内部的标记，不受 window 隔离影响。",[21,8428,8429],{},"不受原型链修改影响","：因为不依赖 ",[24,8432,8257],{},[96,8434,8436],{"className":559,"code":8435,"language":561,"meta":101,"style":101},"export function getType(value: unknown) {\n  return Object.prototype.toString.call(value).slice(8, -1).toLocaleLowerCase()\n}\n\nconsole.log(getType(new Map()))\n\n",[24,8437,8438,8459,8496,8500,8504],{"__ignoreMap":101},[105,8439,8440,8442,8444,8447,8449,8452,8454,8457],{"class":107,"line":108},[105,8441,4997],{"class":111},[105,8443,5000],{"class":111},[105,8445,8446],{"class":241}," getType",[105,8448,321],{"class":115},[105,8450,8451],{"class":122},"value",[105,8453,5017],{"class":111},[105,8455,8456],{"class":129}," unknown",[105,8458,1000],{"class":115},[105,8460,8461,8463,8466,8468,8470,8472,8474,8477,8479,8481,8483,8485,8487,8489,8491,8494],{"class":107,"line":119},[105,8462,4144],{"class":111},[105,8464,8465],{"class":129}," Object",[105,8467,5381],{"class":115},[105,8469,8257],{"class":129},[105,8471,8260],{"class":115},[105,8473,8263],{"class":241},[105,8475,8476],{"class":115},"(value).",[105,8478,4291],{"class":241},[105,8480,321],{"class":115},[105,8482,7495],{"class":129},[105,8484,286],{"class":115},[105,8486,4159],{"class":111},[105,8488,861],{"class":129},[105,8490,5642],{"class":115},[105,8492,8493],{"class":241},"toLocaleLowerCase",[105,8495,5192],{"class":115},[105,8497,8498],{"class":107,"line":133},[105,8499,212],{"class":115},[105,8501,8502],{"class":107,"line":148},[105,8503,307],{"emptyLinePlaceholder":306},[105,8505,8506,8508,8510,8512,8515,8517,8519,8521],{"class":107,"line":163},[105,8507,636],{"class":115},[105,8509,639],{"class":241},[105,8511,321],{"class":115},[105,8513,8514],{"class":241},"getType",[105,8516,321],{"class":115},[105,8518,7430],{"class":111},[105,8520,7433],{"class":241},[105,8522,8523],{"class":115},"()))\n",[14,8525,8526],{"id":8526},"脚本和模块",[18,8528,8529],{},"在JS中，如果没有export，js就会被当成脚本，但是有export就会当做模块，这是 JavaScript 的一个核心区别。",[835,8531,8532,8545],{},[838,8533,8534],{},[841,8535,8536,8539,8542],{},[844,8537,8538],{},"维度",[844,8540,8541],{},"Script",[844,8543,8544],{},"Module（ESM）",[854,8546,8547,8568,8588,8601,8620,8637,8650,8663,8678,8692],{},[841,8548,8549,8554,8561],{},[859,8550,8551],{},[21,8552,8553],{},"触发条件",[859,8555,8556,8557,1885,8559],{},"没有 ",[24,8558,3903],{},[24,8560,4997],{},[859,8562,8563,8564,1885,8566],{},"有顶层 ",[24,8565,3903],{},[24,8567,4997],{},[841,8569,8570,8578,8583],{},[859,8571,8572],{},[21,8573,8574,8577],{},[24,8575,8576],{},"\u003Cscript>"," 标签",[859,8579,8580,8582],{},[24,8581,8576],{},"（默认）",[859,8584,8585],{},[24,8586,8587],{},"\u003Cscript type=\"module\">",[841,8589,8590,8595,8598],{},[859,8591,8592],{},[21,8593,8594],{},"作用域",[859,8596,8597],{},"全局",[859,8599,8600],{},"模块级（文件作用域）",[841,8602,8603,8610,8616],{},[859,8604,8605],{},[21,8606,8607,8608],{},"顶层 ",[24,8609,2888],{},[859,8611,8612,8615],{},[24,8613,8614],{},"window","（浏览器）",[859,8617,8618],{},[24,8619,8397],{},[841,8621,8622,8627,8632],{},[859,8623,8624],{},[21,8625,8626],{},"变量共享",[859,8628,8629,8630],{},"全局变量挂到 ",[24,8631,8614],{},[859,8633,8634,8635],{},"不挂到 ",[24,8636,8614],{},[841,8638,8639,8644,8647],{},[859,8640,8641],{},[21,8642,8643],{},"变量提升",[859,8645,8646],{},"有",[859,8648,8649],{},"有，但有 TDZ",[841,8651,8652,8657,8660],{},[859,8653,8654],{},[21,8655,8656],{},"严格模式",[859,8658,8659],{},"默认非严格",[859,8661,8662],{},"默认严格",[841,8664,8665,8672,8675],{},[859,8666,8667],{},[21,8668,8669,8671],{},[24,8670,5401],{}," 顶层",[859,8673,8674],{},"不允许",[859,8676,8677],{},"允许",[841,8679,8680,8688,8690],{},[859,8681,8682],{},[21,8683,8684,8687],{},[24,8685,8686],{},"import()"," 动态导入",[859,8689,8677],{},[859,8691,8677],{},[841,8693,8694,8702,8704],{},[859,8695,8696],{},[21,8697,8698,8701],{},[24,8699,8700],{},".js"," 默认解析",[859,8703,8541],{},[859,8705,8706],{},"Module",[18,8708,2974],{},[96,8710,8712],{"className":559,"code":8711,"language":561,"meta":101,"style":101},"\u002F\u002F a.js\nvar foo = 'hello'  \u002F\u002F 挂到 window.foo\n\n\u002F\u002F b.js\nconsole.log(foo)   \u002F\u002F \"hello\"，能访问\n",[24,8713,8714,8719,8734,8738,8743],{"__ignoreMap":101},[105,8715,8716],{"class":107,"line":108},[105,8717,8718],{"class":630},"\u002F\u002F a.js\n",[105,8720,8721,8723,8726,8728,8731],{"class":107,"line":119},[105,8722,1767],{"class":111},[105,8724,8725],{"class":115}," foo ",[105,8727,245],{"class":111},[105,8729,8730],{"class":248}," 'hello'",[105,8732,8733],{"class":630},"  \u002F\u002F 挂到 window.foo\n",[105,8735,8736],{"class":107,"line":133},[105,8737,307],{"emptyLinePlaceholder":306},[105,8739,8740],{"class":107,"line":148},[105,8741,8742],{"class":630},"\u002F\u002F b.js\n",[105,8744,8745,8747,8749,8752],{"class":107,"line":163},[105,8746,636],{"class":115},[105,8748,639],{"class":241},[105,8750,8751],{"class":115},"(foo)   ",[105,8753,8754],{"class":630},"\u002F\u002F \"hello\"，能访问\n",[18,8756,8757],{},"module",[96,8759,8761],{"className":559,"code":8760,"language":561,"meta":101,"style":101},"\u002F\u002F a.js\nexport const foo = 'hello'  \u002F\u002F 模块级，不挂到 window\n\n\u002F\u002F b.js\nconsole.log(foo)   \u002F\u002F ❌ ReferenceError，访问不到\n",[24,8762,8763,8767,8784,8788,8792],{"__ignoreMap":101},[105,8764,8765],{"class":107,"line":108},[105,8766,8718],{"class":630},[105,8768,8769,8771,8774,8777,8779,8781],{"class":107,"line":119},[105,8770,4997],{"class":111},[105,8772,8773],{"class":111}," const",[105,8775,8776],{"class":129}," foo",[105,8778,574],{"class":111},[105,8780,8730],{"class":248},[105,8782,8783],{"class":630},"  \u002F\u002F 模块级，不挂到 window\n",[105,8785,8786],{"class":107,"line":133},[105,8787,307],{"emptyLinePlaceholder":306},[105,8789,8790],{"class":107,"line":148},[105,8791,8742],{"class":630},[105,8793,8794,8796,8798,8800],{"class":107,"line":163},[105,8795,636],{"class":115},[105,8797,639],{"class":241},[105,8799,8751],{"class":115},[105,8801,8802],{"class":630},"\u002F\u002F ❌ ReferenceError，访问不到\n",[18,8804,8805],{},"判断一个文件是script还是module可以看有没有import\u002Fexport。也可以看script标签：",[96,8807,8809],{"className":559,"code":8808,"language":561,"meta":101,"style":101},"\u003C!-- Script -->\n\u003Cscript src=\"script.js\">\u003C\u002Fscript>\n\n\u003C!-- Module -->\n\u003Cscript type=\"module\" src=\"module.js\">\u003C\u002Fscript>\n",[24,8810,8811,8822,8841,8845,8854],{"__ignoreMap":101},[105,8812,8813,8816,8819],{"class":107,"line":108},[105,8814,8815],{"class":111},"\u003C!--",[105,8817,8818],{"class":115}," Script ",[105,8820,8821],{"class":111},"-->\n",[105,8823,8824,8826,8828,8830,8832,8835,8837,8839],{"class":107,"line":119},[105,8825,234],{"class":115},[105,8827,2974],{"class":237},[105,8829,3051],{"class":241},[105,8831,245],{"class":111},[105,8833,8834],{"class":248},"\"script.js\"",[105,8836,252],{"class":115},[105,8838,2974],{"class":237},[105,8840,257],{"class":115},[105,8842,8843],{"class":107,"line":133},[105,8844,307],{"emptyLinePlaceholder":306},[105,8846,8847,8849,8852],{"class":107,"line":148},[105,8848,8815],{"class":111},[105,8850,8851],{"class":115}," Module ",[105,8853,8821],{"class":111},[105,8855,8856,8858,8860,8863,8865,8868,8870,8872,8875,8877,8879],{"class":107,"line":163},[105,8857,234],{"class":115},[105,8859,2974],{"class":237},[105,8861,8862],{"class":241}," type",[105,8864,245],{"class":111},[105,8866,8867],{"class":248},"\"module\"",[105,8869,3051],{"class":241},[105,8871,245],{"class":111},[105,8873,8874],{"class":248},"\"module.js\"",[105,8876,252],{"class":115},[105,8878,2974],{"class":237},[105,8880,257],{"class":115},[18,8882,8883],{},"也可以看pacakge.json里的type字段，type:module 中.js文件默认当做module，type:commonjs或者没有.js文件默认当做cmj，mjs当做module，cjs当做cjs.",[18,8885,8886],{},"还有一点，我们都知道import esm是异步方法，我们可以看看@types+node的类型定义层面，",[96,8888,8890],{"className":3259,"code":8889,"language":3261,"meta":101,"style":101},"            \u002F**\n             * The absolute `file:` URL of the module.\n             *\n             * This is defined exactly the same as it is in browsers providing the URL of the\n             * current module file.\n             *\n             * This enables useful patterns such as relative file loading:\n             *\n             * ```js\n             * import { readFileSync } from 'node:fs';\n             * const buffer = readFileSync(new URL('.\u002Fdata.proto', import.meta.url));\n             * ```\n             *\u002F\n            url: string;\n",[24,8891,8892,8897,8902,8907,8912,8917,8921,8926,8930,8935,8940,8945,8950,8955],{"__ignoreMap":101},[105,8893,8894],{"class":107,"line":108},[105,8895,8896],{"class":630},"            \u002F**\n",[105,8898,8899],{"class":107,"line":119},[105,8900,8901],{"class":630},"             * The absolute `file:` URL of the module.\n",[105,8903,8904],{"class":107,"line":133},[105,8905,8906],{"class":630},"             *\n",[105,8908,8909],{"class":107,"line":148},[105,8910,8911],{"class":630},"             * This is defined exactly the same as it is in browsers providing the URL of the\n",[105,8913,8914],{"class":107,"line":163},[105,8915,8916],{"class":630},"             * current module file.\n",[105,8918,8919],{"class":107,"line":179},[105,8920,8906],{"class":630},[105,8922,8923],{"class":107,"line":194},[105,8924,8925],{"class":630},"             * This enables useful patterns such as relative file loading:\n",[105,8927,8928],{"class":107,"line":209},[105,8929,8906],{"class":630},[105,8931,8932],{"class":107,"line":526},[105,8933,8934],{"class":630},"             * ```js\n",[105,8936,8937],{"class":107,"line":531},[105,8938,8939],{"class":630},"             * import { readFileSync } from 'node:fs';\n",[105,8941,8942],{"class":107,"line":668},[105,8943,8944],{"class":630},"             * const buffer = readFileSync(new URL('.\u002Fdata.proto', import.meta.url));\n",[105,8946,8947],{"class":107,"line":673},[105,8948,8949],{"class":630},"             * ```\n",[105,8951,8952],{"class":107,"line":679},[105,8953,8954],{"class":630},"             *\u002F\n",[105,8956,8957,8960],{"class":107,"line":689},[105,8958,8959],{"class":241},"            url",[105,8961,8962],{"class":115},": string;\n",[18,8964,8965,8968],{},[24,8966,8967],{},"import.meta"," 在 TypeScript 里的类型定义分几层：",[835,8970,8971,8981],{},[838,8972,8973],{},[841,8974,8975,8978],{},[844,8976,8977],{},"类型定义来源",[844,8979,8980],{},"提供什么",[854,8982,8983,8999,9014,9031,9052],{},[841,8984,8985,8993],{},[859,8986,8987],{},[21,8988,8989,8990,3222],{},"TypeScript 内置（",[24,8991,8992],{},"lib.es5.d.ts",[859,8994,8995,8998],{},[24,8996,8997],{},"ImportMeta"," 空接口",[841,9000,9001,9008],{},[859,9002,9003],{},[21,9004,9005],{},[24,9006,9007],{},"lib.dom.d.ts",[859,9009,9010,9013],{},[24,9011,9012],{},"import.meta.url","（浏览器环境）",[841,9015,9016,9023],{},[859,9017,9018],{},[21,9019,9020],{},[24,9021,9022],{},"@types\u002Fnode",[859,9024,9025,31,9027,9030],{},[24,9026,9012],{},[24,9028,9029],{},"import.meta.resolve","（Node 环境）",[841,9032,9033,9041],{},[859,9034,9035],{},[21,9036,9037,9038,3222],{},"Vite（",[24,9039,9040],{},"vite\u002Fclient",[859,9042,9043,31,9046,31,9049],{},[24,9044,9045],{},"import.meta.env",[24,9047,9048],{},"import.meta.hot",[24,9050,9051],{},"import.meta.glob",[841,9053,9054,9059],{},[859,9055,9056],{},[21,9057,9058],{},"Nuxt",[859,9060,9061,31,9064,31,9067],{},[24,9062,9063],{},"import.meta.client",[24,9065,9066],{},"import.meta.server",[24,9068,9069],{},"import.meta.dev",[18,9071,9072,9073,9075],{},"我们可以看看Vite \u002F Nuxt 对 ",[24,9074,8967],{}," 的类型扩展：",[96,9077,9079],{"className":3259,"code":9078,"language":3261,"meta":101,"style":101},"interface ImportMeta {\n    browser: boolean;\n    client: boolean;\n    dev: boolean;\n    server: boolean;\n    test: boolean;\n}\n",[24,9080,9081,9090,9102,9113,9124,9135,9146],{"__ignoreMap":101},[105,9082,9083,9085,9088],{"class":107,"line":108},[105,9084,3519],{"class":111},[105,9086,9087],{"class":241}," ImportMeta",[105,9089,116],{"class":115},[105,9091,9092,9095,9097,9100],{"class":107,"line":119},[105,9093,9094],{"class":122},"    browser",[105,9096,5017],{"class":111},[105,9098,9099],{"class":129}," boolean",[105,9101,145],{"class":115},[105,9103,9104,9107,9109,9111],{"class":107,"line":133},[105,9105,9106],{"class":122},"    client",[105,9108,5017],{"class":111},[105,9110,9099],{"class":129},[105,9112,145],{"class":115},[105,9114,9115,9118,9120,9122],{"class":107,"line":148},[105,9116,9117],{"class":122},"    dev",[105,9119,5017],{"class":111},[105,9121,9099],{"class":129},[105,9123,145],{"class":115},[105,9125,9126,9129,9131,9133],{"class":107,"line":163},[105,9127,9128],{"class":122},"    server",[105,9130,5017],{"class":111},[105,9132,9099],{"class":129},[105,9134,145],{"class":115},[105,9136,9137,9140,9142,9144],{"class":107,"line":179},[105,9138,9139],{"class":122},"    test",[105,9141,5017],{"class":111},[105,9143,9099],{"class":129},[105,9145,145],{"class":115},[105,9147,9148],{"class":107,"line":194},[105,9149,212],{"class":115},[18,9151,9152,9153,9155,9156,1998,9159,9161],{},"这些用来标记代码运行在什么环境。Vite 在构建时把 ",[24,9154,9063],{}," 替换成 ",[24,9157,9158],{},"true",[24,9160,7838],{},"，运行时零开销。",[14,9163,9164],{"id":9164},"await和事件循环",[18,9166,9167,9168],{},"我们一直说await是等待任务完成，但是原理模型是需要了解的，",[21,9169,9170,9172],{},[24,9171,5401],{}," 不是“阻塞”，它是“挂起当前函数，让出线程”。",[18,9174,9175],{},"同步阻塞说的类似于fs.readFileSync这样的node环境方法，线程卡在这里，什么都做不了，await会让当前函数暂停，让线程去做别的事情，也叫做suspending：",[96,9177,9179],{"className":559,"code":9178,"language":561,"meta":101,"style":101},"\u002F\u002F 异步挂起\nconst data = await fs.readFile('a.txt')  \u002F\u002F 当前函数暂停，线程去处理其他任务\nconsole.log('done')                      \u002F\u002F 等数据到了，函数恢复执行\n",[24,9180,9181,9186,9213],{"__ignoreMap":101},[105,9182,9183],{"class":107,"line":108},[105,9184,9185],{"class":630},"\u002F\u002F 异步挂起\n",[105,9187,9188,9190,9193,9195,9197,9200,9203,9205,9208,9210],{"class":107,"line":119},[105,9189,568],{"class":111},[105,9191,9192],{"class":129}," data",[105,9194,574],{"class":111},[105,9196,5428],{"class":111},[105,9198,9199],{"class":115}," fs.",[105,9201,9202],{"class":241},"readFile",[105,9204,321],{"class":115},[105,9206,9207],{"class":248},"'a.txt'",[105,9209,5440],{"class":115},[105,9211,9212],{"class":630},"\u002F\u002F 当前函数暂停，线程去处理其他任务\n",[105,9214,9215,9217,9219,9221,9224,9227],{"class":107,"line":133},[105,9216,636],{"class":115},[105,9218,639],{"class":241},[105,9220,321],{"class":115},[105,9222,9223],{"class":248},"'done'",[105,9225,9226],{"class":115},")                      ",[105,9228,9229],{"class":630},"\u002F\u002F 等数据到了，函数恢复执行\n",[96,9231,9233],{"className":559,"code":9232,"language":561,"meta":101,"style":101},"async function fetchData() {\n  console.log('1')\n  const data = await fetch('\u002Fapi')  \u002F\u002F 挂起点\n  console.log('2')\n}\n\nfetchData()\nconsole.log('3')\n\n1. 执行 fetchData，打印 \"1\"\n2. 遇到 await，fetchData 挂起，返回一个 Promise\n3. 主线程继续执行，打印 \"3\"\n4. fetch 完成后，fetchData 恢复，打印 \"2\"\n",[24,9234,9235,9248,9261,9283,9296,9300,9304,9311,9324,9328,9339,9355,9366],{"__ignoreMap":101},[105,9236,9237,9240,9242,9245],{"class":107,"line":108},[105,9238,9239],{"class":111},"async",[105,9241,5000],{"class":111},[105,9243,9244],{"class":241}," fetchData",[105,9246,9247],{"class":115},"() {\n",[105,9249,9250,9252,9254,9256,9259],{"class":107,"line":119},[105,9251,3348],{"class":115},[105,9253,639],{"class":241},[105,9255,321],{"class":115},[105,9257,9258],{"class":248},"'1'",[105,9260,1529],{"class":115},[105,9262,9263,9265,9267,9269,9271,9273,9275,9278,9280],{"class":107,"line":133},[105,9264,4119],{"class":111},[105,9266,9192],{"class":129},[105,9268,574],{"class":111},[105,9270,5428],{"class":111},[105,9272,6000],{"class":241},[105,9274,321],{"class":115},[105,9276,9277],{"class":248},"'\u002Fapi'",[105,9279,5440],{"class":115},[105,9281,9282],{"class":630},"\u002F\u002F 挂起点\n",[105,9284,9285,9287,9289,9291,9294],{"class":107,"line":148},[105,9286,3348],{"class":115},[105,9288,639],{"class":241},[105,9290,321],{"class":115},[105,9292,9293],{"class":248},"'2'",[105,9295,1529],{"class":115},[105,9297,9298],{"class":107,"line":163},[105,9299,212],{"class":115},[105,9301,9302],{"class":107,"line":179},[105,9303,307],{"emptyLinePlaceholder":306},[105,9305,9306,9309],{"class":107,"line":194},[105,9307,9308],{"class":241},"fetchData",[105,9310,5192],{"class":115},[105,9312,9313,9315,9317,9319,9322],{"class":107,"line":209},[105,9314,636],{"class":115},[105,9316,639],{"class":241},[105,9318,321],{"class":115},[105,9320,9321],{"class":248},"'3'",[105,9323,1529],{"class":115},[105,9325,9326],{"class":107,"line":526},[105,9327,307],{"emptyLinePlaceholder":306},[105,9329,9330,9333,9336],{"class":107,"line":531},[105,9331,9332],{"class":129},"1.",[105,9334,9335],{"class":115}," 执行 fetchData，打印 ",[105,9337,9338],{"class":248},"\"1\"\n",[105,9340,9341,9344,9347,9349,9352],{"class":107,"line":668},[105,9342,9343],{"class":129},"2.",[105,9345,9346],{"class":115}," 遇到 ",[105,9348,5401],{"class":111},[105,9350,9351],{"class":115},"，fetchData 挂起，返回一个 ",[105,9353,9354],{"class":129},"Promise\n",[105,9356,9357,9360,9363],{"class":107,"line":673},[105,9358,9359],{"class":129},"3.",[105,9361,9362],{"class":115}," 主线程继续执行，打印 ",[105,9364,9365],{"class":248},"\"3\"\n",[105,9367,9368,9371,9374],{"class":107,"line":679},[105,9369,9370],{"class":129},"4.",[105,9372,9373],{"class":115}," fetch 完成后，fetchData 恢复，打印 ",[105,9375,9376],{"class":248},"\"2\"\n",[18,9378,9379,9380,9383,9384],{},"我们知道await挂起的是async方法，不是整个程序，await之后的代码会被注册为Promise的.then回调，这个回调进入的是微任务队列，而不是宏任务队列。\n",[21,9381,9382],{},"微任务队列","和",[21,9385,9386],{},"宏任务队列",[835,9388,9389,9402],{},[838,9390,9391],{},[841,9392,9393,9396,9399],{},[844,9394,9395],{},"队列",[844,9397,9398],{},"存放内容",[844,9400,9401],{},"执行时机",[854,9403,9404,9419],{},[841,9405,9406,9410,9413],{},[859,9407,9408],{},[21,9409,9382],{},[859,9411,9412],{},"Promise.then、await 恢复、MutationObserver",[859,9414,9415,9416],{},"每个宏任务结束后，",[21,9417,9418],{},"清空所有微任务",[841,9420,9421,9425,9428],{},[859,9422,9423],{},[21,9424,9386],{},[859,9426,9427],{},"setTimeout、setInterval、I\u002FO、UI 渲染",[859,9429,9430,9431,9434],{},"每轮事件循环取",[21,9432,9433],{},"一个","宏任务",[18,9436,9437],{},"我们知道事件的循环是： 执行调用栈里的同步代码，调用栈清空，开始执行微任务队列，微任务队列清空，执行下一个宏任务，然后继续调用栈清空。",[18,9439,9440,9442,9443,9446],{},[24,9441,5401],{}," 恢复是微任务，所以",[21,9444,9445],{},"会在调用栈清空后立刻执行","，不会等 UI 渲染或 setTimeout。",[96,9448,9450],{"className":559,"code":9449,"language":561,"meta":101,"style":101},"console.log('A')\n\nsetTimeout(() => console.log('B'), 0)  \u002F\u002F 宏任务\n\nPromise.resolve().then(() => console.log('C'))  \u002F\u002F 微任务\n\nasync function foo() {\n  console.log('D')\n  await null  \u002F\u002F 挂起\n  console.log('E')  \u002F\u002F 微任务\n}\n\nfoo()\n\nconsole.log('F')\n\nA          ← 同步\nD          ← 同步\nF          ← 同步\nC          ← 微任务\nE          ← 微任务（await 恢复）\nB          ← 宏任务\n",[24,9451,9452,9465,9469,9497,9501,9534,9538,9548,9561,9571,9586,9590,9594,9601,9605,9618,9622,9630,9637,9644,9652,9665],{"__ignoreMap":101},[105,9453,9454,9456,9458,9460,9463],{"class":107,"line":108},[105,9455,636],{"class":115},[105,9457,639],{"class":241},[105,9459,321],{"class":115},[105,9461,9462],{"class":248},"'A'",[105,9464,1529],{"class":115},[105,9466,9467],{"class":107,"line":119},[105,9468,307],{"emptyLinePlaceholder":306},[105,9470,9471,9474,9476,9478,9480,9482,9484,9487,9490,9492,9494],{"class":107,"line":133},[105,9472,9473],{"class":241},"setTimeout",[105,9475,4070],{"class":115},[105,9477,723],{"class":111},[105,9479,5540],{"class":115},[105,9481,639],{"class":241},[105,9483,321],{"class":115},[105,9485,9486],{"class":248},"'B'",[105,9488,9489],{"class":115},"), ",[105,9491,1100],{"class":129},[105,9493,5440],{"class":115},[105,9495,9496],{"class":630},"\u002F\u002F 宏任务\n",[105,9498,9499],{"class":107,"line":148},[105,9500,307],{"emptyLinePlaceholder":306},[105,9502,9503,9506,9508,9511,9513,9515,9517,9519,9521,9523,9525,9528,9531],{"class":107,"line":163},[105,9504,9505],{"class":129},"Promise",[105,9507,5381],{"class":115},[105,9509,9510],{"class":241},"resolve",[105,9512,5763],{"class":115},[105,9514,5530],{"class":241},[105,9516,4070],{"class":115},[105,9518,723],{"class":111},[105,9520,5540],{"class":115},[105,9522,639],{"class":241},[105,9524,321],{"class":115},[105,9526,9527],{"class":248},"'C'",[105,9529,9530],{"class":115},"))  ",[105,9532,9533],{"class":630},"\u002F\u002F 微任务\n",[105,9535,9536],{"class":107,"line":179},[105,9537,307],{"emptyLinePlaceholder":306},[105,9539,9540,9542,9544,9546],{"class":107,"line":194},[105,9541,9239],{"class":111},[105,9543,5000],{"class":111},[105,9545,8776],{"class":241},[105,9547,9247],{"class":115},[105,9549,9550,9552,9554,9556,9559],{"class":107,"line":209},[105,9551,3348],{"class":115},[105,9553,639],{"class":241},[105,9555,321],{"class":115},[105,9557,9558],{"class":248},"'D'",[105,9560,1529],{"class":115},[105,9562,9563,9566,9568],{"class":107,"line":526},[105,9564,9565],{"class":111},"  await",[105,9567,5044],{"class":129},[105,9569,9570],{"class":630},"  \u002F\u002F 挂起\n",[105,9572,9573,9575,9577,9579,9582,9584],{"class":107,"line":531},[105,9574,3348],{"class":115},[105,9576,639],{"class":241},[105,9578,321],{"class":115},[105,9580,9581],{"class":248},"'E'",[105,9583,5440],{"class":115},[105,9585,9533],{"class":630},[105,9587,9588],{"class":107,"line":668},[105,9589,212],{"class":115},[105,9591,9592],{"class":107,"line":673},[105,9593,307],{"emptyLinePlaceholder":306},[105,9595,9596,9599],{"class":107,"line":679},[105,9597,9598],{"class":241},"foo",[105,9600,5192],{"class":115},[105,9602,9603],{"class":107,"line":689},[105,9604,307],{"emptyLinePlaceholder":306},[105,9606,9607,9609,9611,9613,9616],{"class":107,"line":699},[105,9608,636],{"class":115},[105,9610,639],{"class":241},[105,9612,321],{"class":115},[105,9614,9615],{"class":248},"'F'",[105,9617,1529],{"class":115},[105,9619,9620],{"class":107,"line":704},[105,9621,307],{"emptyLinePlaceholder":306},[105,9623,9624,9627],{"class":107,"line":710},[105,9625,9626],{"class":129},"A",[105,9628,9629],{"class":115},"          ← 同步\n",[105,9631,9632,9635],{"class":107,"line":1136},[105,9633,9634],{"class":129},"D",[105,9636,9629],{"class":115},[105,9638,9639,9642],{"class":107,"line":1149},[105,9640,9641],{"class":129},"F",[105,9643,9629],{"class":115},[105,9645,9646,9649],{"class":107,"line":3855},[105,9647,9648],{"class":129},"C",[105,9650,9651],{"class":115},"          ← 微任务\n",[105,9653,9654,9657,9660,9662],{"class":107,"line":3865},[105,9655,9656],{"class":129},"E",[105,9658,9659],{"class":115},"          ← 微任务（",[105,9661,5401],{"class":111},[105,9663,9664],{"class":115}," 恢复）\n",[105,9666,9667,9670],{"class":107,"line":3875},[105,9668,9669],{"class":129},"B",[105,9671,9672],{"class":115},"          ← 宏任务\n",[18,9674,9675],{},"E在B之前，因为await恢复是微任务，setTimeout是宏任务，我们分析就可以知道，微任务队列里面有Promise.then、await恢复，宏任务队列里面有SetTimeout。因此顺序就是同步A同步D同步F微任务C微任务E宏任务B。",[9677,9678,9679],"blockquote",{},[18,9680,9681,7831,9684],{},[105,9682,9683],{},"!tip",[21,9685,9686],{},"事件循环负责协调调用栈、微任务队列和宏任务队列。同步代码在调用栈中执行；调用栈清空后，先清空所有微任务；然后取一个宏任务执行；如此循环。",[14,9688,9690],{"id":9689},"js的核心运行机制","JS的核心运行机制",[18,9692,9693],{},"刚才讲了同步、异步方法的细节，现在来讲讲执行上下文，这个是理解JS运行原理的核心，await、事件循环、this、变量查找都建立在执行上下文。",[18,9695,9696],{},"执行上下文是代码执行的环境，记录了： 变量环境（var、函数声明）、词法环境（let、const、块级作用域）、this绑定（当前函数的this指向）、外部环境引用（作用域链，用于变量查找）、代码类型（全局、函数、eval）。 每一次函数调用，都会创建一个新的执行上下文。",[18,9698,9699],{},"我们知道，js有三种上下文，分别是全局上下文，这个是脚本加载的时候创建的，是整个脚本的顶层代码。然后是函数上下文，每次调用函数的时候创建。最后是Eval上下文，eval()调用的时候会创建，很少使用。",[18,9701,9702],{},"全局上下文只有一个，函数上下文每次调用都会调用新建。‘",[96,9704,9707],{"className":9705,"code":9706,"language":392},[390],"1. 创建阶段\n   ├── 创建变量环境（var、函数声明）\n   ├── 创建词法环境（let、const）\n   ├── 确定 this 绑定\n   └── 建立作用域链\n\n2. 执行阶段\n   ├── 逐行执行代码\n   ├── 变量赋值\n   └── 函数调用（创建新的执行上下文）\n\n3. 销毁阶段\n   └── 函数返回，上下文出栈\n",[24,9708,9706],{"__ignoreMap":101},[18,9710,9711],{},"我举例一段代码：",[96,9713,9716],{"className":9714,"code":9715,"language":392},[390],"function a() {\n  console.log('a')\n  b()\n}\n\nfunction b() {\n  console.log('b')\n  c()\n}\n\nfunction c() {\n  console.log('c')\n}\n\na()\n",[24,9717,9715],{"__ignoreMap":101},[18,9719,9720],{},"上下文栈的变化：",[96,9722,9725],{"className":9723,"code":9724,"language":392},[390],"1. 全局上下文入栈\n   [全局]\n\n2. 调用 a()，a 的上下文入栈\n   [全局, a]\n\n3. a 里调用 b()，b 的上下文入栈\n   [全局, a, b]\n\n4. b 里调用 c()，c 的上下文入栈\n   [全局, a, b, c]\n\n5. c 执行完，c 出栈\n   [全局, a, b]\n\n6. b 执行完，b 出栈\n   [全局, a]\n\n7. a 执行完，a 出栈\n   [全局]\n\n8. 全局执行完，全局出栈\n   []\n",[24,9726,9724],{"__ignoreMap":101},[18,9728,9729],{},"然后我们来聊聊执行上下文和作用域链的关系：",[96,9731,9733],{"className":559,"code":9732,"language":561,"meta":101,"style":101},"function outer() {\n  const a = 1\n\n  function inner() {\n    const b = 2\n    console.log(a + b)  \u002F\u002F 3\n  }\n\n  inner()\n}\n",[24,9734,9735,9744,9756,9760,9769,9781,9798,9802,9806,9813],{"__ignoreMap":101},[105,9736,9737,9739,9742],{"class":107,"line":108},[105,9738,8131],{"class":111},[105,9740,9741],{"class":241}," outer",[105,9743,9247],{"class":115},[105,9745,9746,9748,9751,9753],{"class":107,"line":119},[105,9747,4119],{"class":111},[105,9749,9750],{"class":129}," a",[105,9752,574],{"class":111},[105,9754,9755],{"class":129}," 1\n",[105,9757,9758],{"class":107,"line":133},[105,9759,307],{"emptyLinePlaceholder":306},[105,9761,9762,9764,9767],{"class":107,"line":148},[105,9763,5205],{"class":111},[105,9765,9766],{"class":241}," inner",[105,9768,9247],{"class":115},[105,9770,9771,9773,9776,9778],{"class":107,"line":163},[105,9772,7238],{"class":111},[105,9774,9775],{"class":129}," b",[105,9777,574],{"class":111},[105,9779,9780],{"class":129}," 2\n",[105,9782,9783,9785,9787,9790,9792,9795],{"class":107,"line":179},[105,9784,6680],{"class":115},[105,9786,639],{"class":241},[105,9788,9789],{"class":115},"(a ",[105,9791,1085],{"class":111},[105,9793,9794],{"class":115}," b)  ",[105,9796,9797],{"class":630},"\u002F\u002F 3\n",[105,9799,9800],{"class":107,"line":194},[105,9801,509],{"class":115},[105,9803,9804],{"class":107,"line":209},[105,9805,307],{"emptyLinePlaceholder":306},[105,9807,9808,9811],{"class":107,"line":526},[105,9809,9810],{"class":241},"  inner",[105,9812,5192],{"class":115},[105,9814,9815],{"class":107,"line":531},[105,9816,212],{"class":115},[18,9818,9819],{},"用全局的视角来看，outer函数里包裹一个innre函数，inner的执行上下文（变量环境有b: 2，词法环境有b: 2）里面应该是找不到a的，那么他就会沿着作用域向上寻找，它找到了outer上下文的词法环境（变量环境a:1，外部引用->全局上下文）。",[18,9821,9822],{},"作用域链就是执行上下文的外部引用链，变量查找会沿着这条链向上寻找。",[18,9824,9825],{},"再来深入一点点，刚才我们说了变量环境和词法环境，这两个都是决定变量存放地方和查找、可访问的概念。",[96,9827,9830],{"className":9828,"code":9829,"language":392},[390],"执行上下文\n├── 词法环境（Lexical Environment）\n│   ├── 环境记录（Environment Record）\n│   │   ├── 声明式环境记录（let、const、class、函数声明）\n│   │   └── 对象环境记录（with、global 的 var）\n│   └── 外部环境引用（outer）\n│\n├── 变量环境（Variable Environment）\n│   └── 环境记录（主要放 var、函数声明）\n│\n└── this 绑定\n",[24,9831,9829],{"__ignoreMap":101},[18,9833,9834],{},"词法环境由环境记录、外部环境引用组成，环境记录存放的是let const class 函数声明 catch参数组成。外部环境记录指向外部词法环境，形成了作用域链。全局词法环境的外部引用指向null。块级词法环境的外部引用指向全局词法环境。",[18,9836,9837],{},"var和函数声明进入的是变量环境，是因为var和let\u002Fconst的作用域规则不同，var的作用域是函数级，变量会提升，初始化为undefined，如果是全局的话，会挂载到window上，let\u002Fconst作用域是块级，可以提升但是不会初始化（TDZ，这个我们了解过），不会挂载到window。",[18,9839,9840],{},"变量查找顺序",[18,9842,9843,9844,9847],{},"当代码里访问一个变量 ",[24,9845,9846],{},"x","：",[96,9849,9852],{"className":9850,"code":9851,"language":392},[390],"1. 先查当前词法环境的环境记录\n2. 找不到 → 查当前变量环境的环境记录\n3. 找不到 → 沿词法环境的外部引用往上找\n4. 找不到 → 沿变量环境的外部引用往上找\n5. 到全局还找不到 → ReferenceError\n",[24,9853,9851],{"__ignoreMap":101},[18,9855,9856],{},"块级作用域实现",[96,9858,9860],{"className":559,"code":9859,"language":561,"meta":101,"style":101},"{\n  let a = 1\n  const b = 2\n}\n",[24,9861,9862,9867,9879,9889],{"__ignoreMap":101},[105,9863,9864],{"class":107,"line":108},[105,9865,9866],{"class":115},"{\n",[105,9868,9869,9872,9875,9877],{"class":107,"line":119},[105,9870,9871],{"class":111},"  let",[105,9873,9874],{"class":115}," a ",[105,9876,245],{"class":111},[105,9878,9755],{"class":129},[105,9880,9881,9883,9885,9887],{"class":107,"line":133},[105,9882,4119],{"class":111},[105,9884,9775],{"class":129},[105,9886,574],{"class":111},[105,9888,9780],{"class":129},[105,9890,9891],{"class":107,"line":148},[105,9892,212],{"class":115},[18,9894,9895],{},"每进入一个代码块{}，就会创建一个新的词法环境。",[96,9897,9900],{"className":9898,"code":9899,"language":392},[390],"外层词法环境\n  ├── 环境记录: {}\n  └── 外部引用: null\n\n内层词法环境（{} 内）\n  ├── 环境记录: { a: 1, b: 2 }\n  └── 外部引用: → 外层词法环境\n",[24,9901,9899],{"__ignoreMap":101},[18,9903,9904],{},"这个可以多记忆一点，因为块级作用域的本质就是进入块级的时候创建词法环境，离开的时候销毁。let也会创建一个块级词法环境，for循环是let面试经常被问到的，为什么let可以解决var闭包问题。",[96,9906,9908],{"className":559,"code":9907,"language":561,"meta":101,"style":101},"for (var i = 0; i \u003C 3; i++) {\n  setTimeout(() => console.log(i), 0)\n}\n\u002F\u002F 输出：3 3 3\n",[24,9909,9910,9941,9961,9965],{"__ignoreMap":101},[105,9911,9912,9915,9917,9919,9922,9924,9926,9929,9931,9934,9937,9939],{"class":107,"line":108},[105,9913,9914],{"class":111},"for",[105,9916,4379],{"class":115},[105,9918,1767],{"class":111},[105,9920,9921],{"class":115}," i ",[105,9923,245],{"class":111},[105,9925,2755],{"class":129},[105,9927,9928],{"class":115},"; i ",[105,9930,234],{"class":111},[105,9932,9933],{"class":129}," 3",[105,9935,9936],{"class":115},"; i",[105,9938,2894],{"class":111},[105,9940,1000],{"class":115},[105,9942,9943,9946,9948,9950,9952,9954,9957,9959],{"class":107,"line":119},[105,9944,9945],{"class":241},"  setTimeout",[105,9947,4070],{"class":115},[105,9949,723],{"class":111},[105,9951,5540],{"class":115},[105,9953,639],{"class":241},[105,9955,9956],{"class":115},"(i), ",[105,9958,1100],{"class":129},[105,9960,1529],{"class":115},[105,9962,9963],{"class":107,"line":133},[105,9964,212],{"class":115},[105,9966,9967],{"class":107,"line":148},[105,9968,9969],{"class":630},"\u002F\u002F 输出：3 3 3\n",[18,9971,9972],{},"var i 是函数级作用域，只有一个i，从结构上来看，全局执行上下文下是变量环境，变量环境里有i: 3（循环结束）。三个setTimeout回调在循环结束后执行，此时i = 3，三个闭包引用的是同一个i。而let在for循环中，每次迭代会创建一个新的词法环境，并复制上一轮的变量值。",[9677,9974,9975],{},[18,9976,9977,7831,9979],{},[105,9978,9683],{},[21,9980,9981,9983,9984,9986],{},[24,9982,4356],{}," 的“每次声明创建新绑定”语义，既导致了“不能重复声明”，也导致了“",[24,9985,9914],{}," 循环每轮新环境”。两者是同一规则的两个表现，不是因果关系。",[18,9988,9989],{},"函数作用域的实现",[96,9991,9993],{"className":559,"code":9992,"language":561,"meta":101,"style":101},"function foo() {\n  var a = 1\n  function bar() {}\n}\n",[24,9994,9995,10003,10014,10024],{"__ignoreMap":101},[105,9996,9997,9999,10001],{"class":107,"line":108},[105,9998,8131],{"class":111},[105,10000,8776],{"class":241},[105,10002,9247],{"class":115},[105,10004,10005,10008,10010,10012],{"class":107,"line":119},[105,10006,10007],{"class":111},"  var",[105,10009,9874],{"class":115},[105,10011,245],{"class":111},[105,10013,9755],{"class":129},[105,10015,10016,10018,10021],{"class":107,"line":133},[105,10017,5205],{"class":111},[105,10019,10020],{"class":241}," bar",[105,10022,10023],{"class":115},"() {}\n",[105,10025,10026],{"class":107,"line":148},[105,10027,212],{"class":115},[18,10029,10030],{},"函数在调用时候创建新的执行上下文，foo函数有词法环境、变量环境，这个都知道。然后this指向undefined。",[18,10032,10033],{},"闭包的实现",[96,10035,10037],{"className":559,"code":10036,"language":561,"meta":101,"style":101},"function outer() {\n  let a = 1\n\n  function inner() {\n    console.log(a)  \u002F\u002F 访问外层的 a\n  }\n\n  return inner\n}\n\nconst fn = outer()\nfn()  \u002F\u002F 1\n",[24,10038,10039,10047,10057,10061,10069,10081,10085,10089,10096,10100,10104,10117],{"__ignoreMap":101},[105,10040,10041,10043,10045],{"class":107,"line":108},[105,10042,8131],{"class":111},[105,10044,9741],{"class":241},[105,10046,9247],{"class":115},[105,10048,10049,10051,10053,10055],{"class":107,"line":119},[105,10050,9871],{"class":111},[105,10052,9874],{"class":115},[105,10054,245],{"class":111},[105,10056,9755],{"class":129},[105,10058,10059],{"class":107,"line":133},[105,10060,307],{"emptyLinePlaceholder":306},[105,10062,10063,10065,10067],{"class":107,"line":148},[105,10064,5205],{"class":111},[105,10066,9766],{"class":241},[105,10068,9247],{"class":115},[105,10070,10071,10073,10075,10078],{"class":107,"line":163},[105,10072,6680],{"class":115},[105,10074,639],{"class":241},[105,10076,10077],{"class":115},"(a)  ",[105,10079,10080],{"class":630},"\u002F\u002F 访问外层的 a\n",[105,10082,10083],{"class":107,"line":179},[105,10084,509],{"class":115},[105,10086,10087],{"class":107,"line":194},[105,10088,307],{"emptyLinePlaceholder":306},[105,10090,10091,10093],{"class":107,"line":209},[105,10092,4144],{"class":111},[105,10094,10095],{"class":115}," inner\n",[105,10097,10098],{"class":107,"line":526},[105,10099,212],{"class":115},[105,10101,10102],{"class":107,"line":531},[105,10103,307],{"emptyLinePlaceholder":306},[105,10105,10106,10108,10111,10113,10115],{"class":107,"line":668},[105,10107,568],{"class":111},[105,10109,10110],{"class":129}," fn",[105,10112,574],{"class":111},[105,10114,9741],{"class":241},[105,10116,5192],{"class":115},[105,10118,10119,10122,10124],{"class":107,"line":673},[105,10120,10121],{"class":241},"fn",[105,10123,3002],{"class":115},[105,10125,10126],{"class":630},"\u002F\u002F 1\n",[18,10128,10129],{},"inner返回后，它的执行上下文应该被销毁的，但是我们发现inner作用域引用了outer的词法环境，所以outer的词法环境不会被回收。闭包就相当于一个函数+他创建时候的词法环境链。",[18,10131,10132],{},"作用域和执行上下文的关系",[18,10134,10135],{},"这个可能在面试会被提及，作用域是静态的词法概念，执行上下文是运行时概念。前者决定能够访问哪些变量，后者是执行时的环境。我们写代码的时候发现inner可以访问outer里的属性，这个就是写代码时候确认的，inner的词法作用域链上包含outer。",[14,10137,10138],{"id":10138},"this问题",[18,10140,10141,10142,10145,10146,10149],{},"this是",[21,10143,10144],{},"函数执行时的上下文对象","，它的值取决于",[21,10147,10148],{},"函数怎么被调用","，而不是怎么被定义（箭头函数除外）。",[18,10151,10152],{},"js里有五种绑定this的方法，分别是new绑定，this就会指向新创建的对象，比如说new Foo()，然后是显示绑定，call\u002Fapply\u002Fbind 指定对象，比如foo.call(this)，再就是隐式绑定，this会指向调用方法的对象，比如obj.foo()，再来是默认绑定，this会指向undefined(严格)\u002F(非严格)，最后是箭头函数，this会指向外层作用域this，()->this。",[18,10154,10155,9847,10158,10160],{},[21,10156,10157],{},"优先级高的规则赢",[24,10159,7430],{}," > 显式 > 隐式 > 默认。\n箭头函数比较特殊，它不遵循前面4条规则，而是词法绑定，所以要单独列出来说。new呢，是最高优先级：",[96,10162,10164],{"className":559,"code":10163,"language":561,"meta":101,"style":101},"function Foo(name) {\n  this.name = name\n}\n\nconst obj = new Foo('张三')\nconsole.log(obj.name)  \u002F\u002F '张三'\n",[24,10165,10166,10179,10192,10196,10200,10220],{"__ignoreMap":101},[105,10167,10168,10170,10173,10175,10177],{"class":107,"line":108},[105,10169,8131],{"class":111},[105,10171,10172],{"class":241}," Foo",[105,10174,321],{"class":115},[105,10176,5014],{"class":122},[105,10178,1000],{"class":115},[105,10180,10181,10184,10187,10189],{"class":107,"line":119},[105,10182,10183],{"class":129},"  this",[105,10185,10186],{"class":115},".name ",[105,10188,245],{"class":111},[105,10190,10191],{"class":115}," name\n",[105,10193,10194],{"class":107,"line":133},[105,10195,212],{"class":115},[105,10197,10198],{"class":107,"line":148},[105,10199,307],{"emptyLinePlaceholder":306},[105,10201,10202,10204,10207,10209,10211,10213,10215,10218],{"class":107,"line":163},[105,10203,568],{"class":111},[105,10205,10206],{"class":129}," obj",[105,10208,574],{"class":111},[105,10210,3496],{"class":111},[105,10212,10172],{"class":241},[105,10214,321],{"class":115},[105,10216,10217],{"class":248},"'张三'",[105,10219,1529],{"class":115},[105,10221,10222,10224,10226,10229],{"class":107,"line":179},[105,10223,636],{"class":115},[105,10225,639],{"class":241},[105,10227,10228],{"class":115},"(obj.name)  ",[105,10230,10231],{"class":630},"\u002F\u002F '张三'\n",[18,10233,10234],{},"我们在typecsriptteam.native-preview中可以看看Function的定义：",[96,10236,10238],{"className":3259,"code":10237,"language":3261,"meta":101,"style":101},"interface FunctionConstructor {\n    \u002F**\n     * Creates a new function.\n     * @param args A list of arguments the function accepts.\n     *\u002F\n    new (...args: string[]): Function;\n    (...args: string[]): Function;\n    readonly prototype: Function;\n}\n\ndeclare var Function: FunctionConstructor;\n",[24,10239,10240,10249,10254,10259,10273,10278,10305,10326,10340,10344,10348],{"__ignoreMap":101},[105,10241,10242,10244,10247],{"class":107,"line":108},[105,10243,3519],{"class":111},[105,10245,10246],{"class":241}," FunctionConstructor",[105,10248,116],{"class":115},[105,10250,10251],{"class":107,"line":119},[105,10252,10253],{"class":630},"    \u002F**\n",[105,10255,10256],{"class":107,"line":133},[105,10257,10258],{"class":630},"     * Creates a new function.\n",[105,10260,10261,10264,10267,10270],{"class":107,"line":148},[105,10262,10263],{"class":630},"     * ",[105,10265,10266],{"class":111},"@param",[105,10268,10269],{"class":115}," args",[105,10271,10272],{"class":630}," A list of arguments the function accepts.\n",[105,10274,10275],{"class":107,"line":163},[105,10276,10277],{"class":630},"     *\u002F\n",[105,10279,10280,10283,10285,10288,10291,10293,10295,10298,10300,10303],{"class":107,"line":179},[105,10281,10282],{"class":111},"    new",[105,10284,4379],{"class":115},[105,10286,10287],{"class":111},"...",[105,10289,10290],{"class":122},"args",[105,10292,5017],{"class":111},[105,10294,5020],{"class":129},[105,10296,10297],{"class":115},"[])",[105,10299,5017],{"class":111},[105,10301,10302],{"class":241}," Function",[105,10304,145],{"class":115},[105,10306,10307,10310,10312,10314,10316,10318,10320,10322,10324],{"class":107,"line":194},[105,10308,10309],{"class":115},"    (",[105,10311,10287],{"class":111},[105,10313,10290],{"class":122},[105,10315,5017],{"class":111},[105,10317,5020],{"class":129},[105,10319,10297],{"class":115},[105,10321,5017],{"class":111},[105,10323,10302],{"class":241},[105,10325,145],{"class":115},[105,10327,10328,10331,10334,10336,10338],{"class":107,"line":209},[105,10329,10330],{"class":111},"    readonly",[105,10332,10333],{"class":241}," prototype",[105,10335,5017],{"class":111},[105,10337,10302],{"class":241},[105,10339,145],{"class":115},[105,10341,10342],{"class":107,"line":526},[105,10343,212],{"class":115},[105,10345,10346],{"class":107,"line":531},[105,10347,307],{"emptyLinePlaceholder":306},[105,10349,10350,10353,10356,10358,10360,10362],{"class":107,"line":668},[105,10351,10352],{"class":111},"declare",[105,10354,10355],{"class":111}," var",[105,10357,10302],{"class":115},[105,10359,5017],{"class":111},[105,10361,10246],{"class":241},[105,10363,145],{"class":115},[18,10365,10366],{},"new Function()和 Function()效果一样，都返回新函数，这个是Function的特殊之处，Function既是构造函数也是普通函数。readonly prototype: Function 表示Function.prototype是不可重新赋值的，表示Function.prototype是不可重新赋值。",[14,10368,10369],{"id":10369},"arguments",[18,10371,10372,10373,10376],{},"arguments是普通函数内部自动可用的一个",[21,10374,10375],{},"类数组对象","，包含了调用该函数时候传入的所有实参，他不是数组，但是长得像数组，可以按索引访问，也有length属性。",[96,10378,10381],{"className":10379,"code":10380,"language":392},[390],"function foo(a, b) {\n    console.log(arguments);        \u002F\u002F [1, 2, 3]（类数组）\n    console.log(arguments.length); \u002F\u002F 3\n    console.log(arguments[0]);     \u002F\u002F 1\n    console.log(arguments[1]);     \u002F\u002F 2\n    console.log(arguments[2]);     \u002F\u002F 3\n}\n\nfoo(1, 2, 3);\n",[24,10382,10380],{"__ignoreMap":101},[18,10384,10385],{},"而 fn: Function ， fn.length访问的就是这个方法至少需要多少个参数了，并且箭头函数也是有的，你可以访问得到。",[14,10387,10388],{"id":10388},"call实现",[96,10390,10392],{"className":3259,"code":10391,"language":3261,"meta":101,"style":101},"export {}\n\ndeclare global {\n  interface Function {\n    customBind(thisArg: any, ...args: any[]): (...innerArgs: any[]) => any\n  }\n}\n\nFunction.prototype.customBind = function (thisArg: any, ...args: any[]) {\n  const originalFn = this\n\n  if (typeof originalFn !== 'function') {\n    throw new TypeError('Function.prototype.customBind is not a function')\n  }\n\n  function boundFn(...innerArgs: any[]) {\n    const totalArgs = args.concat(innerArgs)\n\n    \u002F\u002F new 调用：忽略 thisArg，执行 new originalFn\n    if (new.target) {\n      return new (originalFn as any)(...totalArgs)\n    }\n\n    \u002F\u002F 普通调用：绑定 thisArg\n    return originalFn.apply(thisArg, totalArgs)\n  }\n\n  \u002F\u002F 不设置 boundFn.prototype，保持和原生 bind 一致\n  return boundFn\n}\n\n\u002F\u002F 测试\nconst obj = {\n    name: \"John\",\n    sayHello: function() {\n        console.log(`Hello, ${this.name}`);\n    }\n}\nconst sayHello = obj.sayHello.customBind(obj);\nsayHello(); \u002F\u002F Hello, John\nconst sayHello2 = obj.sayHello.bind(obj);\nsayHello2(); \u002F\u002F Hello, John\n",[24,10393,10394,10401,10405,10412,10421,10469,10473,10477,10481,10520,10532,10536,10554,10571,10575,10579,10598,10616,10620,10625,10640,10663,10667,10671,10676,10690,10694,10698,10703,10710,10714,10718,10723,10733,10743,10754,10776,10780,10784,10801,10812,10828],{"__ignoreMap":101},[105,10395,10396,10398],{"class":107,"line":108},[105,10397,4997],{"class":111},[105,10399,10400],{"class":115}," {}\n",[105,10402,10403],{"class":107,"line":119},[105,10404,307],{"emptyLinePlaceholder":306},[105,10406,10407,10409],{"class":107,"line":133},[105,10408,10352],{"class":111},[105,10410,10411],{"class":115}," global {\n",[105,10413,10414,10417,10419],{"class":107,"line":148},[105,10415,10416],{"class":111},"  interface",[105,10418,10302],{"class":241},[105,10420,116],{"class":115},[105,10422,10423,10426,10428,10431,10433,10436,10438,10440,10442,10444,10446,10448,10450,10452,10454,10457,10459,10461,10464,10466],{"class":107,"line":163},[105,10424,10425],{"class":241},"    customBind",[105,10427,321],{"class":115},[105,10429,10430],{"class":122},"thisArg",[105,10432,5017],{"class":111},[105,10434,10435],{"class":129}," any",[105,10437,286],{"class":115},[105,10439,10287],{"class":111},[105,10441,10290],{"class":122},[105,10443,5017],{"class":111},[105,10445,10435],{"class":129},[105,10447,10297],{"class":115},[105,10449,5017],{"class":111},[105,10451,4379],{"class":115},[105,10453,10287],{"class":111},[105,10455,10456],{"class":122},"innerArgs",[105,10458,5017],{"class":111},[105,10460,10435],{"class":129},[105,10462,10463],{"class":115},"[]) ",[105,10465,723],{"class":111},[105,10467,10468],{"class":129}," any\n",[105,10470,10471],{"class":107,"line":179},[105,10472,509],{"class":115},[105,10474,10475],{"class":107,"line":194},[105,10476,212],{"class":115},[105,10478,10479],{"class":107,"line":209},[105,10480,307],{"emptyLinePlaceholder":306},[105,10482,10483,10486,10488,10490,10492,10495,10497,10499,10501,10503,10505,10507,10509,10511,10513,10515,10517],{"class":107,"line":526},[105,10484,10485],{"class":129},"Function",[105,10487,5381],{"class":115},[105,10489,8257],{"class":129},[105,10491,5381],{"class":115},[105,10493,10494],{"class":241},"customBind",[105,10496,574],{"class":111},[105,10498,5000],{"class":111},[105,10500,4379],{"class":115},[105,10502,10430],{"class":122},[105,10504,5017],{"class":111},[105,10506,10435],{"class":129},[105,10508,286],{"class":115},[105,10510,10287],{"class":111},[105,10512,10290],{"class":122},[105,10514,5017],{"class":111},[105,10516,10435],{"class":129},[105,10518,10519],{"class":115},"[]) {\n",[105,10521,10522,10524,10527,10529],{"class":107,"line":531},[105,10523,4119],{"class":111},[105,10525,10526],{"class":129}," originalFn",[105,10528,574],{"class":111},[105,10530,10531],{"class":129}," this\n",[105,10533,10534],{"class":107,"line":668},[105,10535,307],{"emptyLinePlaceholder":306},[105,10537,10538,10540,10542,10544,10547,10549,10552],{"class":107,"line":673},[105,10539,3326],{"class":111},[105,10541,4379],{"class":115},[105,10543,8060],{"class":111},[105,10545,10546],{"class":115}," originalFn ",[105,10548,3332],{"class":111},[105,10550,10551],{"class":248}," 'function'",[105,10553,1000],{"class":115},[105,10555,10556,10559,10561,10564,10566,10569],{"class":107,"line":679},[105,10557,10558],{"class":111},"    throw",[105,10560,3496],{"class":111},[105,10562,10563],{"class":241}," TypeError",[105,10565,321],{"class":115},[105,10567,10568],{"class":248},"'Function.prototype.customBind is not a function'",[105,10570,1529],{"class":115},[105,10572,10573],{"class":107,"line":689},[105,10574,509],{"class":115},[105,10576,10577],{"class":107,"line":699},[105,10578,307],{"emptyLinePlaceholder":306},[105,10580,10581,10583,10586,10588,10590,10592,10594,10596],{"class":107,"line":704},[105,10582,5205],{"class":111},[105,10584,10585],{"class":241}," boundFn",[105,10587,321],{"class":115},[105,10589,10287],{"class":111},[105,10591,10456],{"class":122},[105,10593,5017],{"class":111},[105,10595,10435],{"class":129},[105,10597,10519],{"class":115},[105,10599,10600,10602,10605,10607,10610,10613],{"class":107,"line":710},[105,10601,7238],{"class":111},[105,10603,10604],{"class":129}," totalArgs",[105,10606,574],{"class":111},[105,10608,10609],{"class":115}," args.",[105,10611,10612],{"class":241},"concat",[105,10614,10615],{"class":115},"(innerArgs)\n",[105,10617,10618],{"class":107,"line":1136},[105,10619,307],{"emptyLinePlaceholder":306},[105,10621,10622],{"class":107,"line":1149},[105,10623,10624],{"class":630},"    \u002F\u002F new 调用：忽略 thisArg，执行 new originalFn\n",[105,10626,10627,10629,10631,10633,10635,10638],{"class":107,"line":3855},[105,10628,7263],{"class":111},[105,10630,4379],{"class":115},[105,10632,7430],{"class":111},[105,10634,5381],{"class":115},[105,10636,10637],{"class":129},"target",[105,10639,1000],{"class":115},[105,10641,10642,10645,10647,10650,10653,10655,10658,10660],{"class":107,"line":3865},[105,10643,10644],{"class":111},"      return",[105,10646,3496],{"class":111},[105,10648,10649],{"class":115}," (originalFn ",[105,10651,10652],{"class":111},"as",[105,10654,10435],{"class":129},[105,10656,10657],{"class":115},")(",[105,10659,10287],{"class":111},[105,10661,10662],{"class":115},"totalArgs)\n",[105,10664,10665],{"class":107,"line":3875},[105,10666,5161],{"class":115},[105,10668,10669],{"class":107,"line":3884},[105,10670,307],{"emptyLinePlaceholder":306},[105,10672,10673],{"class":107,"line":3889},[105,10674,10675],{"class":630},"    \u002F\u002F 普通调用：绑定 thisArg\n",[105,10677,10678,10681,10684,10687],{"class":107,"line":3900},[105,10679,10680],{"class":111},"    return",[105,10682,10683],{"class":115}," originalFn.",[105,10685,10686],{"class":241},"apply",[105,10688,10689],{"class":115},"(thisArg, totalArgs)\n",[105,10691,10692],{"class":107,"line":3917},[105,10693,509],{"class":115},[105,10695,10696],{"class":107,"line":3922},[105,10697,307],{"emptyLinePlaceholder":306},[105,10699,10700],{"class":107,"line":3938},[105,10701,10702],{"class":630},"  \u002F\u002F 不设置 boundFn.prototype，保持和原生 bind 一致\n",[105,10704,10705,10707],{"class":107,"line":3954},[105,10706,4144],{"class":111},[105,10708,10709],{"class":115}," boundFn\n",[105,10711,10712],{"class":107,"line":3965},[105,10713,212],{"class":115},[105,10715,10716],{"class":107,"line":3979},[105,10717,307],{"emptyLinePlaceholder":306},[105,10719,10720],{"class":107,"line":3985},[105,10721,10722],{"class":630},"\u002F\u002F 测试\n",[105,10724,10725,10727,10729,10731],{"class":107,"line":3990},[105,10726,568],{"class":111},[105,10728,10206],{"class":129},[105,10730,574],{"class":111},[105,10732,116],{"class":115},[105,10734,10735,10738,10741],{"class":107,"line":4010},[105,10736,10737],{"class":115},"    name: ",[105,10739,10740],{"class":248},"\"John\"",[105,10742,2867],{"class":115},[105,10744,10745,10748,10750,10752],{"class":107,"line":4028},[105,10746,10747],{"class":241},"    sayHello",[105,10749,126],{"class":115},[105,10751,8131],{"class":111},[105,10753,9247],{"class":115},[105,10755,10756,10759,10761,10763,10766,10768,10770,10772,10774],{"class":107,"line":4046},[105,10757,10758],{"class":115},"        console.",[105,10760,639],{"class":241},[105,10762,321],{"class":115},[105,10764,10765],{"class":248},"`Hello, ${",[105,10767,2888],{"class":129},[105,10769,5381],{"class":248},[105,10771,5014],{"class":115},[105,10773,6870],{"class":248},[105,10775,327],{"class":115},[105,10777,10778],{"class":107,"line":4051},[105,10779,5161],{"class":115},[105,10781,10782],{"class":107,"line":4057},[105,10783,212],{"class":115},[105,10785,10786,10788,10791,10793,10796,10798],{"class":107,"line":4087},[105,10787,568],{"class":111},[105,10789,10790],{"class":129}," sayHello",[105,10792,574],{"class":111},[105,10794,10795],{"class":115}," obj.sayHello.",[105,10797,10494],{"class":241},[105,10799,10800],{"class":115},"(obj);\n",[105,10802,10803,10806,10809],{"class":107,"line":4092},[105,10804,10805],{"class":241},"sayHello",[105,10807,10808],{"class":115},"(); ",[105,10810,10811],{"class":630},"\u002F\u002F Hello, John\n",[105,10813,10814,10816,10819,10821,10823,10826],{"class":107,"line":4098},[105,10815,568],{"class":111},[105,10817,10818],{"class":129}," sayHello2",[105,10820,574],{"class":111},[105,10822,10795],{"class":115},[105,10824,10825],{"class":241},"bind",[105,10827,10800],{"class":115},[105,10829,10830,10833,10835],{"class":107,"line":4116},[105,10831,10832],{"class":241},"sayHello2",[105,10834,10808],{"class":115},[105,10836,10811],{"class":630},[18,10838,10839],{},"这里实现非常简单，唯一有些新的是new.target属性，这个是ES6引入的原属性，只能在函数内部使用，值取决于函数怎么被调用。",[18,10841,10842],{},"如果一个函数是new Foo()这样被调用的话，那么new.target代表Foo函数本身，如果是Foo()调用的话，那么new.target代表undefined，所以说这个是用来区分构造调用和普通调用。",[18,10844,10845],{},"如果调用方式是：",[96,10847,10849],{"className":559,"code":10848,"language":561,"meta":101,"style":101},"const BoundPerson = Person.customBind({name: 'ignored'})\nconst p = new BoundPerson('real')\n\n而不能写成：\nif (this instanceof boundFn) {\n  return new originalFn(...totalArgs)\n}\n",[24,10850,10851,10873,10892,10896,10901,10916,10930],{"__ignoreMap":101},[105,10852,10853,10855,10858,10860,10863,10865,10868,10871],{"class":107,"line":108},[105,10854,568],{"class":111},[105,10856,10857],{"class":129}," BoundPerson",[105,10859,574],{"class":111},[105,10861,10862],{"class":115}," Person.",[105,10864,10494],{"class":241},[105,10866,10867],{"class":115},"({name: ",[105,10869,10870],{"class":248},"'ignored'",[105,10872,2906],{"class":115},[105,10874,10875,10877,10879,10881,10883,10885,10887,10890],{"class":107,"line":119},[105,10876,568],{"class":111},[105,10878,1088],{"class":129},[105,10880,574],{"class":111},[105,10882,3496],{"class":111},[105,10884,10857],{"class":241},[105,10886,321],{"class":115},[105,10888,10889],{"class":248},"'real'",[105,10891,1529],{"class":115},[105,10893,10894],{"class":107,"line":133},[105,10895,307],{"emptyLinePlaceholder":306},[105,10897,10898],{"class":107,"line":148},[105,10899,10900],{"class":115},"而不能写成：\n",[105,10902,10903,10905,10907,10909,10912,10914],{"class":107,"line":163},[105,10904,5496],{"class":111},[105,10906,4379],{"class":115},[105,10908,2888],{"class":129},[105,10910,10911],{"class":111}," instanceof",[105,10913,10585],{"class":241},[105,10915,1000],{"class":115},[105,10917,10918,10920,10922,10924,10926,10928],{"class":107,"line":179},[105,10919,4144],{"class":111},[105,10921,3496],{"class":111},[105,10923,10526],{"class":241},[105,10925,321],{"class":115},[105,10927,10287],{"class":111},[105,10929,10662],{"class":115},[105,10931,10932],{"class":107,"line":194},[105,10933,212],{"class":115},[18,10935,10936],{},"这里的调用方式会让new.target为BoundPerson，这样thisArgs就会被忽略了。如果写成了下面那种样子，this在new调用的时候是新创建的对象，它的原型是boundFn.prototype，如果boundFn.prototype是undefined，instanceof就会报错。",[14,10938,8263],{"id":8263},[18,10940,10941],{},"call的作用是立即调用一个函数，并手动指定函数内部的this指向，参数是逐个传入的。",[96,10943,10945],{"className":559,"code":10944,"language":561,"meta":101,"style":101},"function greet(greeting, punctuation) {\n  console.log(`${greeting}, ${this.name}${punctuation}`)\n}\n\nconst user = { name: '张三' }\n\ngreet.call(user, 'Hello', '!')  \u002F\u002F 'Hello, 张三!'\n",[24,10946,10947,10966,10997,11001,11005,11020,11024],{"__ignoreMap":101},[105,10948,10949,10951,10954,10956,10959,10961,10964],{"class":107,"line":108},[105,10950,8131],{"class":111},[105,10952,10953],{"class":241}," greet",[105,10955,321],{"class":115},[105,10957,10958],{"class":122},"greeting",[105,10960,286],{"class":115},[105,10962,10963],{"class":122},"punctuation",[105,10965,1000],{"class":115},[105,10967,10968,10970,10972,10974,10977,10979,10982,10984,10986,10988,10991,10993,10995],{"class":107,"line":119},[105,10969,3348],{"class":115},[105,10971,639],{"class":241},[105,10973,321],{"class":115},[105,10975,10976],{"class":248},"`${",[105,10978,10958],{"class":115},[105,10980,10981],{"class":248},"}, ${",[105,10983,2888],{"class":129},[105,10985,5381],{"class":248},[105,10987,5014],{"class":115},[105,10989,10990],{"class":248},"}${",[105,10992,10963],{"class":115},[105,10994,6870],{"class":248},[105,10996,1529],{"class":115},[105,10998,10999],{"class":107,"line":133},[105,11000,212],{"class":115},[105,11002,11003],{"class":107,"line":148},[105,11004,307],{"emptyLinePlaceholder":306},[105,11006,11007,11009,11011,11013,11016,11018],{"class":107,"line":163},[105,11008,568],{"class":111},[105,11010,5423],{"class":129},[105,11012,574],{"class":111},[105,11014,11015],{"class":115}," { name: ",[105,11017,10217],{"class":248},[105,11019,617],{"class":115},[105,11021,11022],{"class":107,"line":179},[105,11023,307],{"emptyLinePlaceholder":306},[105,11025,11026,11029,11031,11034,11037,11039,11042,11044],{"class":107,"line":194},[105,11027,11028],{"class":115},"greet.",[105,11030,8263],{"class":241},[105,11032,11033],{"class":115},"(user, ",[105,11035,11036],{"class":248},"'Hello'",[105,11038,286],{"class":115},[105,11040,11041],{"class":248},"'!'",[105,11043,5440],{"class":115},[105,11045,11046],{"class":630},"\u002F\u002F 'Hello, 张三!'\n",[18,11048,11049],{},"我们自己的实现大致如下：",[96,11051,11053],{"className":3259,"code":11052,"language":3261,"meta":101,"style":101},"export {}\n\ndeclare global {\n    interface Function {\n        customCall: (thisArgs : any, ...args: any[]) => any\n    }\n}\n\nFunction.prototype.customCall = function(thisArgs, ...args) {\n    thisArgs = thisArgs ?? globalThis\n    if (typeof thisArgs !== 'object' && typeof thisArgs !== 'function') {\n        thisArgs = Object(thisArgs)\n    }\n    const tempKey = Symbol('temp')\n    thisArgs[tempKey] = this\n    try{\n        return thisArgs[tempKey](...args)\n    } finally {\n        delete thisArgs[tempKey]\n    }\n}\n",[24,11054,11055,11061,11065,11071,11080,11113,11117,11121,11125,11154,11170,11199,11211,11215,11234,11243,11250,11263,11273,11281,11285],{"__ignoreMap":101},[105,11056,11057,11059],{"class":107,"line":108},[105,11058,4997],{"class":111},[105,11060,10400],{"class":115},[105,11062,11063],{"class":107,"line":119},[105,11064,307],{"emptyLinePlaceholder":306},[105,11066,11067,11069],{"class":107,"line":133},[105,11068,10352],{"class":111},[105,11070,10411],{"class":115},[105,11072,11073,11076,11078],{"class":107,"line":148},[105,11074,11075],{"class":111},"    interface",[105,11077,10302],{"class":241},[105,11079,116],{"class":115},[105,11081,11082,11085,11087,11089,11092,11095,11097,11099,11101,11103,11105,11107,11109,11111],{"class":107,"line":163},[105,11083,11084],{"class":241},"        customCall",[105,11086,5017],{"class":111},[105,11088,4379],{"class":115},[105,11090,11091],{"class":122},"thisArgs",[105,11093,11094],{"class":111}," :",[105,11096,10435],{"class":129},[105,11098,286],{"class":115},[105,11100,10287],{"class":111},[105,11102,10290],{"class":122},[105,11104,5017],{"class":111},[105,11106,10435],{"class":129},[105,11108,10463],{"class":115},[105,11110,723],{"class":111},[105,11112,10468],{"class":129},[105,11114,11115],{"class":107,"line":179},[105,11116,5161],{"class":115},[105,11118,11119],{"class":107,"line":194},[105,11120,212],{"class":115},[105,11122,11123],{"class":107,"line":209},[105,11124,307],{"emptyLinePlaceholder":306},[105,11126,11127,11129,11131,11133,11135,11138,11140,11142,11144,11146,11148,11150,11152],{"class":107,"line":526},[105,11128,10485],{"class":129},[105,11130,5381],{"class":115},[105,11132,8257],{"class":129},[105,11134,5381],{"class":115},[105,11136,11137],{"class":241},"customCall",[105,11139,574],{"class":111},[105,11141,5000],{"class":111},[105,11143,321],{"class":115},[105,11145,11091],{"class":122},[105,11147,286],{"class":115},[105,11149,10287],{"class":111},[105,11151,10290],{"class":122},[105,11153,1000],{"class":115},[105,11155,11156,11159,11161,11164,11167],{"class":107,"line":531},[105,11157,11158],{"class":115},"    thisArgs ",[105,11160,245],{"class":111},[105,11162,11163],{"class":115}," thisArgs ",[105,11165,11166],{"class":111},"??",[105,11168,11169],{"class":115}," globalThis\n",[105,11171,11172,11174,11176,11178,11180,11182,11185,11188,11191,11193,11195,11197],{"class":107,"line":668},[105,11173,7263],{"class":111},[105,11175,4379],{"class":115},[105,11177,8060],{"class":111},[105,11179,11163],{"class":115},[105,11181,3332],{"class":111},[105,11183,11184],{"class":248}," 'object'",[105,11186,11187],{"class":111}," &&",[105,11189,11190],{"class":111}," typeof",[105,11192,11163],{"class":115},[105,11194,3332],{"class":111},[105,11196,10551],{"class":248},[105,11198,1000],{"class":115},[105,11200,11201,11204,11206,11208],{"class":107,"line":673},[105,11202,11203],{"class":115},"        thisArgs ",[105,11205,245],{"class":111},[105,11207,8465],{"class":241},[105,11209,11210],{"class":115},"(thisArgs)\n",[105,11212,11213],{"class":107,"line":679},[105,11214,5161],{"class":115},[105,11216,11217,11219,11222,11224,11227,11229,11232],{"class":107,"line":689},[105,11218,7238],{"class":111},[105,11220,11221],{"class":129}," tempKey",[105,11223,574],{"class":111},[105,11225,11226],{"class":241}," Symbol",[105,11228,321],{"class":115},[105,11230,11231],{"class":248},"'temp'",[105,11233,1529],{"class":115},[105,11235,11236,11239,11241],{"class":107,"line":699},[105,11237,11238],{"class":115},"    thisArgs[tempKey] ",[105,11240,245],{"class":111},[105,11242,10531],{"class":129},[105,11244,11245,11248],{"class":107,"line":704},[105,11246,11247],{"class":111},"    try",[105,11249,9866],{"class":115},[105,11251,11252,11255,11258,11260],{"class":107,"line":710},[105,11253,11254],{"class":111},"        return",[105,11256,11257],{"class":115}," thisArgs[tempKey](",[105,11259,10287],{"class":111},[105,11261,11262],{"class":115},"args)\n",[105,11264,11265,11268,11271],{"class":107,"line":1136},[105,11266,11267],{"class":115},"    } ",[105,11269,11270],{"class":111},"finally",[105,11272,116],{"class":115},[105,11274,11275,11278],{"class":107,"line":1149},[105,11276,11277],{"class":111},"        delete",[105,11279,11280],{"class":115}," thisArgs[tempKey]\n",[105,11282,11283],{"class":107,"line":3855},[105,11284,5161],{"class":115},[105,11286,11287],{"class":107,"line":3865},[105,11288,212],{"class":115},[18,11290,11291,11292,11295],{},"apply和call有一点细致差别，仅仅是传递的非...args而是没展开的数组，直接传入 ",[24,11293,11294],{},"[...args]","即可。",[14,11297,11298],{"id":11298},"new的实现",[18,11300,11301,11302,11304,11305,11308],{},"现 ",[24,11303,7430],{}," 是理解 JS 原型链和构造函数机制的经典练习，一个new分为经典的四步，首先是创建一个新对象obj，然后把obj的原型指向Foo.prototype (obj.",[21,11306,11307],{},"proto"," === Foo.prototype)，紧接以obj为this，执行Foo，传入参数，判断Foo的返回值，如果返回值是对象或者函数，那么就用这个返回值，如果不是，就返回原始值\u002F无返回值，用之前创建的obj。",[96,11310,11312],{"className":3259,"code":11311,"language":3261,"meta":101,"style":101},"export function mynew(Constructor: Function, ...args: any[]) {\n    const obj = Object.create(Constructor.prototype)\n    const result = Constructor.apply(obj, args) \u002F\u002F constructor.\n    if (result !== null && (typeof result === 'object' || typeof result === 'function')) return result\n    return obj\n}\n",[24,11313,11314,11344,11368,11387,11430,11437],{"__ignoreMap":101},[105,11315,11316,11318,11320,11323,11325,11328,11330,11332,11334,11336,11338,11340,11342],{"class":107,"line":108},[105,11317,4997],{"class":111},[105,11319,5000],{"class":111},[105,11321,11322],{"class":241}," mynew",[105,11324,321],{"class":115},[105,11326,11327],{"class":241},"Constructor",[105,11329,5017],{"class":111},[105,11331,10302],{"class":241},[105,11333,286],{"class":115},[105,11335,10287],{"class":111},[105,11337,10290],{"class":122},[105,11339,5017],{"class":111},[105,11341,10435],{"class":129},[105,11343,10519],{"class":115},[105,11345,11346,11348,11350,11352,11355,11358,11360,11362,11364,11366],{"class":107,"line":119},[105,11347,7238],{"class":111},[105,11349,10206],{"class":129},[105,11351,574],{"class":111},[105,11353,11354],{"class":115}," Object.",[105,11356,11357],{"class":241},"create",[105,11359,321],{"class":115},[105,11361,11327],{"class":129},[105,11363,5381],{"class":115},[105,11365,8257],{"class":129},[105,11367,1529],{"class":115},[105,11369,11370,11372,11374,11376,11379,11381,11384],{"class":107,"line":133},[105,11371,7238],{"class":111},[105,11373,7287],{"class":129},[105,11375,574],{"class":111},[105,11377,11378],{"class":115}," Constructor.",[105,11380,10686],{"class":241},[105,11382,11383],{"class":115},"(obj, args) ",[105,11385,11386],{"class":630},"\u002F\u002F constructor.\n",[105,11388,11389,11391,11394,11396,11398,11400,11402,11404,11407,11409,11411,11414,11416,11418,11420,11422,11425,11427],{"class":107,"line":148},[105,11390,7263],{"class":111},[105,11392,11393],{"class":115}," (result ",[105,11395,3332],{"class":111},[105,11397,5044],{"class":129},[105,11399,11187],{"class":111},[105,11401,4379],{"class":115},[105,11403,8060],{"class":111},[105,11405,11406],{"class":115}," result ",[105,11408,7269],{"class":111},[105,11410,11184],{"class":248},[105,11412,11413],{"class":111}," ||",[105,11415,11190],{"class":111},[105,11417,11406],{"class":115},[105,11419,7269],{"class":111},[105,11421,10551],{"class":248},[105,11423,11424],{"class":115},")) ",[105,11426,3340],{"class":111},[105,11428,11429],{"class":115}," result\n",[105,11431,11432,11434],{"class":107,"line":163},[105,11433,10680],{"class":111},[105,11435,11436],{"class":115}," obj\n",[105,11438,11439],{"class":107,"line":179},[105,11440,212],{"class":115},[18,11442,11443,11444,11446,11447,11449,11450,11453,11454,11456],{},"前面我们已知，Function是一个特殊的东西，它自己是自己的构造函数，Function.",[21,11445,11307],{}," === Function.prototype，而Function.prototype.",[21,11448,11307],{}," === Object.prototype。一般对象的 ",[24,11451,11452],{},"__proto__"," 指向\"创建它的构造函数的 ",[24,11455,8257],{},"\"，我们都知道prototype一般是obj，但是Function.prototype 类型是function，这是唯一的例外。",[18,11458,11459,11462,11463,11466,11467],{},[24,11460,11461],{},"Function.prototype.__proto__"," 指向 ",[24,11464,11465],{},"Object.prototype","也只是刚好说明了函数也是对象，所以原型链指向最终的Object.prototype。",[21,11468,11469,11470,11472,11473,11476,11477,11479,11480,11483,11484,11486,11487,91],{},"默认情况下，用 ",[24,11471,8131],{}," 声明的函数，它的 ",[24,11474,11475],{},"constructor"," 都是 ",[24,11478,10485],{},"，它的 ",[24,11481,11482],{},"[[Prototype]]","（即 ",[24,11485,11452],{},"）都指向 ",[24,11488,11489],{},"Function.prototype",[14,11491,11492],{"id":11492},"promise",[18,11494,11495,11496,11501],{},"ECMA-262标准在Promise instance一节中，明确规定了Promise实例必须包含的核心内部槽，其中包括PromiseState： 一个字符串值，取值明确限定为pending，fulfilled、rejected三种状态，这个正式状态机的状态集合。然后是PromiseResult，用于存储promise完成的值，仅当PromiseState不为pending的时候才有意义。然后是一个PromiseFulfillReactions列表，存储当promise从pending转移到fulfilled的时候需要处理的记录，也就是注册的then回调。",[21,11497,11498],{},[24,11499,11500],{},"PromiseRejectReactions"," 是一个列表，用于存储当Promise从pending转移到reject的时候需要处理的记录。",[18,11503,11504],{},"标准的抽象操作定义了状态如果迁移，fulfillPromise可以讲一个状态为pending的promise转变为fulfilled，并设置PromiseResult，同时触发PromiseFulfillReactions中的任务。RejectPromise讲一个状态为pending的Promise转变为rejected。",[18,11506,11507,11508,11511,11512,1998,11515,11511,11517,11520],{},"这些定义构成了一个严格的单向状态机：",[24,11509,11510],{},"pending"," → ",[24,11513,11514],{},"fulfilled",[24,11516,11510],{},[24,11518,11519],{},"rejected","，且状态一旦改变便不可逆。",[18,11522,11523],{},"机制是十分简单的，只是说签名稍微复杂一点，特别是then方法等的签名，这里我先写出来：",[96,11525,11527],{"className":3259,"code":11526,"language":3261,"meta":101,"style":101},"    then\u003CTResult1 = T, TResult2 = never>(\n        onFulfilled?: ((value: T) => TResult1 | CustomPromise\u003CTResult1>) | null,\n        onRejected?: ((reason: any) => TResult2 | CustomPromise\u003CTResult2>) | null\n    ): CustomPromise\u003CTResult1 | TResult2>;\n",[24,11528,11529,11555,11600,11641],{"__ignoreMap":101},[105,11530,11531,11534,11536,11539,11541,11543,11546,11548,11551,11553],{"class":107,"line":108},[105,11532,11533],{"class":115},"    then",[105,11535,234],{"class":111},[105,11537,11538],{"class":115},"TResult1 ",[105,11540,245],{"class":111},[105,11542,5075],{"class":129},[105,11544,11545],{"class":115},", TResult2 ",[105,11547,245],{"class":111},[105,11549,11550],{"class":115}," never",[105,11552,1645],{"class":111},[105,11554,583],{"class":115},[105,11556,11557,11560,11563,11566,11568,11570,11572,11574,11576,11579,11582,11585,11587,11590,11592,11594,11596,11598],{"class":107,"line":119},[105,11558,11559],{"class":115},"        onFulfilled",[105,11561,11562],{"class":111},"?:",[105,11564,11565],{"class":115}," ((",[105,11567,8451],{"class":122},[105,11569,5017],{"class":111},[105,11571,5075],{"class":241},[105,11573,2854],{"class":115},[105,11575,723],{"class":111},[105,11577,11578],{"class":115}," TResult1 ",[105,11580,11581],{"class":111},"|",[105,11583,11584],{"class":115}," CustomPromise",[105,11586,234],{"class":111},[105,11588,11589],{"class":115},"TResult1",[105,11591,1645],{"class":111},[105,11593,2854],{"class":115},[105,11595,11581],{"class":111},[105,11597,5044],{"class":129},[105,11599,2867],{"class":115},[105,11601,11602,11605,11607,11609,11612,11614,11616,11618,11620,11623,11625,11627,11629,11632,11634,11636,11638],{"class":107,"line":133},[105,11603,11604],{"class":115},"        onRejected",[105,11606,11562],{"class":111},[105,11608,11565],{"class":115},[105,11610,11611],{"class":122},"reason",[105,11613,5017],{"class":111},[105,11615,10435],{"class":129},[105,11617,2854],{"class":115},[105,11619,723],{"class":111},[105,11621,11622],{"class":115}," TResult2 ",[105,11624,11581],{"class":111},[105,11626,11584],{"class":115},[105,11628,234],{"class":111},[105,11630,11631],{"class":115},"TResult2",[105,11633,1645],{"class":111},[105,11635,2854],{"class":115},[105,11637,11581],{"class":111},[105,11639,11640],{"class":129}," null\n",[105,11642,11643,11646,11648,11650,11652,11655,11657],{"class":107,"line":148},[105,11644,11645],{"class":115},"    ): CustomPromise",[105,11647,234],{"class":111},[105,11649,11538],{"class":115},[105,11651,11581],{"class":111},[105,11653,11654],{"class":115}," TResult2",[105,11656,1645],{"class":111},[105,11658,145],{"class":115},[18,11660,11661,11662,9075],{},"可以看到，then方法是接受一个回调函数，可以在回调函数里注册onFulfilled和onRejected，然后返回的是我们自己的Promise类型，拆开 ",[24,11663,11664],{},"onFulfilled",[96,11666,11668],{"className":3259,"code":11667,"language":3261,"meta":101,"style":101},"((value: T) => TResult1 | CustomPromise\u003CTResult1>) | null | undefined\n",[24,11669,11670],{"__ignoreMap":101},[105,11671,11672,11674,11676,11678,11680,11682,11684,11686,11688,11690,11692,11694,11696,11698,11700,11702,11704],{"class":107,"line":108},[105,11673,7298],{"class":115},[105,11675,8451],{"class":122},[105,11677,5017],{"class":111},[105,11679,5075],{"class":241},[105,11681,2854],{"class":115},[105,11683,723],{"class":111},[105,11685,11578],{"class":115},[105,11687,11581],{"class":111},[105,11689,11584],{"class":115},[105,11691,234],{"class":111},[105,11693,11589],{"class":115},[105,11695,1645],{"class":111},[105,11697,2854],{"class":115},[105,11699,11581],{"class":111},[105,11701,5044],{"class":129},[105,11703,5041],{"class":111},[105,11705,11706],{"class":129}," undefined\n",[18,11708,11709,11710,9383,11713,11716,11717,11719],{},"可能是一个函数，可能是null和undefined，类型上onFulfilled只可能是这些，但是T和Promise不在这个类型里面，它们出现在这个函数的",[21,11711,11712],{},"参数",[21,11714,11715],{},"返回值","里，不是 ",[24,11718,11664],{}," 本身。",[18,11721,11722],{},"这里需要复习常见类型在布尔运算里的真值表：",[835,11724,11725,11742],{},[838,11726,11727],{},[841,11728,11729,11731,11737],{},[844,11730,1163],{},[844,11732,11733,11736],{},[24,11734,11735],{},"if (x)"," 为真分支",[844,11738,11739,11741],{},[24,11740,11735],{}," 为假分支",[854,11743,11744,11760,11777,11791,11803],{},[841,11745,11746,11751,11756],{},[859,11747,11748],{},[24,11749,11750],{},"unknown",[859,11752,11753,11754],{},"不变，仍是 ",[24,11755,11750],{},[859,11757,11753,11758],{},[24,11759,11750],{},[841,11761,11762,11767,11773],{},[859,11763,11764],{},[24,11765,11766],{},"never",[859,11768,11769,11772],{},[21,11770,11771],{},"不进入","（分支不可达）",[859,11774,11775,11772],{},[21,11776,11771],{},[841,11778,11779,11783,11786],{},[859,11780,11781],{},[24,11782,4005],{},[859,11784,11785],{},"排除了",[859,11787,11788,11789],{},"只剩 ",[24,11790,4005],{},[841,11792,11793,11797,11799],{},[859,11794,11795],{},[24,11796,8397],{},[859,11798,11785],{},[859,11800,11788,11801],{},[24,11802,8397],{},[841,11804,11805,11810,11812],{},[859,11806,11807],{},[24,11808,11809],{},"null | undefined",[859,11811,11785],{},[859,11813,11814,11815],{},"剩 ",[24,11816,11809],{},[96,11818,11820],{"className":3259,"code":11819,"language":3261,"meta":101,"style":101},"type PromiseState = 'pending' | 'fulfilled' | 'rejected';\n\ninterface CustomPromise\u003CT> {\n    state: PromiseState;\n    result: T | undefined;\n    PromiseFulfillReactions: ((value: any) => void)[];\n    PromiseRejectReactions: ((reason: any) => void)[];\n\n    resolve(value: T): void;\n    reject(reason?: any): void;\n\n    then\u003CTResult1 = T, TResult2 = never>(\n        onFulfilled?: ((value: T) => TResult1 | CustomPromise\u003CTResult1>) | null,\n        onRejected?: ((reason: any) => TResult2 | CustomPromise\u003CTResult2>) | null\n    ): CustomPromise\u003CTResult1 | TResult2>;\n\n    catch\u003CTResult = never>(\n        onRejected?: ((reason: any) => TResult | CustomPromise\u003CTResult>) | null\n    ): CustomPromise\u003CT | TResult>;\n}\n\n\n\nclass MyPromise\u003CT> implements CustomPromise\u003CT> {\n    state: PromiseState = 'pending';\n    result: T | undefined = undefined;\n    PromiseFulfillReactions: ((value: any) => void)[] = [];\n    PromiseRejectReactions: ((reason: any) => void)[] = [];\n\n    constructor(\n        executor?: (\n            resolve: (value: T) => void,\n            reject: (reason?: any) => void\n        ) => void\n    ) {\n        if (typeof executor === 'function') {\n            try {\n                executor(\n                    (value) => this.resolve(value),\n                    (reason) => this.reject(reason)\n                );\n            } catch (e) {\n                this.reject(e);\n            }\n        }\n    }\n\n    resolve(value: T): void {\n        if (this.state !== 'pending') return;\n        if (value instanceof MyPromise) {\n            value.then(\n                (v) => this.resolve(v as T),\n                (r) => this.reject(r)\n            );\n            return;\n        }\n\n        this.state = 'fulfilled';\n        this.result = value;\n\n        const reactions = this.PromiseFulfillReactions;\n        this.PromiseFulfillReactions = [];\n        this.PromiseRejectReactions = [];\n        for (const reaction of reactions) {\n            queueMicrotask(() => reaction(value));\n        }\n    }\n\n    reject(reason?: any): void {\n        if (this.state !== 'pending') return;\n\n        this.state = 'rejected';\n        this.result = reason;\n\n        const reactions = this.PromiseRejectReactions;\n        this.PromiseFulfillReactions = [];\n        this.PromiseRejectReactions = [];\n        for (const reaction of reactions) {\n            queueMicrotask(() => reaction(reason));\n        }\n    }\n\n    then\u003CTResult1 = T, TResult2 = never>(\n        onFulfilled?: ((value: T) => TResult1 | CustomPromise\u003CTResult1>) | null,\n        onRejected?: ((reason: any) => TResult2 | CustomPromise\u003CTResult2>) | null\n    ): CustomPromise\u003CTResult1 | TResult2> {\n        return new MyPromise\u003CTResult1 | TResult2>((resolve, reject) => {\n            const handlerFulfilled = (value: T) => {\n                if (typeof onFulfilled !== 'function') {\n                    resolve(value as unknown as TResult1);\n                    return;\n                }\n                try {\n                    const r = onFulfilled(value);\n                    if (r instanceof MyPromise) {\n                        r.then(\n                            (v) => resolve(v as TResult1),\n                            (e) => reject(e)\n                        );\n                    } else {\n                        resolve(r as TResult1);\n                    }\n                } catch (e) {\n                    reject(e);\n                }\n            };\n\n            const handlerRejected = (reason: any) => {\n                if (typeof onRejected !== 'function') {\n                    reject(reason);\n                    return;\n                }\n                try {\n                    const r = onRejected(reason);\n                    if (r instanceof MyPromise) {\n                        r.then(\n                            (v) => resolve(v as TResult1),\n                            (e) => reject(e)\n                        );\n                    } else {\n                        resolve(r as unknown as TResult1);\n                    }\n                } catch (e) {\n                    reject(e);\n                }\n            };\n\n            if (this.state === 'pending') {\n                this.PromiseFulfillReactions.push(handlerFulfilled);\n                this.PromiseRejectReactions.push(handlerRejected);\n            } else if (this.state === 'fulfilled') {\n                queueMicrotask(() => handlerFulfilled(this.result as T));\n            } else {\n                queueMicrotask(() => handlerRejected(this.result));\n            }\n        });\n    }\n\n    catch\u003CTResult = never>(\n        onRejected?: ((reason: any) => TResult | CustomPromise\u003CTResult>) | null\n    ): CustomPromise\u003CT | TResult> {\n        return this.then(null, onRejected);\n    }\n\n    static resolve\u003CT>(value: T): MyPromise\u003CT> {\n        return new MyPromise\u003CT>((resolve) => resolve(value));\n    }\n\n    static reject\u003CT = never>(reason?: any): MyPromise\u003CT> {\n        return new MyPromise\u003CT>((_, reject) => reject(reason));\n    }\n}\n\nMyPromise.resolve(10).then(\n    (v) => console.log('fulfilled', v),   \u002F\u002F fulfilled 10\n    (r) => console.log('rejected', r)\n);\n",[24,11821,11822,11846,11850,11863,11874,11890,11914,11937,11941,11963,11984,11988,12011,12049,12083,12103,12107,12123,12158,12176,12180,12184,12188,12192,12218,12232,12250,12278,12304,12308,12315,12325,12348,12370,12379,12384,12402,12409,12416,12437,12457,12462,12472,12484,12489,12494,12498,12502,12522,12543,12556,12565,12593,12613,12617,12623,12627,12631,12644,12656,12660,12674,12685,12696,12714,12728,12732,12736,12740,12760,12780,12784,12796,12807,12811,12824,12834,12844,12858,12871,12875,12879,12883,12905,12941,12975,12993,13024,13047,13065,13084,13091,13096,13103,13119,13133,13142,13164,13180,13185,13195,13209,13214,13223,13230,13234,13239,13243,13266,13283,13290,13296,13300,13306,13319,13331,13339,13359,13373,13377,13386,13403,13408,13417,13424,13429,13434,13439,13456,13470,13483,13505,13530,13539,13557,13562,13568,13573,13578,13593,13628,13647,13665,13670,13675,13707,13732,13737,13742,13777,13807,13812,13817,13822,13841,13867,13890],{"__ignoreMap":101},[105,11823,11824,11826,11829,11831,11834,11836,11839,11841,11844],{"class":107,"line":108},[105,11825,7243],{"class":111},[105,11827,11828],{"class":241}," PromiseState",[105,11830,574],{"class":111},[105,11832,11833],{"class":248}," 'pending'",[105,11835,5041],{"class":111},[105,11837,11838],{"class":248}," 'fulfilled'",[105,11840,5041],{"class":111},[105,11842,11843],{"class":248}," 'rejected'",[105,11845,145],{"class":115},[105,11847,11848],{"class":107,"line":119},[105,11849,307],{"emptyLinePlaceholder":306},[105,11851,11852,11854,11856,11858,11860],{"class":107,"line":133},[105,11853,3519],{"class":111},[105,11855,11584],{"class":241},[105,11857,234],{"class":115},[105,11859,5008],{"class":241},[105,11861,11862],{"class":115},"> {\n",[105,11864,11865,11868,11870,11872],{"class":107,"line":148},[105,11866,11867],{"class":122},"    state",[105,11869,5017],{"class":111},[105,11871,11828],{"class":241},[105,11873,145],{"class":115},[105,11875,11876,11879,11881,11883,11885,11888],{"class":107,"line":163},[105,11877,11878],{"class":122},"    result",[105,11880,5017],{"class":111},[105,11882,5075],{"class":241},[105,11884,5041],{"class":111},[105,11886,11887],{"class":129}," undefined",[105,11889,145],{"class":115},[105,11891,11892,11895,11897,11899,11901,11903,11905,11907,11909,11911],{"class":107,"line":179},[105,11893,11894],{"class":122},"    PromiseFulfillReactions",[105,11896,5017],{"class":111},[105,11898,11565],{"class":115},[105,11900,8451],{"class":122},[105,11902,5017],{"class":111},[105,11904,10435],{"class":129},[105,11906,2854],{"class":115},[105,11908,723],{"class":111},[105,11910,5082],{"class":129},[105,11912,11913],{"class":115},")[];\n",[105,11915,11916,11919,11921,11923,11925,11927,11929,11931,11933,11935],{"class":107,"line":194},[105,11917,11918],{"class":122},"    PromiseRejectReactions",[105,11920,5017],{"class":111},[105,11922,11565],{"class":115},[105,11924,11611],{"class":122},[105,11926,5017],{"class":111},[105,11928,10435],{"class":129},[105,11930,2854],{"class":115},[105,11932,723],{"class":111},[105,11934,5082],{"class":129},[105,11936,11913],{"class":115},[105,11938,11939],{"class":107,"line":209},[105,11940,307],{"emptyLinePlaceholder":306},[105,11942,11943,11946,11948,11950,11952,11954,11957,11959,11961],{"class":107,"line":526},[105,11944,11945],{"class":241},"    resolve",[105,11947,321],{"class":115},[105,11949,8451],{"class":122},[105,11951,5017],{"class":111},[105,11953,5075],{"class":241},[105,11955,11956],{"class":115},")",[105,11958,5017],{"class":111},[105,11960,5082],{"class":129},[105,11962,145],{"class":115},[105,11964,11965,11968,11970,11972,11974,11976,11978,11980,11982],{"class":107,"line":531},[105,11966,11967],{"class":241},"    reject",[105,11969,321],{"class":115},[105,11971,11611],{"class":122},[105,11973,11562],{"class":111},[105,11975,10435],{"class":129},[105,11977,11956],{"class":115},[105,11979,5017],{"class":111},[105,11981,5082],{"class":129},[105,11983,145],{"class":115},[105,11985,11986],{"class":107,"line":668},[105,11987,307],{"emptyLinePlaceholder":306},[105,11989,11990,11992,11994,11996,11998,12000,12002,12004,12006,12008],{"class":107,"line":673},[105,11991,11533],{"class":241},[105,11993,234],{"class":115},[105,11995,11589],{"class":241},[105,11997,574],{"class":111},[105,11999,5075],{"class":241},[105,12001,286],{"class":115},[105,12003,11631],{"class":241},[105,12005,574],{"class":111},[105,12007,11550],{"class":129},[105,12009,12010],{"class":115},">(\n",[105,12012,12013,12015,12017,12019,12021,12023,12025,12027,12029,12032,12034,12036,12038,12040,12043,12045,12047],{"class":107,"line":679},[105,12014,11559],{"class":122},[105,12016,11562],{"class":111},[105,12018,11565],{"class":115},[105,12020,8451],{"class":122},[105,12022,5017],{"class":111},[105,12024,5075],{"class":241},[105,12026,2854],{"class":115},[105,12028,723],{"class":111},[105,12030,12031],{"class":241}," TResult1",[105,12033,5041],{"class":111},[105,12035,11584],{"class":241},[105,12037,234],{"class":115},[105,12039,11589],{"class":241},[105,12041,12042],{"class":115},">) ",[105,12044,11581],{"class":111},[105,12046,5044],{"class":129},[105,12048,2867],{"class":115},[105,12050,12051,12053,12055,12057,12059,12061,12063,12065,12067,12069,12071,12073,12075,12077,12079,12081],{"class":107,"line":689},[105,12052,11604],{"class":122},[105,12054,11562],{"class":111},[105,12056,11565],{"class":115},[105,12058,11611],{"class":122},[105,12060,5017],{"class":111},[105,12062,10435],{"class":129},[105,12064,2854],{"class":115},[105,12066,723],{"class":111},[105,12068,11654],{"class":241},[105,12070,5041],{"class":111},[105,12072,11584],{"class":241},[105,12074,234],{"class":115},[105,12076,11631],{"class":241},[105,12078,12042],{"class":115},[105,12080,11581],{"class":111},[105,12082,11640],{"class":129},[105,12084,12085,12088,12090,12092,12094,12096,12098,12100],{"class":107,"line":699},[105,12086,12087],{"class":115},"    )",[105,12089,5017],{"class":111},[105,12091,11584],{"class":241},[105,12093,234],{"class":115},[105,12095,11589],{"class":241},[105,12097,5041],{"class":111},[105,12099,11654],{"class":241},[105,12101,12102],{"class":115},">;\n",[105,12104,12105],{"class":107,"line":704},[105,12106,307],{"emptyLinePlaceholder":306},[105,12108,12109,12112,12114,12117,12119,12121],{"class":107,"line":710},[105,12110,12111],{"class":241},"    catch",[105,12113,234],{"class":115},[105,12115,12116],{"class":241},"TResult",[105,12118,574],{"class":111},[105,12120,11550],{"class":129},[105,12122,12010],{"class":115},[105,12124,12125,12127,12129,12131,12133,12135,12137,12139,12141,12144,12146,12148,12150,12152,12154,12156],{"class":107,"line":1136},[105,12126,11604],{"class":122},[105,12128,11562],{"class":111},[105,12130,11565],{"class":115},[105,12132,11611],{"class":122},[105,12134,5017],{"class":111},[105,12136,10435],{"class":129},[105,12138,2854],{"class":115},[105,12140,723],{"class":111},[105,12142,12143],{"class":241}," TResult",[105,12145,5041],{"class":111},[105,12147,11584],{"class":241},[105,12149,234],{"class":115},[105,12151,12116],{"class":241},[105,12153,12042],{"class":115},[105,12155,11581],{"class":111},[105,12157,11640],{"class":129},[105,12159,12160,12162,12164,12166,12168,12170,12172,12174],{"class":107,"line":1149},[105,12161,12087],{"class":115},[105,12163,5017],{"class":111},[105,12165,11584],{"class":241},[105,12167,234],{"class":115},[105,12169,5008],{"class":241},[105,12171,5041],{"class":111},[105,12173,12143],{"class":241},[105,12175,12102],{"class":115},[105,12177,12178],{"class":107,"line":3855},[105,12179,212],{"class":115},[105,12181,12182],{"class":107,"line":3865},[105,12183,307],{"emptyLinePlaceholder":306},[105,12185,12186],{"class":107,"line":3875},[105,12187,307],{"emptyLinePlaceholder":306},[105,12189,12190],{"class":107,"line":3884},[105,12191,307],{"emptyLinePlaceholder":306},[105,12193,12194,12197,12200,12202,12204,12207,12210,12212,12214,12216],{"class":107,"line":3889},[105,12195,12196],{"class":111},"class",[105,12198,12199],{"class":241}," MyPromise",[105,12201,234],{"class":115},[105,12203,5008],{"class":241},[105,12205,12206],{"class":115},"> ",[105,12208,12209],{"class":111},"implements",[105,12211,11584],{"class":241},[105,12213,234],{"class":115},[105,12215,5008],{"class":241},[105,12217,11862],{"class":115},[105,12219,12220,12222,12224,12226,12228,12230],{"class":107,"line":3900},[105,12221,11867],{"class":122},[105,12223,5017],{"class":111},[105,12225,11828],{"class":241},[105,12227,574],{"class":111},[105,12229,11833],{"class":248},[105,12231,145],{"class":115},[105,12233,12234,12236,12238,12240,12242,12244,12246,12248],{"class":107,"line":3917},[105,12235,11878],{"class":122},[105,12237,5017],{"class":111},[105,12239,5075],{"class":241},[105,12241,5041],{"class":111},[105,12243,11887],{"class":129},[105,12245,574],{"class":111},[105,12247,11887],{"class":129},[105,12249,145],{"class":115},[105,12251,12252,12254,12256,12258,12260,12262,12264,12266,12268,12270,12273,12275],{"class":107,"line":3922},[105,12253,11894],{"class":122},[105,12255,5017],{"class":111},[105,12257,11565],{"class":115},[105,12259,8451],{"class":122},[105,12261,5017],{"class":111},[105,12263,10435],{"class":129},[105,12265,2854],{"class":115},[105,12267,723],{"class":111},[105,12269,5082],{"class":129},[105,12271,12272],{"class":115},")[] ",[105,12274,245],{"class":111},[105,12276,12277],{"class":115}," [];\n",[105,12279,12280,12282,12284,12286,12288,12290,12292,12294,12296,12298,12300,12302],{"class":107,"line":3938},[105,12281,11918],{"class":122},[105,12283,5017],{"class":111},[105,12285,11565],{"class":115},[105,12287,11611],{"class":122},[105,12289,5017],{"class":111},[105,12291,10435],{"class":129},[105,12293,2854],{"class":115},[105,12295,723],{"class":111},[105,12297,5082],{"class":129},[105,12299,12272],{"class":115},[105,12301,245],{"class":111},[105,12303,12277],{"class":115},[105,12305,12306],{"class":107,"line":3954},[105,12307,307],{"emptyLinePlaceholder":306},[105,12309,12310,12313],{"class":107,"line":3965},[105,12311,12312],{"class":111},"    constructor",[105,12314,583],{"class":115},[105,12316,12317,12320,12322],{"class":107,"line":3979},[105,12318,12319],{"class":241},"        executor",[105,12321,11562],{"class":111},[105,12323,12324],{"class":115}," (\n",[105,12326,12327,12330,12332,12334,12336,12338,12340,12342,12344,12346],{"class":107,"line":3985},[105,12328,12329],{"class":241},"            resolve",[105,12331,5017],{"class":111},[105,12333,4379],{"class":115},[105,12335,8451],{"class":122},[105,12337,5017],{"class":111},[105,12339,5075],{"class":241},[105,12341,2854],{"class":115},[105,12343,723],{"class":111},[105,12345,5082],{"class":129},[105,12347,2867],{"class":115},[105,12349,12350,12353,12355,12357,12359,12361,12363,12365,12367],{"class":107,"line":3990},[105,12351,12352],{"class":241},"            reject",[105,12354,5017],{"class":111},[105,12356,4379],{"class":115},[105,12358,11611],{"class":122},[105,12360,11562],{"class":111},[105,12362,10435],{"class":129},[105,12364,2854],{"class":115},[105,12366,723],{"class":111},[105,12368,12369],{"class":129}," void\n",[105,12371,12372,12375,12377],{"class":107,"line":4010},[105,12373,12374],{"class":115},"        ) ",[105,12376,723],{"class":111},[105,12378,12369],{"class":129},[105,12380,12381],{"class":107,"line":4028},[105,12382,12383],{"class":115},"    ) {\n",[105,12385,12386,12389,12391,12393,12396,12398,12400],{"class":107,"line":4046},[105,12387,12388],{"class":111},"        if",[105,12390,4379],{"class":115},[105,12392,8060],{"class":111},[105,12394,12395],{"class":115}," executor ",[105,12397,7269],{"class":111},[105,12399,10551],{"class":248},[105,12401,1000],{"class":115},[105,12403,12404,12407],{"class":107,"line":4051},[105,12405,12406],{"class":111},"            try",[105,12408,116],{"class":115},[105,12410,12411,12414],{"class":107,"line":4057},[105,12412,12413],{"class":241},"                executor",[105,12415,583],{"class":115},[105,12417,12418,12421,12423,12425,12427,12430,12432,12434],{"class":107,"line":4087},[105,12419,12420],{"class":115},"                    (",[105,12422,8451],{"class":122},[105,12424,2854],{"class":115},[105,12426,723],{"class":111},[105,12428,12429],{"class":129}," this",[105,12431,5381],{"class":115},[105,12433,9510],{"class":241},[105,12435,12436],{"class":115},"(value),\n",[105,12438,12439,12441,12443,12445,12447,12449,12451,12454],{"class":107,"line":4092},[105,12440,12420],{"class":115},[105,12442,11611],{"class":122},[105,12444,2854],{"class":115},[105,12446,723],{"class":111},[105,12448,12429],{"class":129},[105,12450,5381],{"class":115},[105,12452,12453],{"class":241},"reject",[105,12455,12456],{"class":115},"(reason)\n",[105,12458,12459],{"class":107,"line":4098},[105,12460,12461],{"class":115},"                );\n",[105,12463,12464,12467,12469],{"class":107,"line":4116},[105,12465,12466],{"class":115},"            } ",[105,12468,5557],{"class":111},[105,12470,12471],{"class":115}," (e) {\n",[105,12473,12474,12477,12479,12481],{"class":107,"line":4141},[105,12475,12476],{"class":129},"                this",[105,12478,5381],{"class":115},[105,12480,12453],{"class":241},[105,12482,12483],{"class":115},"(e);\n",[105,12485,12486],{"class":107,"line":4165},[105,12487,12488],{"class":115},"            }\n",[105,12490,12491],{"class":107,"line":4170},[105,12492,12493],{"class":115},"        }\n",[105,12495,12496],{"class":107,"line":4175},[105,12497,5161],{"class":115},[105,12499,12500],{"class":107,"line":4181},[105,12501,307],{"emptyLinePlaceholder":306},[105,12503,12504,12506,12508,12510,12512,12514,12516,12518,12520],{"class":107,"line":4199},[105,12505,11945],{"class":241},[105,12507,321],{"class":115},[105,12509,8451],{"class":122},[105,12511,5017],{"class":111},[105,12513,5075],{"class":241},[105,12515,11956],{"class":115},[105,12517,5017],{"class":111},[105,12519,5082],{"class":129},[105,12521,116],{"class":115},[105,12523,12524,12526,12528,12530,12533,12535,12537,12539,12541],{"class":107,"line":4225},[105,12525,12388],{"class":111},[105,12527,4379],{"class":115},[105,12529,2888],{"class":129},[105,12531,12532],{"class":115},".state ",[105,12534,3332],{"class":111},[105,12536,11833],{"class":248},[105,12538,2854],{"class":115},[105,12540,3340],{"class":111},[105,12542,145],{"class":115},[105,12544,12545,12547,12550,12552,12554],{"class":107,"line":4252},[105,12546,12388],{"class":111},[105,12548,12549],{"class":115}," (value ",[105,12551,8171],{"class":111},[105,12553,12199],{"class":241},[105,12555,1000],{"class":115},[105,12557,12558,12561,12563],{"class":107,"line":4257},[105,12559,12560],{"class":115},"            value.",[105,12562,5530],{"class":241},[105,12564,583],{"class":115},[105,12566,12567,12570,12573,12575,12577,12579,12581,12583,12586,12588,12590],{"class":107,"line":4262},[105,12568,12569],{"class":115},"                (",[105,12571,12572],{"class":122},"v",[105,12574,2854],{"class":115},[105,12576,723],{"class":111},[105,12578,12429],{"class":129},[105,12580,5381],{"class":115},[105,12582,9510],{"class":241},[105,12584,12585],{"class":115},"(v ",[105,12587,10652],{"class":111},[105,12589,5075],{"class":241},[105,12591,12592],{"class":115},"),\n",[105,12594,12595,12597,12600,12602,12604,12606,12608,12610],{"class":107,"line":4268},[105,12596,12569],{"class":115},[105,12598,12599],{"class":122},"r",[105,12601,2854],{"class":115},[105,12603,723],{"class":111},[105,12605,12429],{"class":129},[105,12607,5381],{"class":115},[105,12609,12453],{"class":241},[105,12611,12612],{"class":115},"(r)\n",[105,12614,12615],{"class":107,"line":4285},[105,12616,5842],{"class":115},[105,12618,12619,12621],{"class":107,"line":4303},[105,12620,5658],{"class":111},[105,12622,145],{"class":115},[105,12624,12625],{"class":107,"line":4308},[105,12626,12493],{"class":115},[105,12628,12629],{"class":107,"line":4313},[105,12630,307],{"emptyLinePlaceholder":306},[105,12632,12633,12636,12638,12640,12642],{"class":107,"line":4319},[105,12634,12635],{"class":129},"        this",[105,12637,12532],{"class":115},[105,12639,245],{"class":111},[105,12641,11838],{"class":248},[105,12643,145],{"class":115},[105,12645,12646,12648,12651,12653],{"class":107,"line":4342},[105,12647,12635],{"class":129},[105,12649,12650],{"class":115},".result ",[105,12652,245],{"class":111},[105,12654,12655],{"class":115}," value;\n",[105,12657,12658],{"class":107,"line":4347},[105,12659,307],{"emptyLinePlaceholder":306},[105,12661,12662,12664,12667,12669,12671],{"class":107,"line":4353},[105,12663,7284],{"class":111},[105,12665,12666],{"class":129}," reactions",[105,12668,574],{"class":111},[105,12670,12429],{"class":129},[105,12672,12673],{"class":115},".PromiseFulfillReactions;\n",[105,12675,12676,12678,12681,12683],{"class":107,"line":4369},[105,12677,12635],{"class":129},[105,12679,12680],{"class":115},".PromiseFulfillReactions ",[105,12682,245],{"class":111},[105,12684,12277],{"class":115},[105,12686,12687,12689,12692,12694],{"class":107,"line":4390},[105,12688,12635],{"class":129},[105,12690,12691],{"class":115},".PromiseRejectReactions ",[105,12693,245],{"class":111},[105,12695,12277],{"class":115},[105,12697,12698,12701,12703,12705,12708,12711],{"class":107,"line":4402},[105,12699,12700],{"class":111},"        for",[105,12702,4379],{"class":115},[105,12704,568],{"class":111},[105,12706,12707],{"class":129}," reaction",[105,12709,12710],{"class":111}," of",[105,12712,12713],{"class":115}," reactions) {\n",[105,12715,12716,12719,12721,12723,12725],{"class":107,"line":4415},[105,12717,12718],{"class":241},"            queueMicrotask",[105,12720,4070],{"class":115},[105,12722,723],{"class":111},[105,12724,12707],{"class":241},[105,12726,12727],{"class":115},"(value));\n",[105,12729,12730],{"class":107,"line":4427},[105,12731,12493],{"class":115},[105,12733,12734],{"class":107,"line":4438},[105,12735,5161],{"class":115},[105,12737,12738],{"class":107,"line":4450},[105,12739,307],{"emptyLinePlaceholder":306},[105,12741,12742,12744,12746,12748,12750,12752,12754,12756,12758],{"class":107,"line":4456},[105,12743,11967],{"class":241},[105,12745,321],{"class":115},[105,12747,11611],{"class":122},[105,12749,11562],{"class":111},[105,12751,10435],{"class":129},[105,12753,11956],{"class":115},[105,12755,5017],{"class":111},[105,12757,5082],{"class":129},[105,12759,116],{"class":115},[105,12761,12762,12764,12766,12768,12770,12772,12774,12776,12778],{"class":107,"line":4462},[105,12763,12388],{"class":111},[105,12765,4379],{"class":115},[105,12767,2888],{"class":129},[105,12769,12532],{"class":115},[105,12771,3332],{"class":111},[105,12773,11833],{"class":248},[105,12775,2854],{"class":115},[105,12777,3340],{"class":111},[105,12779,145],{"class":115},[105,12781,12782],{"class":107,"line":4467},[105,12783,307],{"emptyLinePlaceholder":306},[105,12785,12786,12788,12790,12792,12794],{"class":107,"line":4473},[105,12787,12635],{"class":129},[105,12789,12532],{"class":115},[105,12791,245],{"class":111},[105,12793,11843],{"class":248},[105,12795,145],{"class":115},[105,12797,12798,12800,12802,12804],{"class":107,"line":4489},[105,12799,12635],{"class":129},[105,12801,12650],{"class":115},[105,12803,245],{"class":111},[105,12805,12806],{"class":115}," reason;\n",[105,12808,12809],{"class":107,"line":4497},[105,12810,307],{"emptyLinePlaceholder":306},[105,12812,12813,12815,12817,12819,12821],{"class":107,"line":4508},[105,12814,7284],{"class":111},[105,12816,12666],{"class":129},[105,12818,574],{"class":111},[105,12820,12429],{"class":129},[105,12822,12823],{"class":115},".PromiseRejectReactions;\n",[105,12825,12826,12828,12830,12832],{"class":107,"line":4513},[105,12827,12635],{"class":129},[105,12829,12680],{"class":115},[105,12831,245],{"class":111},[105,12833,12277],{"class":115},[105,12835,12836,12838,12840,12842],{"class":107,"line":4518},[105,12837,12635],{"class":129},[105,12839,12691],{"class":115},[105,12841,245],{"class":111},[105,12843,12277],{"class":115},[105,12845,12846,12848,12850,12852,12854,12856],{"class":107,"line":4523},[105,12847,12700],{"class":111},[105,12849,4379],{"class":115},[105,12851,568],{"class":111},[105,12853,12707],{"class":129},[105,12855,12710],{"class":111},[105,12857,12713],{"class":115},[105,12859,12860,12862,12864,12866,12868],{"class":107,"line":4535},[105,12861,12718],{"class":241},[105,12863,4070],{"class":115},[105,12865,723],{"class":111},[105,12867,12707],{"class":241},[105,12869,12870],{"class":115},"(reason));\n",[105,12872,12873],{"class":107,"line":4543},[105,12874,12493],{"class":115},[105,12876,12877],{"class":107,"line":4559},[105,12878,5161],{"class":115},[105,12880,12881],{"class":107,"line":4564},[105,12882,307],{"emptyLinePlaceholder":306},[105,12884,12885,12887,12889,12891,12893,12895,12897,12899,12901,12903],{"class":107,"line":4569},[105,12886,11533],{"class":241},[105,12888,234],{"class":115},[105,12890,11589],{"class":241},[105,12892,574],{"class":111},[105,12894,5075],{"class":241},[105,12896,286],{"class":115},[105,12898,11631],{"class":241},[105,12900,574],{"class":111},[105,12902,11550],{"class":129},[105,12904,12010],{"class":115},[105,12906,12907,12909,12911,12913,12915,12917,12919,12921,12923,12925,12927,12929,12931,12933,12935,12937,12939],{"class":107,"line":4581},[105,12908,11559],{"class":122},[105,12910,11562],{"class":111},[105,12912,11565],{"class":115},[105,12914,8451],{"class":122},[105,12916,5017],{"class":111},[105,12918,5075],{"class":241},[105,12920,2854],{"class":115},[105,12922,723],{"class":111},[105,12924,12031],{"class":241},[105,12926,5041],{"class":111},[105,12928,11584],{"class":241},[105,12930,234],{"class":115},[105,12932,11589],{"class":241},[105,12934,12042],{"class":115},[105,12936,11581],{"class":111},[105,12938,5044],{"class":129},[105,12940,2867],{"class":115},[105,12942,12943,12945,12947,12949,12951,12953,12955,12957,12959,12961,12963,12965,12967,12969,12971,12973],{"class":107,"line":4594},[105,12944,11604],{"class":122},[105,12946,11562],{"class":111},[105,12948,11565],{"class":115},[105,12950,11611],{"class":122},[105,12952,5017],{"class":111},[105,12954,10435],{"class":129},[105,12956,2854],{"class":115},[105,12958,723],{"class":111},[105,12960,11654],{"class":241},[105,12962,5041],{"class":111},[105,12964,11584],{"class":241},[105,12966,234],{"class":115},[105,12968,11631],{"class":241},[105,12970,12042],{"class":115},[105,12972,11581],{"class":111},[105,12974,11640],{"class":129},[105,12976,12977,12979,12981,12983,12985,12987,12989,12991],{"class":107,"line":4599},[105,12978,12087],{"class":115},[105,12980,5017],{"class":111},[105,12982,11584],{"class":241},[105,12984,234],{"class":115},[105,12986,11589],{"class":241},[105,12988,5041],{"class":111},[105,12990,11654],{"class":241},[105,12992,11862],{"class":115},[105,12994,12995,12997,12999,13001,13003,13005,13007,13009,13012,13014,13016,13018,13020,13022],{"class":107,"line":4608},[105,12996,11254],{"class":111},[105,12998,3496],{"class":111},[105,13000,12199],{"class":241},[105,13002,234],{"class":115},[105,13004,11589],{"class":241},[105,13006,5041],{"class":111},[105,13008,11654],{"class":241},[105,13010,13011],{"class":115},">((",[105,13013,9510],{"class":122},[105,13015,286],{"class":115},[105,13017,12453],{"class":122},[105,13019,2854],{"class":115},[105,13021,723],{"class":111},[105,13023,116],{"class":115},[105,13025,13026,13028,13031,13033,13035,13037,13039,13041,13043,13045],{"class":107,"line":4613},[105,13027,6307],{"class":111},[105,13029,13030],{"class":241}," handlerFulfilled",[105,13032,574],{"class":111},[105,13034,4379],{"class":115},[105,13036,8451],{"class":122},[105,13038,5017],{"class":111},[105,13040,5075],{"class":241},[105,13042,2854],{"class":115},[105,13044,723],{"class":111},[105,13046,116],{"class":115},[105,13048,13049,13052,13054,13056,13059,13061,13063],{"class":107,"line":4625},[105,13050,13051],{"class":111},"                if",[105,13053,4379],{"class":115},[105,13055,8060],{"class":111},[105,13057,13058],{"class":115}," onFulfilled ",[105,13060,3332],{"class":111},[105,13062,10551],{"class":248},[105,13064,1000],{"class":115},[105,13066,13067,13070,13073,13075,13077,13080,13082],{"class":107,"line":4633},[105,13068,13069],{"class":241},"                    resolve",[105,13071,13072],{"class":115},"(value ",[105,13074,10652],{"class":111},[105,13076,8456],{"class":129},[105,13078,13079],{"class":111}," as",[105,13081,12031],{"class":241},[105,13083,327],{"class":115},[105,13085,13086,13089],{"class":107,"line":4651},[105,13087,13088],{"class":111},"                    return",[105,13090,145],{"class":115},[105,13092,13093],{"class":107,"line":4663},[105,13094,13095],{"class":115},"                }\n",[105,13097,13098,13101],{"class":107,"line":4674},[105,13099,13100],{"class":111},"                try",[105,13102,116],{"class":115},[105,13104,13105,13108,13111,13113,13116],{"class":107,"line":4692},[105,13106,13107],{"class":111},"                    const",[105,13109,13110],{"class":129}," r",[105,13112,574],{"class":111},[105,13114,13115],{"class":241}," onFulfilled",[105,13117,13118],{"class":115},"(value);\n",[105,13120,13121,13124,13127,13129,13131],{"class":107,"line":4697},[105,13122,13123],{"class":111},"                    if",[105,13125,13126],{"class":115}," (r ",[105,13128,8171],{"class":111},[105,13130,12199],{"class":241},[105,13132,1000],{"class":115},[105,13134,13135,13138,13140],{"class":107,"line":4702},[105,13136,13137],{"class":115},"                        r.",[105,13139,5530],{"class":241},[105,13141,583],{"class":115},[105,13143,13144,13147,13149,13151,13153,13156,13158,13160,13162],{"class":107,"line":4710},[105,13145,13146],{"class":115},"                            (",[105,13148,12572],{"class":122},[105,13150,2854],{"class":115},[105,13152,723],{"class":111},[105,13154,13155],{"class":241}," resolve",[105,13157,12585],{"class":115},[105,13159,10652],{"class":111},[105,13161,12031],{"class":241},[105,13163,12592],{"class":115},[105,13165,13166,13168,13170,13172,13174,13177],{"class":107,"line":4721},[105,13167,13146],{"class":115},[105,13169,3315],{"class":122},[105,13171,2854],{"class":115},[105,13173,723],{"class":111},[105,13175,13176],{"class":241}," reject",[105,13178,13179],{"class":115},"(e)\n",[105,13181,13182],{"class":107,"line":4732},[105,13183,13184],{"class":115},"                        );\n",[105,13186,13187,13190,13193],{"class":107,"line":4743},[105,13188,13189],{"class":115},"                    } ",[105,13191,13192],{"class":111},"else",[105,13194,116],{"class":115},[105,13196,13197,13200,13203,13205,13207],{"class":107,"line":4755},[105,13198,13199],{"class":241},"                        resolve",[105,13201,13202],{"class":115},"(r ",[105,13204,10652],{"class":111},[105,13206,12031],{"class":241},[105,13208,327],{"class":115},[105,13210,13211],{"class":107,"line":4771},[105,13212,13213],{"class":115},"                    }\n",[105,13215,13216,13219,13221],{"class":107,"line":4776},[105,13217,13218],{"class":115},"                } ",[105,13220,5557],{"class":111},[105,13222,12471],{"class":115},[105,13224,13225,13228],{"class":107,"line":4781},[105,13226,13227],{"class":241},"                    reject",[105,13229,12483],{"class":115},[105,13231,13232],{"class":107,"line":4789},[105,13233,13095],{"class":115},[105,13235,13236],{"class":107,"line":4800},[105,13237,13238],{"class":115},"            };\n",[105,13240,13241],{"class":107,"line":4811},[105,13242,307],{"emptyLinePlaceholder":306},[105,13244,13245,13247,13250,13252,13254,13256,13258,13260,13262,13264],{"class":107,"line":4822},[105,13246,6307],{"class":111},[105,13248,13249],{"class":241}," handlerRejected",[105,13251,574],{"class":111},[105,13253,4379],{"class":115},[105,13255,11611],{"class":122},[105,13257,5017],{"class":111},[105,13259,10435],{"class":129},[105,13261,2854],{"class":115},[105,13263,723],{"class":111},[105,13265,116],{"class":115},[105,13267,13268,13270,13272,13274,13277,13279,13281],{"class":107,"line":4833},[105,13269,13051],{"class":111},[105,13271,4379],{"class":115},[105,13273,8060],{"class":111},[105,13275,13276],{"class":115}," onRejected ",[105,13278,3332],{"class":111},[105,13280,10551],{"class":248},[105,13282,1000],{"class":115},[105,13284,13285,13287],{"class":107,"line":4845},[105,13286,13227],{"class":241},[105,13288,13289],{"class":115},"(reason);\n",[105,13291,13292,13294],{"class":107,"line":4850},[105,13293,13088],{"class":111},[105,13295,145],{"class":115},[105,13297,13298],{"class":107,"line":4855},[105,13299,13095],{"class":115},[105,13301,13302,13304],{"class":107,"line":4863},[105,13303,13100],{"class":111},[105,13305,116],{"class":115},[105,13307,13308,13310,13312,13314,13317],{"class":107,"line":4876},[105,13309,13107],{"class":111},[105,13311,13110],{"class":129},[105,13313,574],{"class":111},[105,13315,13316],{"class":241}," onRejected",[105,13318,13289],{"class":115},[105,13320,13321,13323,13325,13327,13329],{"class":107,"line":4895},[105,13322,13123],{"class":111},[105,13324,13126],{"class":115},[105,13326,8171],{"class":111},[105,13328,12199],{"class":241},[105,13330,1000],{"class":115},[105,13332,13333,13335,13337],{"class":107,"line":4907},[105,13334,13137],{"class":115},[105,13336,5530],{"class":241},[105,13338,583],{"class":115},[105,13340,13341,13343,13345,13347,13349,13351,13353,13355,13357],{"class":107,"line":4920},[105,13342,13146],{"class":115},[105,13344,12572],{"class":122},[105,13346,2854],{"class":115},[105,13348,723],{"class":111},[105,13350,13155],{"class":241},[105,13352,12585],{"class":115},[105,13354,10652],{"class":111},[105,13356,12031],{"class":241},[105,13358,12592],{"class":115},[105,13360,13361,13363,13365,13367,13369,13371],{"class":107,"line":4936},[105,13362,13146],{"class":115},[105,13364,3315],{"class":122},[105,13366,2854],{"class":115},[105,13368,723],{"class":111},[105,13370,13176],{"class":241},[105,13372,13179],{"class":115},[105,13374,13375],{"class":107,"line":4941},[105,13376,13184],{"class":115},[105,13378,13380,13382,13384],{"class":107,"line":13379},120,[105,13381,13189],{"class":115},[105,13383,13192],{"class":111},[105,13385,116],{"class":115},[105,13387,13389,13391,13393,13395,13397,13399,13401],{"class":107,"line":13388},121,[105,13390,13199],{"class":241},[105,13392,13202],{"class":115},[105,13394,10652],{"class":111},[105,13396,8456],{"class":129},[105,13398,13079],{"class":111},[105,13400,12031],{"class":241},[105,13402,327],{"class":115},[105,13404,13406],{"class":107,"line":13405},122,[105,13407,13213],{"class":115},[105,13409,13411,13413,13415],{"class":107,"line":13410},123,[105,13412,13218],{"class":115},[105,13414,5557],{"class":111},[105,13416,12471],{"class":115},[105,13418,13420,13422],{"class":107,"line":13419},124,[105,13421,13227],{"class":241},[105,13423,12483],{"class":115},[105,13425,13427],{"class":107,"line":13426},125,[105,13428,13095],{"class":115},[105,13430,13432],{"class":107,"line":13431},126,[105,13433,13238],{"class":115},[105,13435,13437],{"class":107,"line":13436},127,[105,13438,307],{"emptyLinePlaceholder":306},[105,13440,13442,13444,13446,13448,13450,13452,13454],{"class":107,"line":13441},128,[105,13443,5980],{"class":111},[105,13445,4379],{"class":115},[105,13447,2888],{"class":129},[105,13449,12532],{"class":115},[105,13451,7269],{"class":111},[105,13453,11833],{"class":248},[105,13455,1000],{"class":115},[105,13457,13459,13461,13464,13467],{"class":107,"line":13458},129,[105,13460,12476],{"class":129},[105,13462,13463],{"class":115},".PromiseFulfillReactions.",[105,13465,13466],{"class":241},"push",[105,13468,13469],{"class":115},"(handlerFulfilled);\n",[105,13471,13473,13475,13478,13480],{"class":107,"line":13472},130,[105,13474,12476],{"class":129},[105,13476,13477],{"class":115},".PromiseRejectReactions.",[105,13479,13466],{"class":241},[105,13481,13482],{"class":115},"(handlerRejected);\n",[105,13484,13486,13488,13490,13493,13495,13497,13499,13501,13503],{"class":107,"line":13485},131,[105,13487,12466],{"class":115},[105,13489,13192],{"class":111},[105,13491,13492],{"class":111}," if",[105,13494,4379],{"class":115},[105,13496,2888],{"class":129},[105,13498,12532],{"class":115},[105,13500,7269],{"class":111},[105,13502,11838],{"class":248},[105,13504,1000],{"class":115},[105,13506,13508,13511,13513,13515,13517,13519,13521,13523,13525,13527],{"class":107,"line":13507},132,[105,13509,13510],{"class":241},"                queueMicrotask",[105,13512,4070],{"class":115},[105,13514,723],{"class":111},[105,13516,13030],{"class":241},[105,13518,321],{"class":115},[105,13520,2888],{"class":129},[105,13522,12650],{"class":115},[105,13524,10652],{"class":111},[105,13526,5075],{"class":241},[105,13528,13529],{"class":115},"));\n",[105,13531,13533,13535,13537],{"class":107,"line":13532},133,[105,13534,12466],{"class":115},[105,13536,13192],{"class":111},[105,13538,116],{"class":115},[105,13540,13542,13544,13546,13548,13550,13552,13554],{"class":107,"line":13541},134,[105,13543,13510],{"class":241},[105,13545,4070],{"class":115},[105,13547,723],{"class":111},[105,13549,13249],{"class":241},[105,13551,321],{"class":115},[105,13553,2888],{"class":129},[105,13555,13556],{"class":115},".result));\n",[105,13558,13560],{"class":107,"line":13559},135,[105,13561,12488],{"class":115},[105,13563,13565],{"class":107,"line":13564},136,[105,13566,13567],{"class":115},"        });\n",[105,13569,13571],{"class":107,"line":13570},137,[105,13572,5161],{"class":115},[105,13574,13576],{"class":107,"line":13575},138,[105,13577,307],{"emptyLinePlaceholder":306},[105,13579,13581,13583,13585,13587,13589,13591],{"class":107,"line":13580},139,[105,13582,12111],{"class":241},[105,13584,234],{"class":115},[105,13586,12116],{"class":241},[105,13588,574],{"class":111},[105,13590,11550],{"class":129},[105,13592,12010],{"class":115},[105,13594,13596,13598,13600,13602,13604,13606,13608,13610,13612,13614,13616,13618,13620,13622,13624,13626],{"class":107,"line":13595},140,[105,13597,11604],{"class":122},[105,13599,11562],{"class":111},[105,13601,11565],{"class":115},[105,13603,11611],{"class":122},[105,13605,5017],{"class":111},[105,13607,10435],{"class":129},[105,13609,2854],{"class":115},[105,13611,723],{"class":111},[105,13613,12143],{"class":241},[105,13615,5041],{"class":111},[105,13617,11584],{"class":241},[105,13619,234],{"class":115},[105,13621,12116],{"class":241},[105,13623,12042],{"class":115},[105,13625,11581],{"class":111},[105,13627,11640],{"class":129},[105,13629,13631,13633,13635,13637,13639,13641,13643,13645],{"class":107,"line":13630},141,[105,13632,12087],{"class":115},[105,13634,5017],{"class":111},[105,13636,11584],{"class":241},[105,13638,234],{"class":115},[105,13640,5008],{"class":241},[105,13642,5041],{"class":111},[105,13644,12143],{"class":241},[105,13646,11862],{"class":115},[105,13648,13650,13652,13654,13656,13658,13660,13662],{"class":107,"line":13649},142,[105,13651,11254],{"class":111},[105,13653,12429],{"class":129},[105,13655,5381],{"class":115},[105,13657,5530],{"class":241},[105,13659,321],{"class":115},[105,13661,4005],{"class":129},[105,13663,13664],{"class":115},", onRejected);\n",[105,13666,13668],{"class":107,"line":13667},143,[105,13669,5161],{"class":115},[105,13671,13673],{"class":107,"line":13672},144,[105,13674,307],{"emptyLinePlaceholder":306},[105,13676,13678,13681,13683,13685,13687,13689,13691,13693,13695,13697,13699,13701,13703,13705],{"class":107,"line":13677},145,[105,13679,13680],{"class":111},"    static",[105,13682,13155],{"class":241},[105,13684,234],{"class":115},[105,13686,5008],{"class":241},[105,13688,5011],{"class":115},[105,13690,8451],{"class":122},[105,13692,5017],{"class":111},[105,13694,5075],{"class":241},[105,13696,11956],{"class":115},[105,13698,5017],{"class":111},[105,13700,12199],{"class":241},[105,13702,234],{"class":115},[105,13704,5008],{"class":241},[105,13706,11862],{"class":115},[105,13708,13710,13712,13714,13716,13718,13720,13722,13724,13726,13728,13730],{"class":107,"line":13709},146,[105,13711,11254],{"class":111},[105,13713,3496],{"class":111},[105,13715,12199],{"class":241},[105,13717,234],{"class":115},[105,13719,5008],{"class":241},[105,13721,13011],{"class":115},[105,13723,9510],{"class":122},[105,13725,2854],{"class":115},[105,13727,723],{"class":111},[105,13729,13155],{"class":241},[105,13731,12727],{"class":115},[105,13733,13735],{"class":107,"line":13734},147,[105,13736,5161],{"class":115},[105,13738,13740],{"class":107,"line":13739},148,[105,13741,307],{"emptyLinePlaceholder":306},[105,13743,13745,13747,13749,13751,13753,13755,13757,13759,13761,13763,13765,13767,13769,13771,13773,13775],{"class":107,"line":13744},149,[105,13746,13680],{"class":111},[105,13748,13176],{"class":241},[105,13750,234],{"class":115},[105,13752,5008],{"class":241},[105,13754,574],{"class":111},[105,13756,11550],{"class":129},[105,13758,5011],{"class":115},[105,13760,11611],{"class":122},[105,13762,11562],{"class":111},[105,13764,10435],{"class":129},[105,13766,11956],{"class":115},[105,13768,5017],{"class":111},[105,13770,12199],{"class":241},[105,13772,234],{"class":115},[105,13774,5008],{"class":241},[105,13776,11862],{"class":115},[105,13778,13780,13782,13784,13786,13788,13790,13792,13795,13797,13799,13801,13803,13805],{"class":107,"line":13779},150,[105,13781,11254],{"class":111},[105,13783,3496],{"class":111},[105,13785,12199],{"class":241},[105,13787,234],{"class":115},[105,13789,5008],{"class":241},[105,13791,13011],{"class":115},[105,13793,13794],{"class":122},"_",[105,13796,286],{"class":115},[105,13798,12453],{"class":122},[105,13800,2854],{"class":115},[105,13802,723],{"class":111},[105,13804,13176],{"class":241},[105,13806,12870],{"class":115},[105,13808,13810],{"class":107,"line":13809},151,[105,13811,5161],{"class":115},[105,13813,13815],{"class":107,"line":13814},152,[105,13816,212],{"class":115},[105,13818,13820],{"class":107,"line":13819},153,[105,13821,307],{"emptyLinePlaceholder":306},[105,13823,13825,13828,13830,13832,13835,13837,13839],{"class":107,"line":13824},154,[105,13826,13827],{"class":115},"MyPromise.",[105,13829,9510],{"class":241},[105,13831,321],{"class":115},[105,13833,13834],{"class":129},"10",[105,13836,5642],{"class":115},[105,13838,5530],{"class":241},[105,13840,583],{"class":115},[105,13842,13844,13846,13848,13850,13852,13854,13856,13858,13861,13864],{"class":107,"line":13843},155,[105,13845,10309],{"class":115},[105,13847,12572],{"class":122},[105,13849,2854],{"class":115},[105,13851,723],{"class":111},[105,13853,5540],{"class":115},[105,13855,639],{"class":241},[105,13857,321],{"class":115},[105,13859,13860],{"class":248},"'fulfilled'",[105,13862,13863],{"class":115},", v),   ",[105,13865,13866],{"class":630},"\u002F\u002F fulfilled 10\n",[105,13868,13870,13872,13874,13876,13878,13880,13882,13884,13887],{"class":107,"line":13869},156,[105,13871,10309],{"class":115},[105,13873,12599],{"class":122},[105,13875,2854],{"class":115},[105,13877,723],{"class":111},[105,13879,5540],{"class":115},[105,13881,639],{"class":241},[105,13883,321],{"class":115},[105,13885,13886],{"class":248},"'rejected'",[105,13888,13889],{"class":115},", r)\n",[105,13891,13893],{"class":107,"line":13892},157,[105,13894,327],{"class":115},[18,13896,13897],{},"await就是专门为Promise设计的，我们一般喜欢await x 这样来使用，如果x不是thenable，就会直接返回x，如果x是thenable，就会等它敲定，比如fulfilled返回成功值，rejected跑出拒绝原因。因此await不要求x是Promise实例，只要有then方法就行，说白了，就是认定鸭子类型。",[14,13899,13900],{"id":13900},"原型链",[18,13902,13903],{},"原型链的最初设计目的是为了用最少的机制来解决几个问题，Brendan Eich 1995年设计js的时候，是让对象能够借用另一个对象的方法和属性，而不需要每个对象都复制一份。原型链的本质就是委托，当你访问b.greet的时候b自己没有，就会委托给它的原型a去找，这是一种运行时动态查找，而不是编译器的类继承。",[18,13905,13906],{},"Brendan Eich收Self语言影响，核心思想是不需要类，对象可以直接从对象继承。这个就和传统OOP倾向的语言比如Java\u002FC++不一样了。Self\u002FJS的原型模型那就是： 对象 -> 对象 -> 对象。继承关系在对象之间定义，没有类中间层，每个对象都可以有自己的原型，可以动态修改。",[18,13908,13909],{},"所以，完全可以得出一个结论： js选择原型，是因为更加简单，更加灵活，更加适合脚本语言。这样就可以解决好几个问题：",[18,13911,13912],{},"1是内存效率问题，通过千万个实例可以共享一个函数，而不是每个都复制一份。2是动态性，原型链本来就是运行时查找，改了原型，所有实例都会立即收到影响，这个是类继承做不到的。",[18,13914,13915],{},"es5之前，js是没有class语法的，定义类的方式就是构造函数+原型。es6之后引入class作为了关键字和语法糖。但是到现在都没有interface关键字，因为js是动态语言，运行时才检查类型，不需要接口作为编译器约束。",[18,13917,13918,13921,13922,13924,13925,13927,13928,13931,13932,7831,13939,13942],{},[21,13919,13920],{},"每个函数","都有一个 ",[24,13923,8257],{}," 属性，指向一个对象，",[24,13926,8257],{}," 是",[21,13929,13930],{},"函数才有的属性","，它的作用是：",[21,13933,13934,13935,13938],{},"当这个函数被用作构造函数（",[24,13936,13937],{},"new Foo()","）时，新对象的原型会指向它。",[21,13940,13941],{},"proto__就是每个对象内部的一个槽位，叫做[[Prototype]]，这个指向它的原型对象，访问方式是通过__proto","，或者Object.getPrototypeOf方法。 __proto__是对象才有的属性，指向继承。",[18,13944,13945,13946,9847],{},"默认情况下，",[24,13947,13948],{},"Foo.prototype.constructor === Foo",[96,13950,13952],{"className":3259,"code":13951,"language":3261,"meta":101,"style":101},"function Foo() {}\nFoo.prototype.constructor === Foo   \u002F\u002F true\n",[24,13953,13954,13962],{"__ignoreMap":101},[105,13955,13956,13958,13960],{"class":107,"line":108},[105,13957,8131],{"class":111},[105,13959,10172],{"class":241},[105,13961,10023],{"class":115},[105,13963,13964,13967,13969,13971,13973,13975,13978,13981],{"class":107,"line":119},[105,13965,13966],{"class":129},"Foo",[105,13968,5381],{"class":115},[105,13970,8257],{"class":129},[105,13972,5381],{"class":115},[105,13974,11475],{"class":129},[105,13976,13977],{"class":111}," ===",[105,13979,13980],{"class":115}," Foo   ",[105,13982,13983],{"class":630},"\u002F\u002F true\n",[18,13985,13986,13988],{},[24,13987,11475],{}," 让实例能反向找到\"创建我的构造函数\"。",[18,13990,13991],{},"还记得我们之前提到过的类型检测方法吗：",[96,13993,13994],{"className":3259,"code":8245,"language":3261,"meta":101,"style":101},[24,13995,13996,14018,14040,14056,14078,14094,14114],{"__ignoreMap":101},[105,13997,13998,14000,14002,14004,14006,14008,14010,14012,14014,14016],{"class":107,"line":108},[105,13999,8252],{"class":129},[105,14001,5381],{"class":115},[105,14003,8257],{"class":129},[105,14005,8260],{"class":115},[105,14007,8263],{"class":241},[105,14009,321],{"class":115},[105,14011,7430],{"class":111},[105,14013,7433],{"class":241},[105,14015,8272],{"class":115},[105,14017,8275],{"class":630},[105,14019,14020,14022,14024,14026,14028,14030,14032,14034,14036,14038],{"class":107,"line":119},[105,14021,8252],{"class":129},[105,14023,5381],{"class":115},[105,14025,8257],{"class":129},[105,14027,8260],{"class":115},[105,14029,8263],{"class":241},[105,14031,321],{"class":115},[105,14033,7430],{"class":111},[105,14035,5064],{"class":241},[105,14037,8272],{"class":115},[105,14039,8298],{"class":630},[105,14041,14042,14044,14046,14048,14050,14052,14054],{"class":107,"line":133},[105,14043,8252],{"class":129},[105,14045,5381],{"class":115},[105,14047,8257],{"class":129},[105,14049,8260],{"class":115},[105,14051,8263],{"class":241},[105,14053,8313],{"class":115},[105,14055,8316],{"class":630},[105,14057,14058,14060,14062,14064,14066,14068,14070,14072,14074,14076],{"class":107,"line":148},[105,14059,8252],{"class":129},[105,14061,5381],{"class":115},[105,14063,8257],{"class":129},[105,14065,8260],{"class":115},[105,14067,8263],{"class":241},[105,14069,321],{"class":115},[105,14071,7430],{"class":111},[105,14073,7477],{"class":241},[105,14075,8337],{"class":115},[105,14077,8340],{"class":630},[105,14079,14080,14082,14084,14086,14088,14090,14092],{"class":107,"line":163},[105,14081,8252],{"class":129},[105,14083,5381],{"class":115},[105,14085,8257],{"class":129},[105,14087,8260],{"class":115},[105,14089,8263],{"class":241},[105,14091,8355],{"class":115},[105,14093,8358],{"class":630},[105,14095,14096,14098,14100,14102,14104,14106,14108,14110,14112],{"class":107,"line":179},[105,14097,8252],{"class":129},[105,14099,5381],{"class":115},[105,14101,8257],{"class":129},[105,14103,8260],{"class":115},[105,14105,8263],{"class":241},[105,14107,321],{"class":115},[105,14109,4005],{"class":129},[105,14111,8377],{"class":115},[105,14113,8380],{"class":630},[105,14115,14116,14118,14120,14122,14124,14126,14128,14130,14132],{"class":107,"line":194},[105,14117,8252],{"class":129},[105,14119,5381],{"class":115},[105,14121,8257],{"class":129},[105,14123,8260],{"class":115},[105,14125,8263],{"class":241},[105,14127,321],{"class":115},[105,14129,8397],{"class":129},[105,14131,8400],{"class":115},[105,14133,8403],{"class":630},[18,14135,14136],{},"这里可以发现Object本身就是函数，所以他有prototype，这个也很有趣，Object既是函数，是一个构造函数，我们可以断定的是Object.prototype是所有普通对象的原型链顶端：",[96,14138,14140],{"className":3259,"code":14139,"language":3261,"meta":101,"style":101},"const obj = {};\nobj.__proto__ === Object.prototype   \u002F\u002F true\n\nObject.getPrototypeOf(obj) === Object.prototype   \u002F\u002F true\n\n`Object.prototype` 上挂着所有对象共享的方法：\n\nObject.prototype.toString\nObject.prototype.hasOwnProperty\nObject.prototype.valueOf\nObject.prototype.isPrototypeOf\nObject.prototype.propertyIsEnumerable\nObject.prototype.toLocaleString\n\nObject.prototype.constructor === Object \u002F\u002F true , constructor也是指向Object自己\n",[24,14141,14142,14153,14171,14175,14196,14200,14208,14212,14223,14234,14245,14256,14267,14278,14282],{"__ignoreMap":101},[105,14143,14144,14146,14148,14150],{"class":107,"line":108},[105,14145,568],{"class":111},[105,14147,10206],{"class":129},[105,14149,574],{"class":111},[105,14151,14152],{"class":115}," {};\n",[105,14154,14155,14158,14160,14162,14164,14166,14168],{"class":107,"line":119},[105,14156,14157],{"class":115},"obj.",[105,14159,11452],{"class":129},[105,14161,13977],{"class":111},[105,14163,8465],{"class":129},[105,14165,5381],{"class":115},[105,14167,8257],{"class":129},[105,14169,14170],{"class":630},"   \u002F\u002F true\n",[105,14172,14173],{"class":107,"line":133},[105,14174,307],{"emptyLinePlaceholder":306},[105,14176,14177,14180,14183,14186,14188,14190,14192,14194],{"class":107,"line":148},[105,14178,14179],{"class":115},"Object.",[105,14181,14182],{"class":241},"getPrototypeOf",[105,14184,14185],{"class":115},"(obj) ",[105,14187,7269],{"class":111},[105,14189,8465],{"class":129},[105,14191,5381],{"class":115},[105,14193,8257],{"class":129},[105,14195,14170],{"class":630},[105,14197,14198],{"class":107,"line":163},[105,14199,307],{"emptyLinePlaceholder":306},[105,14201,14202,14205],{"class":107,"line":179},[105,14203,14204],{"class":248},"`Object.prototype`",[105,14206,14207],{"class":115}," 上挂着所有对象共享的方法：\n",[105,14209,14210],{"class":107,"line":194},[105,14211,307],{"emptyLinePlaceholder":306},[105,14213,14214,14216,14218,14220],{"class":107,"line":209},[105,14215,8252],{"class":129},[105,14217,5381],{"class":115},[105,14219,8257],{"class":129},[105,14221,14222],{"class":115},".toString\n",[105,14224,14225,14227,14229,14231],{"class":107,"line":526},[105,14226,8252],{"class":129},[105,14228,5381],{"class":115},[105,14230,8257],{"class":129},[105,14232,14233],{"class":115},".hasOwnProperty\n",[105,14235,14236,14238,14240,14242],{"class":107,"line":531},[105,14237,8252],{"class":129},[105,14239,5381],{"class":115},[105,14241,8257],{"class":129},[105,14243,14244],{"class":115},".valueOf\n",[105,14246,14247,14249,14251,14253],{"class":107,"line":668},[105,14248,8252],{"class":129},[105,14250,5381],{"class":115},[105,14252,8257],{"class":129},[105,14254,14255],{"class":115},".isPrototypeOf\n",[105,14257,14258,14260,14262,14264],{"class":107,"line":673},[105,14259,8252],{"class":129},[105,14261,5381],{"class":115},[105,14263,8257],{"class":129},[105,14265,14266],{"class":115},".propertyIsEnumerable\n",[105,14268,14269,14271,14273,14275],{"class":107,"line":679},[105,14270,8252],{"class":129},[105,14272,5381],{"class":115},[105,14274,8257],{"class":129},[105,14276,14277],{"class":115},".toLocaleString\n",[105,14279,14280],{"class":107,"line":689},[105,14281,307],{"emptyLinePlaceholder":306},[105,14283,14284,14286,14288,14290,14292,14294,14296,14299],{"class":107,"line":699},[105,14285,8252],{"class":129},[105,14287,5381],{"class":115},[105,14289,8257],{"class":129},[105,14291,5381],{"class":115},[105,14293,11475],{"class":129},[105,14295,13977],{"class":111},[105,14297,14298],{"class":115}," Object ",[105,14300,14301],{"class":630},"\u002F\u002F true , constructor也是指向Object自己\n",[18,14303,14304],{},"instanceof 的原理是： obj instanceof Foo 检查 Foo.prototype 是否在obj的原型链上，你甚至也可以手动去实现：",[96,14306,14308],{"className":3259,"code":14307,"language":3261,"meta":101,"style":101},"function myInstanceof(obj, Constructor) {\n    let proto = Object.getPrototypeOf(obj);\n    while (proto !== null) {\n        if (proto === Constructor.prototype) return true;\n        proto = Object.getPrototypeOf(proto);\n    }\n    return false;\n}\n",[24,14309,14310,14328,14344,14358,14381,14395,14399,14407],{"__ignoreMap":101},[105,14311,14312,14314,14317,14319,14322,14324,14326],{"class":107,"line":108},[105,14313,8131],{"class":111},[105,14315,14316],{"class":241}," myInstanceof",[105,14318,321],{"class":115},[105,14320,14321],{"class":122},"obj",[105,14323,286],{"class":115},[105,14325,11327],{"class":122},[105,14327,1000],{"class":115},[105,14329,14330,14333,14336,14338,14340,14342],{"class":107,"line":119},[105,14331,14332],{"class":111},"    let",[105,14334,14335],{"class":115}," proto ",[105,14337,245],{"class":111},[105,14339,11354],{"class":115},[105,14341,14182],{"class":241},[105,14343,10800],{"class":115},[105,14345,14346,14349,14352,14354,14356],{"class":107,"line":133},[105,14347,14348],{"class":111},"    while",[105,14350,14351],{"class":115}," (proto ",[105,14353,3332],{"class":111},[105,14355,5044],{"class":129},[105,14357,1000],{"class":115},[105,14359,14360,14362,14364,14366,14369,14371,14373,14375,14377,14379],{"class":107,"line":148},[105,14361,12388],{"class":111},[105,14363,14351],{"class":115},[105,14365,7269],{"class":111},[105,14367,14368],{"class":129}," Constructor",[105,14370,5381],{"class":115},[105,14372,8257],{"class":129},[105,14374,2854],{"class":115},[105,14376,3340],{"class":111},[105,14378,4410],{"class":129},[105,14380,145],{"class":115},[105,14382,14383,14386,14388,14390,14392],{"class":107,"line":163},[105,14384,14385],{"class":115},"        proto ",[105,14387,245],{"class":111},[105,14389,11354],{"class":115},[105,14391,14182],{"class":241},[105,14393,14394],{"class":115},"(proto);\n",[105,14396,14397],{"class":107,"line":179},[105,14398,5161],{"class":115},[105,14400,14401,14403,14405],{"class":107,"line":194},[105,14402,10680],{"class":111},[105,14404,4364],{"class":129},[105,14406,145],{"class":115},[105,14408,14409],{"class":107,"line":209},[105,14410,212],{"class":115},[18,14412,14413,14414,14417],{},"所以函数是一种特殊的对象，之所以能被调用是因为有 ",[24,14415,14416],{},"[[Call]]","，同时具备对象的一切能力，能挂载属性，有原型，能作为参数传递，函数既可以被当作普通函数，也可以被当成构造函数。",[18,14419,14420],{},[21,14421,14422,14423,14425],{},"两个 ",[24,14424,11475],{},"，别混：",[835,14427,14428,14440],{},[838,14429,14430],{},[841,14431,14432,14435,14438],{},[844,14433,14434],{},"表达式",[844,14436,14437],{},"结果",[844,14439,1169],{},[854,14441,14442,14462,14481],{},[841,14443,14444,14449,14453],{},[859,14445,14446],{},[24,14447,14448],{},"Foo.constructor",[859,14450,14451],{},[24,14452,10485],{},[859,14454,14455,14457,14458,14461],{},[24,14456,13966],{}," 这个函数",[21,14459,14460],{},"对象","由谁创建",[841,14463,14464,14469,14473],{},[859,14465,14466],{},[24,14467,14468],{},"Foo.prototype.constructor",[859,14470,14471],{},[24,14472,13966],{},[859,14474,14475,14477,14478,14461],{},[24,14476,13966],{}," 的",[21,14479,14480],{},"实例",[841,14482,14483,14486,14488],{},[859,14484,14485],{},"new Foo()执行的就是Foo这个函数，而不是Foo.constructor。",[859,14487],{},[859,14489],{},[14,14491,14492],{"id":14492},"数组可以调用的方法",[18,14494,14495],{},"数组可以调用的函数分为实例方法，挂载到Array.prototype上的，数组可以直接使用。静态方法是挂载在Array的构造函数上的，通过Array.xxx调用。",[18,14497,14498],{},"实例方法：",[835,14500,14501,14510],{},[838,14502,14503],{},[841,14504,14505,14508],{},[844,14506,14507],{},"方法",[844,14509,2241],{},[854,14511,14512,14522,14532,14542,14552,14562,14572,14582,14592],{},[841,14513,14514,14519],{},[859,14515,14516],{},[24,14517,14518],{},"push(...items)",[859,14520,14521],{},"末尾添加，返回新长度",[841,14523,14524,14529],{},[859,14525,14526],{},[24,14527,14528],{},"pop()",[859,14530,14531],{},"删除末尾，返回被删元素",[841,14533,14534,14539],{},[859,14535,14536],{},[24,14537,14538],{},"unshift(...items)",[859,14540,14541],{},"开头添加，返回新长度",[841,14543,14544,14549],{},[859,14545,14546],{},[24,14547,14548],{},"shift()",[859,14550,14551],{},"删除开头，返回被删元素",[841,14553,14554,14559],{},[859,14555,14556],{},[24,14557,14558],{},"splice(start, deleteCount, ...items)",[859,14560,14561],{},"任意位置增删，返回被删元素数组",[841,14563,14564,14569],{},[859,14565,14566],{},[24,14567,14568],{},"reverse()",[859,14570,14571],{},"反转，返回原数组",[841,14573,14574,14579],{},[859,14575,14576],{},[24,14577,14578],{},"sort(compareFn?)",[859,14580,14581],{},"排序，返回原数组",[841,14583,14584,14589],{},[859,14585,14586],{},[24,14587,14588],{},"fill(value, start?, end?)",[859,14590,14591],{},"填充，返回原数组",[841,14593,14594,14599],{},[859,14595,14596],{},[24,14597,14598],{},"copyWithin(target, start?, end?)",[859,14600,14601],{},"内部复制，返回原数组",[835,14603,14604,14612],{},[838,14605,14606],{},[841,14607,14608,14610],{},[844,14609,14507],{},[844,14611,2241],{},[854,14613,14614,14624,14634,14644,14654,14664,14674,14684,14694,14704,14714,14724,14734,14744,14754,14764,14774,14784,14794,14804],{},[841,14615,14616,14621],{},[859,14617,14618],{},[24,14619,14620],{},"concat(...items)",[859,14622,14623],{},"合并，返回新数组",[841,14625,14626,14631],{},[859,14627,14628],{},[24,14629,14630],{},"slice(start?, end?)",[859,14632,14633],{},"截取，返回新数组",[841,14635,14636,14641],{},[859,14637,14638],{},[24,14639,14640],{},"join(sep?)",[859,14642,14643],{},"转字符串",[841,14645,14646,14651],{},[859,14647,14648],{},[24,14649,14650],{},"indexOf(item, from?)",[859,14652,14653],{},"找索引，找不到返回 -1",[841,14655,14656,14661],{},[859,14657,14658],{},[24,14659,14660],{},"lastIndexOf(item, from?)",[859,14662,14663],{},"从后找索引",[841,14665,14666,14671],{},[859,14667,14668],{},[24,14669,14670],{},"includes(item, from?)",[859,14672,14673],{},"是否包含，返回 boolean",[841,14675,14676,14681],{},[859,14677,14678],{},[24,14679,14680],{},"find(cb)",[859,14682,14683],{},"找第一个满足的元素",[841,14685,14686,14691],{},[859,14687,14688],{},[24,14689,14690],{},"findIndex(cb)",[859,14692,14693],{},"找第一个满足的索引",[841,14695,14696,14701],{},[859,14697,14698],{},[24,14699,14700],{},"findLast(cb)",[859,14702,14703],{},"从后找第一个满足的元素（ES2023）",[841,14705,14706,14711],{},[859,14707,14708],{},[24,14709,14710],{},"findLastIndex(cb)",[859,14712,14713],{},"从后找索引（ES2023）",[841,14715,14716,14721],{},[859,14717,14718],{},[24,14719,14720],{},"filter(cb)",[859,14722,14723],{},"过滤，返回新数组",[841,14725,14726,14731],{},[859,14727,14728],{},[24,14729,14730],{},"map(cb)",[859,14732,14733],{},"映射，返回新数组",[841,14735,14736,14741],{},[859,14737,14738],{},[24,14739,14740],{},"flat(depth?)",[859,14742,14743],{},"扁平化",[841,14745,14746,14751],{},[859,14747,14748],{},[24,14749,14750],{},"flatMap(cb)",[859,14752,14753],{},"map + flat",[841,14755,14756,14761],{},[859,14757,14758],{},[24,14759,14760],{},"at(index)",[859,14762,14763],{},"支持负数索引",[841,14765,14766,14771],{},[859,14767,14768],{},[24,14769,14770],{},"with(index, value)",[859,14772,14773],{},"返回替换后的新数组（ES2023）",[841,14775,14776,14781],{},[859,14777,14778],{},[24,14779,14780],{},"toReversed()",[859,14782,14783],{},"反转副本（ES2023）",[841,14785,14786,14791],{},[859,14787,14788],{},[24,14789,14790],{},"toSorted(cmp?)",[859,14792,14793],{},"排序副本（ES2023）",[841,14795,14796,14801],{},[859,14797,14798],{},[24,14799,14800],{},"toSpliced(...)",[859,14802,14803],{},"splice 副本（ES2023）",[841,14805,14806,14809],{},[859,14807,14808],{},"静态方法：",[859,14810],{},[835,14812,14813,14821],{},[838,14814,14815],{},[841,14816,14817,14819],{},[844,14818,14507],{},[844,14820,2241],{},[854,14822,14823,14833,14846,14856],{},[841,14824,14825,14830],{},[859,14826,14827],{},[24,14828,14829],{},"Array.from(iterable, mapFn?, thisArg?)",[859,14831,14832],{},"从类数组\u002F可迭代对象创建数组",[841,14834,14835,14840],{},[859,14836,14837],{},[24,14838,14839],{},"Array.fromAsync(iterable)",[859,14841,14842,14843,14845],{},"异步版 ",[24,14844,3909],{},"（ES2023）",[841,14847,14848,14853],{},[859,14849,14850],{},[24,14851,14852],{},"Array.of(...items)",[859,14854,14855],{},"用参数创建数组",[841,14857,14858,14863],{},[859,14859,14860],{},[24,14861,14862],{},"Array.isArray(x)",[859,14864,14865],{},"判断是否数组",[18,14867,14868,14870,14871,14874],{},[24,14869,8148],{}," 是 ",[24,14872,14873],{},"ArrayConstructor"," ，这个可以在ts的lib.es5.d.ts里找，大致如下：",[96,14876,14878],{"className":3259,"code":14877,"language":3261,"meta":101,"style":101},"interface ArrayConstructor {\n    new (arrayLength?: number): any[];\n    new \u003CT>(arrayLength: number): T[];\n    new \u003CT>(...items: T[]): T[];\n    isArray(arg: any): arg is any[];\n    from\u003CT>(arrayLike: ArrayLike\u003CT>): T[];\n    of\u003CT>(...items: T[]): T[];\n    \u002F\u002F ...\n}\n\ndeclare var Array: ArrayConstructor;\n",[24,14879,14880,14889,14912,14937,14964,14992,15024,15051,15056,15060,15064],{"__ignoreMap":101},[105,14881,14882,14884,14887],{"class":107,"line":108},[105,14883,3519],{"class":111},[105,14885,14886],{"class":241}," ArrayConstructor",[105,14888,116],{"class":115},[105,14890,14891,14893,14895,14898,14900,14903,14905,14907,14909],{"class":107,"line":119},[105,14892,10282],{"class":111},[105,14894,4379],{"class":115},[105,14896,14897],{"class":122},"arrayLength",[105,14899,11562],{"class":111},[105,14901,14902],{"class":129}," number",[105,14904,11956],{"class":115},[105,14906,5017],{"class":111},[105,14908,10435],{"class":129},[105,14910,14911],{"class":115},"[];\n",[105,14913,14914,14916,14919,14921,14923,14925,14927,14929,14931,14933,14935],{"class":107,"line":133},[105,14915,10282],{"class":111},[105,14917,14918],{"class":115}," \u003C",[105,14920,5008],{"class":241},[105,14922,5011],{"class":115},[105,14924,14897],{"class":122},[105,14926,5017],{"class":111},[105,14928,14902],{"class":129},[105,14930,11956],{"class":115},[105,14932,5017],{"class":111},[105,14934,5075],{"class":241},[105,14936,14911],{"class":115},[105,14938,14939,14941,14943,14945,14947,14949,14952,14954,14956,14958,14960,14962],{"class":107,"line":148},[105,14940,10282],{"class":111},[105,14942,14918],{"class":115},[105,14944,5008],{"class":241},[105,14946,5011],{"class":115},[105,14948,10287],{"class":111},[105,14950,14951],{"class":122},"items",[105,14953,5017],{"class":111},[105,14955,5075],{"class":241},[105,14957,10297],{"class":115},[105,14959,5017],{"class":111},[105,14961,5075],{"class":241},[105,14963,14911],{"class":115},[105,14965,14966,14969,14971,14974,14976,14978,14980,14982,14985,14988,14990],{"class":107,"line":163},[105,14967,14968],{"class":241},"    isArray",[105,14970,321],{"class":115},[105,14972,14973],{"class":122},"arg",[105,14975,5017],{"class":111},[105,14977,10435],{"class":129},[105,14979,11956],{"class":115},[105,14981,5017],{"class":111},[105,14983,14984],{"class":122}," arg",[105,14986,14987],{"class":111}," is",[105,14989,10435],{"class":129},[105,14991,14911],{"class":115},[105,14993,14994,14997,14999,15001,15003,15006,15008,15011,15013,15015,15018,15020,15022],{"class":107,"line":179},[105,14995,14996],{"class":241},"    from",[105,14998,234],{"class":115},[105,15000,5008],{"class":241},[105,15002,5011],{"class":115},[105,15004,15005],{"class":122},"arrayLike",[105,15007,5017],{"class":111},[105,15009,15010],{"class":241}," ArrayLike",[105,15012,234],{"class":115},[105,15014,5008],{"class":241},[105,15016,15017],{"class":115},">)",[105,15019,5017],{"class":111},[105,15021,5075],{"class":241},[105,15023,14911],{"class":115},[105,15025,15026,15029,15031,15033,15035,15037,15039,15041,15043,15045,15047,15049],{"class":107,"line":194},[105,15027,15028],{"class":241},"    of",[105,15030,234],{"class":115},[105,15032,5008],{"class":241},[105,15034,5011],{"class":115},[105,15036,10287],{"class":111},[105,15038,14951],{"class":122},[105,15040,5017],{"class":111},[105,15042,5075],{"class":241},[105,15044,10297],{"class":115},[105,15046,5017],{"class":111},[105,15048,5075],{"class":241},[105,15050,14911],{"class":115},[105,15052,15053],{"class":107,"line":209},[105,15054,15055],{"class":630},"    \u002F\u002F ...\n",[105,15057,15058],{"class":107,"line":526},[105,15059,212],{"class":115},[105,15061,15062],{"class":107,"line":531},[105,15063,307],{"emptyLinePlaceholder":306},[105,15065,15066,15068,15070,15073,15075,15077],{"class":107,"line":668},[105,15067,10352],{"class":111},[105,15069,10355],{"class":111},[105,15071,15072],{"class":115}," Array",[105,15074,5017],{"class":111},[105,15076,14886],{"class":241},[105,15078,145],{"class":115},[18,15080,15081],{},"Array是ArrayConstructor类型的变量，它同时是函数。push在Array.prototype上：",[96,15083,15085],{"className":3259,"code":15084,"language":3261,"meta":101,"style":101},"interface Array\u003CT> {\n    push(...items: T[]): number;\n    pop(): T | undefined;\n    map\u003CU>(cb: (v: T, i: number, arr: T[]) => U): U[];\n    \u002F\u002F ...\n}\n\ninterface ArrayConstructor {\n    \u002F\u002F 静态方法\n}\n\ndeclare var Array: ArrayConstructor;\n",[24,15086,15087,15099,15122,15140,15198,15202,15206,15210,15218,15223,15227,15231],{"__ignoreMap":101},[105,15088,15089,15091,15093,15095,15097],{"class":107,"line":108},[105,15090,3519],{"class":111},[105,15092,15072],{"class":241},[105,15094,234],{"class":115},[105,15096,5008],{"class":241},[105,15098,11862],{"class":115},[105,15100,15101,15104,15106,15108,15110,15112,15114,15116,15118,15120],{"class":107,"line":119},[105,15102,15103],{"class":241},"    push",[105,15105,321],{"class":115},[105,15107,10287],{"class":111},[105,15109,14951],{"class":122},[105,15111,5017],{"class":111},[105,15113,5075],{"class":241},[105,15115,10297],{"class":115},[105,15117,5017],{"class":111},[105,15119,14902],{"class":129},[105,15121,145],{"class":115},[105,15123,15124,15127,15130,15132,15134,15136,15138],{"class":107,"line":133},[105,15125,15126],{"class":241},"    pop",[105,15128,15129],{"class":115},"()",[105,15131,5017],{"class":111},[105,15133,5075],{"class":241},[105,15135,5041],{"class":111},[105,15137,11887],{"class":129},[105,15139,145],{"class":115},[105,15141,15142,15145,15147,15150,15152,15155,15157,15159,15161,15163,15165,15167,15170,15172,15174,15176,15179,15181,15183,15185,15187,15190,15192,15194,15196],{"class":107,"line":148},[105,15143,15144],{"class":241},"    map",[105,15146,234],{"class":115},[105,15148,15149],{"class":241},"U",[105,15151,5011],{"class":115},[105,15153,15154],{"class":241},"cb",[105,15156,5017],{"class":111},[105,15158,4379],{"class":115},[105,15160,12572],{"class":122},[105,15162,5017],{"class":111},[105,15164,5075],{"class":241},[105,15166,286],{"class":115},[105,15168,15169],{"class":122},"i",[105,15171,5017],{"class":111},[105,15173,14902],{"class":129},[105,15175,286],{"class":115},[105,15177,15178],{"class":122},"arr",[105,15180,5017],{"class":111},[105,15182,5075],{"class":241},[105,15184,10463],{"class":115},[105,15186,723],{"class":111},[105,15188,15189],{"class":241}," U",[105,15191,11956],{"class":115},[105,15193,5017],{"class":111},[105,15195,15189],{"class":241},[105,15197,14911],{"class":115},[105,15199,15200],{"class":107,"line":163},[105,15201,15055],{"class":630},[105,15203,15204],{"class":107,"line":179},[105,15205,212],{"class":115},[105,15207,15208],{"class":107,"line":194},[105,15209,307],{"emptyLinePlaceholder":306},[105,15211,15212,15214,15216],{"class":107,"line":209},[105,15213,3519],{"class":111},[105,15215,14886],{"class":241},[105,15217,116],{"class":115},[105,15219,15220],{"class":107,"line":526},[105,15221,15222],{"class":630},"    \u002F\u002F 静态方法\n",[105,15224,15225],{"class":107,"line":531},[105,15226,212],{"class":115},[105,15228,15229],{"class":107,"line":668},[105,15230,307],{"emptyLinePlaceholder":306},[105,15232,15233,15235,15237,15239,15241,15243],{"class":107,"line":673},[105,15234,10352],{"class":111},[105,15236,10355],{"class":111},[105,15238,15072],{"class":115},[105,15240,5017],{"class":111},[105,15242,14886],{"class":241},[105,15244,145],{"class":115},[18,15246,15247,15248,15251,15252,15254,15255,15257],{},"可以看到push是定义在",[24,15249,15250],{},"Array\u003CT>","上的，对应Array.prototype上，所有数组实例共享。",[105,15253],{},"或者new Array()都可以得到Array实例。",[105,15256],{},"是语法糖，或者严格来说是数组字面量，由规范里的ArrayLiteral生产式定义。两者创建的对象原型链完全一样。",[14,15259,15260],{"id":15260},"生成器与迭代器",[18,15262,15263],{},"[[生成器与迭代器]]",[14,15265,15266],{"id":15266},"流式操作",[18,15268,15269,15270,15273,15274,31,15277,31,15280,15283,15284],{},"流式操作远不止“读取大文件”。围绕 ",[21,15271,15272],{},"Web Streams API","（",[24,15275,15276],{},"ReadableStream",[24,15278,15279],{},"WritableStream",[24,15281,15282],{},"TransformStream","），可以构建出一整套",[21,15285,15286],{},"数据处理管道",[18,15288,15289],{},"比如说，读取视频文件，你可以把流接到目标：",[96,15291,15293],{"className":3259,"code":15292,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fvideo.mp4');\nconst writable = new WritableStream({\n    write(chunk) {\n        console.log('收到', chunk.length, '字节');\n    }\n});\n\nawait response.body.pipeTo(writable);\n",[24,15294,15295,15314,15330,15342,15365,15369,15373,15377],{"__ignoreMap":101},[105,15296,15297,15299,15301,15303,15305,15307,15309,15312],{"class":107,"line":108},[105,15298,568],{"class":111},[105,15300,6513],{"class":129},[105,15302,574],{"class":111},[105,15304,5428],{"class":111},[105,15306,6000],{"class":241},[105,15308,321],{"class":115},[105,15310,15311],{"class":248},"'\u002Fvideo.mp4'",[105,15313,327],{"class":115},[105,15315,15316,15318,15321,15323,15325,15328],{"class":107,"line":119},[105,15317,568],{"class":111},[105,15319,15320],{"class":129}," writable",[105,15322,574],{"class":111},[105,15324,3496],{"class":111},[105,15326,15327],{"class":241}," WritableStream",[105,15329,3935],{"class":115},[105,15331,15332,15335,15337,15340],{"class":107,"line":133},[105,15333,15334],{"class":241},"    write",[105,15336,321],{"class":115},[105,15338,15339],{"class":122},"chunk",[105,15341,1000],{"class":115},[105,15343,15344,15346,15348,15350,15353,15356,15358,15360,15363],{"class":107,"line":148},[105,15345,10758],{"class":115},[105,15347,639],{"class":241},[105,15349,321],{"class":115},[105,15351,15352],{"class":248},"'收到'",[105,15354,15355],{"class":115},", chunk.",[105,15357,4078],{"class":129},[105,15359,286],{"class":115},[105,15361,15362],{"class":248},"'字节'",[105,15364,327],{"class":115},[105,15366,15367],{"class":107,"line":163},[105,15368,5161],{"class":115},[105,15370,15371],{"class":107,"line":179},[105,15372,3982],{"class":115},[105,15374,15375],{"class":107,"line":194},[105,15376,307],{"emptyLinePlaceholder":306},[105,15378,15379,15381,15384,15387],{"class":107,"line":209},[105,15380,5401],{"class":111},[105,15382,15383],{"class":115}," response.body.",[105,15385,15386],{"class":241},"pipeTo",[105,15388,15389],{"class":115},"(writable);\n",[18,15391,15392],{},"流可以经过转换器，body有个方法叫做pipeThrough，能够把字节转换为字符串，字符串转换为大写：",[96,15394,15396],{"className":3259,"code":15395,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fdata.txt');\nconst stream = response.body\n    .pipeThrough(new TextDecoderStream())      \u002F\u002F 字节 → 字符串\n    .pipeThrough(new TransformStream({         \u002F\u002F 字符串 → 大写\n        transform(chunk, controller) {\n            controller.enqueue(chunk.toUpperCase());\n        }\n    }));\n\nfor await (const chunk of stream) {\n    console.log(chunk);\n}\n",[24,15397,15398,15417,15429,15449,15468,15484,15501,15505,15510,15514,15532,15541],{"__ignoreMap":101},[105,15399,15400,15402,15404,15406,15408,15410,15412,15415],{"class":107,"line":108},[105,15401,568],{"class":111},[105,15403,6513],{"class":129},[105,15405,574],{"class":111},[105,15407,5428],{"class":111},[105,15409,6000],{"class":241},[105,15411,321],{"class":115},[105,15413,15414],{"class":248},"'\u002Fdata.txt'",[105,15416,327],{"class":115},[105,15418,15419,15421,15424,15426],{"class":107,"line":119},[105,15420,568],{"class":111},[105,15422,15423],{"class":129}," stream",[105,15425,574],{"class":111},[105,15427,15428],{"class":115}," response.body\n",[105,15430,15431,15434,15437,15439,15441,15444,15446],{"class":107,"line":133},[105,15432,15433],{"class":115},"    .",[105,15435,15436],{"class":241},"pipeThrough",[105,15438,321],{"class":115},[105,15440,7430],{"class":111},[105,15442,15443],{"class":241}," TextDecoderStream",[105,15445,8272],{"class":115},[105,15447,15448],{"class":630},"\u002F\u002F 字节 → 字符串\n",[105,15450,15451,15453,15455,15457,15459,15462,15465],{"class":107,"line":148},[105,15452,15433],{"class":115},[105,15454,15436],{"class":241},[105,15456,321],{"class":115},[105,15458,7430],{"class":111},[105,15460,15461],{"class":241}," TransformStream",[105,15463,15464],{"class":115},"({         ",[105,15466,15467],{"class":630},"\u002F\u002F 字符串 → 大写\n",[105,15469,15470,15473,15475,15477,15479,15482],{"class":107,"line":163},[105,15471,15472],{"class":241},"        transform",[105,15474,321],{"class":115},[105,15476,15339],{"class":122},[105,15478,286],{"class":115},[105,15480,15481],{"class":122},"controller",[105,15483,1000],{"class":115},[105,15485,15486,15489,15492,15495,15498],{"class":107,"line":179},[105,15487,15488],{"class":115},"            controller.",[105,15490,15491],{"class":241},"enqueue",[105,15493,15494],{"class":115},"(chunk.",[105,15496,15497],{"class":241},"toUpperCase",[105,15499,15500],{"class":115},"());\n",[105,15502,15503],{"class":107,"line":194},[105,15504,12493],{"class":115},[105,15506,15507],{"class":107,"line":209},[105,15508,15509],{"class":115},"    }));\n",[105,15511,15512],{"class":107,"line":526},[105,15513,307],{"emptyLinePlaceholder":306},[105,15515,15516,15518,15520,15522,15524,15527,15529],{"class":107,"line":531},[105,15517,9914],{"class":111},[105,15519,5428],{"class":111},[105,15521,4379],{"class":115},[105,15523,568],{"class":111},[105,15525,15526],{"class":129}," chunk",[105,15528,12710],{"class":111},[105,15530,15531],{"class":115}," stream) {\n",[105,15533,15534,15536,15538],{"class":107,"line":668},[105,15535,6680],{"class":115},[105,15537,639],{"class":241},[105,15539,15540],{"class":115},"(chunk);\n",[105,15542,15543],{"class":107,"line":673},[105,15544,212],{"class":115},[18,15546,15547],{},"你甚至可以用tee把流直接一分为2交给两个消费者：",[96,15549,15551],{"className":3259,"code":15550,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fdata');\nconst [stream1, stream2] = response.body.tee();\n\n\u002F\u002F 两个消费者同时处理\nstream1.pipeTo(new WritableStream({ write(c) { \u002F* ... *\u002F } }));\nstream2.pipeTo(new WritableStream({ write(c) { \u002F* ... *\u002F } }));\n",[24,15552,15553,15572,15599,15603,15608,15640],{"__ignoreMap":101},[105,15554,15555,15557,15559,15561,15563,15565,15567,15570],{"class":107,"line":108},[105,15556,568],{"class":111},[105,15558,6513],{"class":129},[105,15560,574],{"class":111},[105,15562,5428],{"class":111},[105,15564,6000],{"class":241},[105,15566,321],{"class":115},[105,15568,15569],{"class":248},"'\u002Fdata'",[105,15571,327],{"class":115},[105,15573,15574,15576,15579,15582,15584,15587,15590,15592,15594,15597],{"class":107,"line":119},[105,15575,568],{"class":111},[105,15577,15578],{"class":115}," [",[105,15580,15581],{"class":129},"stream1",[105,15583,286],{"class":115},[105,15585,15586],{"class":129},"stream2",[105,15588,15589],{"class":115},"] ",[105,15591,245],{"class":111},[105,15593,15383],{"class":115},[105,15595,15596],{"class":241},"tee",[105,15598,665],{"class":115},[105,15600,15601],{"class":107,"line":133},[105,15602,307],{"emptyLinePlaceholder":306},[105,15604,15605],{"class":107,"line":148},[105,15606,15607],{"class":630},"\u002F\u002F 两个消费者同时处理\n",[105,15609,15610,15613,15615,15617,15619,15621,15624,15627,15629,15632,15635,15637],{"class":107,"line":163},[105,15611,15612],{"class":115},"stream1.",[105,15614,15386],{"class":241},[105,15616,321],{"class":115},[105,15618,7430],{"class":111},[105,15620,15327],{"class":241},[105,15622,15623],{"class":115},"({ ",[105,15625,15626],{"class":241},"write",[105,15628,321],{"class":115},[105,15630,15631],{"class":122},"c",[105,15633,15634],{"class":115},") { ",[105,15636,729],{"class":630},[105,15638,15639],{"class":115}," } }));\n",[105,15641,15642,15645,15647,15649,15651,15653,15655,15657,15659,15661,15663,15665],{"class":107,"line":179},[105,15643,15644],{"class":115},"stream2.",[105,15646,15386],{"class":241},[105,15648,321],{"class":115},[105,15650,7430],{"class":111},[105,15652,15327],{"class":241},[105,15654,15623],{"class":115},[105,15656,15626],{"class":241},[105,15658,321],{"class":115},[105,15660,15631],{"class":122},[105,15662,15634],{"class":115},[105,15664,729],{"class":630},[105,15666,15639],{"class":115},[1908,15668,15669],{"id":15669},"常用场景",[18,15671,15672],{},"解压操作gz；",[96,15674,15676],{"className":3259,"code":15675,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fdata.gz');\nconst stream = response.body\n    .pipeThrough(new DecompressionStream('gzip'));\n\nconst text = await new Response(stream).text();\nconsole.log(text);\n",[24,15677,15678,15697,15707,15727,15731,15754],{"__ignoreMap":101},[105,15679,15680,15682,15684,15686,15688,15690,15692,15695],{"class":107,"line":108},[105,15681,568],{"class":111},[105,15683,6513],{"class":129},[105,15685,574],{"class":111},[105,15687,5428],{"class":111},[105,15689,6000],{"class":241},[105,15691,321],{"class":115},[105,15693,15694],{"class":248},"'\u002Fdata.gz'",[105,15696,327],{"class":115},[105,15698,15699,15701,15703,15705],{"class":107,"line":119},[105,15700,568],{"class":111},[105,15702,15423],{"class":129},[105,15704,574],{"class":111},[105,15706,15428],{"class":115},[105,15708,15709,15711,15713,15715,15717,15720,15722,15725],{"class":107,"line":133},[105,15710,15433],{"class":115},[105,15712,15436],{"class":241},[105,15714,321],{"class":115},[105,15716,7430],{"class":111},[105,15718,15719],{"class":241}," DecompressionStream",[105,15721,321],{"class":115},[105,15723,15724],{"class":248},"'gzip'",[105,15726,13529],{"class":115},[105,15728,15729],{"class":107,"line":148},[105,15730,307],{"emptyLinePlaceholder":306},[105,15732,15733,15735,15738,15740,15742,15744,15747,15750,15752],{"class":107,"line":163},[105,15734,568],{"class":111},[105,15736,15737],{"class":129}," text",[105,15739,574],{"class":111},[105,15741,5428],{"class":111},[105,15743,3496],{"class":111},[105,15745,15746],{"class":241}," Response",[105,15748,15749],{"class":115},"(stream).",[105,15751,392],{"class":241},[105,15753,665],{"class":115},[105,15755,15756,15758,15760],{"class":107,"line":179},[105,15757,636],{"class":115},[105,15759,639],{"class":241},[105,15761,15762],{"class":115},"(text);\n",[18,15764,15765],{},"压缩为gzip：",[96,15767,15769],{"className":3259,"code":15768,"language":3261,"meta":101,"style":101},"const stream = new Blob(['hello world'])\n    .stream()\n    .pipeThrough(new CompressionStream('gzip'));\n\nconst compressed = await new Response(stream).arrayBuffer();\n",[24,15770,15771,15792,15801,15820,15824],{"__ignoreMap":101},[105,15772,15773,15775,15777,15779,15781,15784,15786,15789],{"class":107,"line":108},[105,15774,568],{"class":111},[105,15776,15423],{"class":129},[105,15778,574],{"class":111},[105,15780,3496],{"class":111},[105,15782,15783],{"class":241}," Blob",[105,15785,6472],{"class":115},[105,15787,15788],{"class":248},"'hello world'",[105,15790,15791],{"class":115},"])\n",[105,15793,15794,15796,15799],{"class":107,"line":119},[105,15795,15433],{"class":115},[105,15797,15798],{"class":241},"stream",[105,15800,5192],{"class":115},[105,15802,15803,15805,15807,15809,15811,15814,15816,15818],{"class":107,"line":133},[105,15804,15433],{"class":115},[105,15806,15436],{"class":241},[105,15808,321],{"class":115},[105,15810,7430],{"class":111},[105,15812,15813],{"class":241}," CompressionStream",[105,15815,321],{"class":115},[105,15817,15724],{"class":248},[105,15819,13529],{"class":115},[105,15821,15822],{"class":107,"line":148},[105,15823,307],{"emptyLinePlaceholder":306},[105,15825,15826,15828,15831,15833,15835,15837,15839,15841,15844],{"class":107,"line":163},[105,15827,568],{"class":111},[105,15829,15830],{"class":129}," compressed",[105,15832,574],{"class":111},[105,15834,5428],{"class":111},[105,15836,3496],{"class":111},[105,15838,15746],{"class":241},[105,15840,15749],{"class":115},[105,15842,15843],{"class":241},"arrayBuffer",[105,15845,665],{"class":115},[18,15847,15848],{},"文本接编码；",[96,15850,15852],{"className":3259,"code":15851,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fdata.txt');\nconst stream = response.body\n    .pipeThrough(new TextDecoderStream());\n\nfor await (const chunk of stream) {\n    console.log(chunk);  \u002F\u002F 字符串\n}\n\nconst stream = new ReadableStream({\n    start(controller) {\n        controller.enqueue('hello');\n        controller.enqueue('world');\n        controller.close();\n    }\n}).pipeThrough(new TextEncoderStream());\n",[24,15853,15854,15872,15882,15896,15900,15916,15928,15932,15936,15951,15962,15975,15988,15996,16000],{"__ignoreMap":101},[105,15855,15856,15858,15860,15862,15864,15866,15868,15870],{"class":107,"line":108},[105,15857,568],{"class":111},[105,15859,6513],{"class":129},[105,15861,574],{"class":111},[105,15863,5428],{"class":111},[105,15865,6000],{"class":241},[105,15867,321],{"class":115},[105,15869,15414],{"class":248},[105,15871,327],{"class":115},[105,15873,15874,15876,15878,15880],{"class":107,"line":119},[105,15875,568],{"class":111},[105,15877,15423],{"class":129},[105,15879,574],{"class":111},[105,15881,15428],{"class":115},[105,15883,15884,15886,15888,15890,15892,15894],{"class":107,"line":133},[105,15885,15433],{"class":115},[105,15887,15436],{"class":241},[105,15889,321],{"class":115},[105,15891,7430],{"class":111},[105,15893,15443],{"class":241},[105,15895,15500],{"class":115},[105,15897,15898],{"class":107,"line":148},[105,15899,307],{"emptyLinePlaceholder":306},[105,15901,15902,15904,15906,15908,15910,15912,15914],{"class":107,"line":163},[105,15903,9914],{"class":111},[105,15905,5428],{"class":111},[105,15907,4379],{"class":115},[105,15909,568],{"class":111},[105,15911,15526],{"class":129},[105,15913,12710],{"class":111},[105,15915,15531],{"class":115},[105,15917,15918,15920,15922,15925],{"class":107,"line":179},[105,15919,6680],{"class":115},[105,15921,639],{"class":241},[105,15923,15924],{"class":115},"(chunk);  ",[105,15926,15927],{"class":630},"\u002F\u002F 字符串\n",[105,15929,15930],{"class":107,"line":194},[105,15931,212],{"class":115},[105,15933,15934],{"class":107,"line":209},[105,15935,307],{"emptyLinePlaceholder":306},[105,15937,15938,15940,15942,15944,15946,15949],{"class":107,"line":526},[105,15939,568],{"class":111},[105,15941,15423],{"class":129},[105,15943,574],{"class":111},[105,15945,3496],{"class":111},[105,15947,15948],{"class":241}," ReadableStream",[105,15950,3935],{"class":115},[105,15952,15953,15956,15958,15960],{"class":107,"line":531},[105,15954,15955],{"class":241},"    start",[105,15957,321],{"class":115},[105,15959,15481],{"class":122},[105,15961,1000],{"class":115},[105,15963,15964,15967,15969,15971,15973],{"class":107,"line":668},[105,15965,15966],{"class":115},"        controller.",[105,15968,15491],{"class":241},[105,15970,321],{"class":115},[105,15972,3280],{"class":248},[105,15974,327],{"class":115},[105,15976,15977,15979,15981,15983,15986],{"class":107,"line":673},[105,15978,15966],{"class":115},[105,15980,15491],{"class":241},[105,15982,321],{"class":115},[105,15984,15985],{"class":248},"'world'",[105,15987,327],{"class":115},[105,15989,15990,15992,15994],{"class":107,"line":679},[105,15991,15966],{"class":115},[105,15993,5189],{"class":241},[105,15995,665],{"class":115},[105,15997,15998],{"class":107,"line":689},[105,15999,5161],{"class":115},[105,16001,16002,16005,16007,16009,16011,16014],{"class":107,"line":699},[105,16003,16004],{"class":115},"}).",[105,16006,15436],{"class":241},[105,16008,321],{"class":115},[105,16010,7430],{"class":111},[105,16012,16013],{"class":241}," TextEncoderStream",[105,16015,15500],{"class":115},[1908,16017,16018],{"id":16018},"流式数据格式解析",[18,16020,16021],{},"NDJSON，每行有一个json:",[96,16023,16025],{"className":3259,"code":16024,"language":3261,"meta":101,"style":101},"async function* parseNDJSON(stream) {\n    const reader = stream\n        .pipeThrough(new TextDecoderStream())\n        .getReader();\n    let buffer = '';\n\n    while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buffer += value;\n        const lines = buffer.split('\\n');\n        buffer = lines.pop();\n        for (const line of lines) {\n            if (line.trim()) yield JSON.parse(line);\n        }\n    }\n}\n\nconst response = await fetch('\u002Flogs.ndjson');\nfor await (const obj of parseNDJSON(response.body)) {\n    console.log(obj);\n}\n",[24,16026,16027,16043,16055,16070,16079,16093,16097,16107,16134,16146,16156,16183,16197,16213,16240,16244,16248,16252,16256,16275,16294,16302],{"__ignoreMap":101},[105,16028,16029,16031,16034,16037,16039,16041],{"class":107,"line":108},[105,16030,9239],{"class":111},[105,16032,16033],{"class":111}," function*",[105,16035,16036],{"class":241}," parseNDJSON",[105,16038,321],{"class":115},[105,16040,15798],{"class":122},[105,16042,1000],{"class":115},[105,16044,16045,16047,16050,16052],{"class":107,"line":119},[105,16046,7238],{"class":111},[105,16048,16049],{"class":129}," reader",[105,16051,574],{"class":111},[105,16053,16054],{"class":115}," stream\n",[105,16056,16057,16059,16061,16063,16065,16067],{"class":107,"line":133},[105,16058,5527],{"class":115},[105,16060,15436],{"class":241},[105,16062,321],{"class":115},[105,16064,7430],{"class":111},[105,16066,15443],{"class":241},[105,16068,16069],{"class":115},"())\n",[105,16071,16072,16074,16077],{"class":107,"line":148},[105,16073,5527],{"class":115},[105,16075,16076],{"class":241},"getReader",[105,16078,665],{"class":115},[105,16080,16081,16083,16086,16088,16091],{"class":107,"line":163},[105,16082,14332],{"class":111},[105,16084,16085],{"class":115}," buffer ",[105,16087,245],{"class":111},[105,16089,16090],{"class":248}," ''",[105,16092,145],{"class":115},[105,16094,16095],{"class":107,"line":179},[105,16096,307],{"emptyLinePlaceholder":306},[105,16098,16099,16101,16103,16105],{"class":107,"line":194},[105,16100,14348],{"class":111},[105,16102,4379],{"class":115},[105,16104,9158],{"class":129},[105,16106,1000],{"class":115},[105,16108,16109,16111,16113,16116,16118,16120,16122,16124,16126,16129,16132],{"class":107,"line":209},[105,16110,7284],{"class":111},[105,16112,726],{"class":115},[105,16114,16115],{"class":129},"done",[105,16117,286],{"class":115},[105,16119,8451],{"class":129},[105,16121,3973],{"class":115},[105,16123,245],{"class":111},[105,16125,5428],{"class":111},[105,16127,16128],{"class":115}," reader.",[105,16130,16131],{"class":241},"read",[105,16133,665],{"class":115},[105,16135,16136,16138,16141,16144],{"class":107,"line":526},[105,16137,12388],{"class":111},[105,16139,16140],{"class":115}," (done) ",[105,16142,16143],{"class":111},"break",[105,16145,145],{"class":115},[105,16147,16148,16151,16154],{"class":107,"line":531},[105,16149,16150],{"class":115},"        buffer ",[105,16152,16153],{"class":111},"+=",[105,16155,12655],{"class":115},[105,16157,16158,16160,16163,16165,16168,16171,16173,16176,16179,16181],{"class":107,"line":668},[105,16159,7284],{"class":111},[105,16161,16162],{"class":129}," lines",[105,16164,574],{"class":111},[105,16166,16167],{"class":115}," buffer.",[105,16169,16170],{"class":241},"split",[105,16172,321],{"class":115},[105,16174,16175],{"class":248},"'",[105,16177,16178],{"class":129},"\\n",[105,16180,16175],{"class":248},[105,16182,327],{"class":115},[105,16184,16185,16187,16189,16192,16195],{"class":107,"line":673},[105,16186,16150],{"class":115},[105,16188,245],{"class":111},[105,16190,16191],{"class":115}," lines.",[105,16193,16194],{"class":241},"pop",[105,16196,665],{"class":115},[105,16198,16199,16201,16203,16205,16208,16210],{"class":107,"line":679},[105,16200,12700],{"class":111},[105,16202,4379],{"class":115},[105,16204,568],{"class":111},[105,16206,16207],{"class":129}," line",[105,16209,12710],{"class":111},[105,16211,16212],{"class":115}," lines) {\n",[105,16214,16215,16217,16220,16223,16226,16229,16232,16234,16237],{"class":107,"line":689},[105,16216,5980],{"class":111},[105,16218,16219],{"class":115}," (line.",[105,16221,16222],{"class":241},"trim",[105,16224,16225],{"class":115},"()) ",[105,16227,16228],{"class":111},"yield",[105,16230,16231],{"class":129}," JSON",[105,16233,5381],{"class":115},[105,16235,16236],{"class":241},"parse",[105,16238,16239],{"class":115},"(line);\n",[105,16241,16242],{"class":107,"line":699},[105,16243,12493],{"class":115},[105,16245,16246],{"class":107,"line":704},[105,16247,5161],{"class":115},[105,16249,16250],{"class":107,"line":710},[105,16251,212],{"class":115},[105,16253,16254],{"class":107,"line":1136},[105,16255,307],{"emptyLinePlaceholder":306},[105,16257,16258,16260,16262,16264,16266,16268,16270,16273],{"class":107,"line":1149},[105,16259,568],{"class":111},[105,16261,6513],{"class":129},[105,16263,574],{"class":111},[105,16265,5428],{"class":111},[105,16267,6000],{"class":241},[105,16269,321],{"class":115},[105,16271,16272],{"class":248},"'\u002Flogs.ndjson'",[105,16274,327],{"class":115},[105,16276,16277,16279,16281,16283,16285,16287,16289,16291],{"class":107,"line":3855},[105,16278,9914],{"class":111},[105,16280,5428],{"class":111},[105,16282,4379],{"class":115},[105,16284,568],{"class":111},[105,16286,10206],{"class":129},[105,16288,12710],{"class":111},[105,16290,16036],{"class":241},[105,16292,16293],{"class":115},"(response.body)) {\n",[105,16295,16296,16298,16300],{"class":107,"line":3865},[105,16297,6680],{"class":115},[105,16299,639],{"class":241},[105,16301,10800],{"class":115},[105,16303,16304],{"class":107,"line":3875},[105,16305,212],{"class":115},[18,16307,16308,16309,1998,16312,16315,16316,16319],{},"SSE数据处理，sse服务器可以发送默认消息和自定义事件，消息没有event字段，浏览器默认按照message事件处理，客户端用",[24,16310,16311],{},"es.onmessage",[24,16313,16314],{},"es.addEventListener('message', ...)"," 接收。自定义事件有event字段，指定事件类型，- 客户端用 ",[24,16317,16318],{},"es.addEventListener('eventName', ...)"," 接收。",[96,16321,16323],{"className":3259,"code":16322,"language":3261,"meta":101,"style":101},"async function* parseSSE(stream) {\n    const reader = stream\n        .pipeThrough(new TextDecoderStream())\n        .getReader();\n    let buffer = '';\n\n    while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buffer += value;\n        const events = buffer.split('\\n\\n');\n        buffer = events.pop();\n        for (const event of events) {\n            const data = event\n                .split('\\n')\n                .filter(l => l.startsWith('data:'))\n                .map(l => l.slice(5).trim())\n                .join('\\n');\n            yield data;\n        }\n    }\n}\n",[24,16324,16325,16340,16350,16364,16372,16384,16388,16398,16422,16432,16440,16464,16477,16493,16504,16521,16548,16574,16591,16599,16603,16607],{"__ignoreMap":101},[105,16326,16327,16329,16331,16334,16336,16338],{"class":107,"line":108},[105,16328,9239],{"class":111},[105,16330,16033],{"class":111},[105,16332,16333],{"class":241}," parseSSE",[105,16335,321],{"class":115},[105,16337,15798],{"class":122},[105,16339,1000],{"class":115},[105,16341,16342,16344,16346,16348],{"class":107,"line":119},[105,16343,7238],{"class":111},[105,16345,16049],{"class":129},[105,16347,574],{"class":111},[105,16349,16054],{"class":115},[105,16351,16352,16354,16356,16358,16360,16362],{"class":107,"line":133},[105,16353,5527],{"class":115},[105,16355,15436],{"class":241},[105,16357,321],{"class":115},[105,16359,7430],{"class":111},[105,16361,15443],{"class":241},[105,16363,16069],{"class":115},[105,16365,16366,16368,16370],{"class":107,"line":148},[105,16367,5527],{"class":115},[105,16369,16076],{"class":241},[105,16371,665],{"class":115},[105,16373,16374,16376,16378,16380,16382],{"class":107,"line":163},[105,16375,14332],{"class":111},[105,16377,16085],{"class":115},[105,16379,245],{"class":111},[105,16381,16090],{"class":248},[105,16383,145],{"class":115},[105,16385,16386],{"class":107,"line":179},[105,16387,307],{"emptyLinePlaceholder":306},[105,16389,16390,16392,16394,16396],{"class":107,"line":194},[105,16391,14348],{"class":111},[105,16393,4379],{"class":115},[105,16395,9158],{"class":129},[105,16397,1000],{"class":115},[105,16399,16400,16402,16404,16406,16408,16410,16412,16414,16416,16418,16420],{"class":107,"line":209},[105,16401,7284],{"class":111},[105,16403,726],{"class":115},[105,16405,16115],{"class":129},[105,16407,286],{"class":115},[105,16409,8451],{"class":129},[105,16411,3973],{"class":115},[105,16413,245],{"class":111},[105,16415,5428],{"class":111},[105,16417,16128],{"class":115},[105,16419,16131],{"class":241},[105,16421,665],{"class":115},[105,16423,16424,16426,16428,16430],{"class":107,"line":526},[105,16425,12388],{"class":111},[105,16427,16140],{"class":115},[105,16429,16143],{"class":111},[105,16431,145],{"class":115},[105,16433,16434,16436,16438],{"class":107,"line":531},[105,16435,16150],{"class":115},[105,16437,16153],{"class":111},[105,16439,12655],{"class":115},[105,16441,16442,16444,16447,16449,16451,16453,16455,16457,16460,16462],{"class":107,"line":668},[105,16443,7284],{"class":111},[105,16445,16446],{"class":129}," events",[105,16448,574],{"class":111},[105,16450,16167],{"class":115},[105,16452,16170],{"class":241},[105,16454,321],{"class":115},[105,16456,16175],{"class":248},[105,16458,16459],{"class":129},"\\n\\n",[105,16461,16175],{"class":248},[105,16463,327],{"class":115},[105,16465,16466,16468,16470,16473,16475],{"class":107,"line":673},[105,16467,16150],{"class":115},[105,16469,245],{"class":111},[105,16471,16472],{"class":115}," events.",[105,16474,16194],{"class":241},[105,16476,665],{"class":115},[105,16478,16479,16481,16483,16485,16488,16490],{"class":107,"line":679},[105,16480,12700],{"class":111},[105,16482,4379],{"class":115},[105,16484,568],{"class":111},[105,16486,16487],{"class":129}," event",[105,16489,12710],{"class":111},[105,16491,16492],{"class":115}," events) {\n",[105,16494,16495,16497,16499,16501],{"class":107,"line":689},[105,16496,6307],{"class":111},[105,16498,9192],{"class":129},[105,16500,574],{"class":111},[105,16502,16503],{"class":115}," event\n",[105,16505,16506,16509,16511,16513,16515,16517,16519],{"class":107,"line":699},[105,16507,16508],{"class":115},"                .",[105,16510,16170],{"class":241},[105,16512,321],{"class":115},[105,16514,16175],{"class":248},[105,16516,16178],{"class":129},[105,16518,16175],{"class":248},[105,16520,1529],{"class":115},[105,16522,16523,16525,16527,16529,16532,16534,16537,16540,16542,16545],{"class":107,"line":704},[105,16524,16508],{"class":115},[105,16526,5795],{"class":241},[105,16528,321],{"class":115},[105,16530,16531],{"class":122},"l",[105,16533,5150],{"class":111},[105,16535,16536],{"class":115}," l.",[105,16538,16539],{"class":241},"startsWith",[105,16541,321],{"class":115},[105,16543,16544],{"class":248},"'data:'",[105,16546,16547],{"class":115},"))\n",[105,16549,16550,16552,16554,16556,16558,16560,16562,16564,16566,16568,16570,16572],{"class":107,"line":710},[105,16551,16508],{"class":115},[105,16553,5820],{"class":241},[105,16555,321],{"class":115},[105,16557,16531],{"class":122},[105,16559,5150],{"class":111},[105,16561,16536],{"class":115},[105,16563,4291],{"class":241},[105,16565,321],{"class":115},[105,16567,928],{"class":129},[105,16569,5642],{"class":115},[105,16571,16222],{"class":241},[105,16573,16069],{"class":115},[105,16575,16576,16578,16581,16583,16585,16587,16589],{"class":107,"line":1136},[105,16577,16508],{"class":115},[105,16579,16580],{"class":241},"join",[105,16582,321],{"class":115},[105,16584,16175],{"class":248},[105,16586,16178],{"class":129},[105,16588,16175],{"class":248},[105,16590,327],{"class":115},[105,16592,16593,16596],{"class":107,"line":1149},[105,16594,16595],{"class":111},"            yield",[105,16597,16598],{"class":115}," data;\n",[105,16600,16601],{"class":107,"line":3855},[105,16602,12493],{"class":115},[105,16604,16605],{"class":107,"line":3865},[105,16606,5161],{"class":115},[105,16608,16609],{"class":107,"line":3875},[105,16610,212],{"class":115},[18,16612,16613],{},"CSV流式解析，CSV的结构如下：",[96,16615,16619],{"className":16616,"code":16617,"language":16618,"meta":101,"style":101},"language-csv shiki shiki-themes github-light github-dark","name,age,city\nAlice,30,Beijing\nBob,25,Shanghai\nCharlie,35,Guangzhou\n...\n","csv",[24,16620,16621,16626,16631,16636,16641],{"__ignoreMap":101},[105,16622,16623],{"class":107,"line":108},[105,16624,16625],{},"name,age,city\n",[105,16627,16628],{"class":107,"line":119},[105,16629,16630],{},"Alice,30,Beijing\n",[105,16632,16633],{"class":107,"line":133},[105,16634,16635],{},"Bob,25,Shanghai\n",[105,16637,16638],{"class":107,"line":148},[105,16639,16640],{},"Charlie,35,Guangzhou\n",[105,16642,16643],{"class":107,"line":163},[105,16644,7993],{},[18,16646,16647,16648,16651],{},"每条记录以\\n或者\\r\\n结束，列分隔用,分隔，不像json有嵌套结构，不需要等整个文档解析完。无全局头，第一行是列名，之后每行独立。因此我们需要从ReadableStream或者文件读取一块数据，用TextDecorder按UTF-8解码，按照\\n切分，最后一行可能不完整，留在缓冲区。把每行按 ",[24,16649,16650],{},","," 切分成字段，每行独立处理，不需要等下一行。",[96,16653,16655],{"className":3259,"code":16654,"language":3261,"meta":101,"style":101},"async function* parseCSV(stream) {\n    const reader = stream\n        .pipeThrough(new TextDecoderStream())\n        .getReader();\n    let buffer = '';\n    let headers = null;\n\n    while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buffer += value;\n        const lines = buffer.split('\\n');\n        buffer = lines.pop();\n\n        for (const line of lines) {\n            const cells = line.split(',');\n            if (!headers) {\n                headers = cells;\n            } else {\n                yield Object.fromEntries(\n                    headers.map((h, i) => [h, cells[i]])\n                );\n            }\n        }\n    }\n}\n\n\u002F\u002F 使用\nconst response = await fetch('\u002Fdata.csv');\nfor await (const row of parseCSV(response.body)) {\n    console.log(row);  \u002F\u002F { name: 'Alice', age: '30', city: 'Beijing' }\n}\n",[24,16656,16657,16672,16682,16696,16704,16716,16729,16733,16743,16767,16777,16785,16807,16819,16823,16837,16858,16870,16880,16888,16900,16922,16926,16930,16934,16938,16942,16946,16951,16970,16989,17001],{"__ignoreMap":101},[105,16658,16659,16661,16663,16666,16668,16670],{"class":107,"line":108},[105,16660,9239],{"class":111},[105,16662,16033],{"class":111},[105,16664,16665],{"class":241}," parseCSV",[105,16667,321],{"class":115},[105,16669,15798],{"class":122},[105,16671,1000],{"class":115},[105,16673,16674,16676,16678,16680],{"class":107,"line":119},[105,16675,7238],{"class":111},[105,16677,16049],{"class":129},[105,16679,574],{"class":111},[105,16681,16054],{"class":115},[105,16683,16684,16686,16688,16690,16692,16694],{"class":107,"line":133},[105,16685,5527],{"class":115},[105,16687,15436],{"class":241},[105,16689,321],{"class":115},[105,16691,7430],{"class":111},[105,16693,15443],{"class":241},[105,16695,16069],{"class":115},[105,16697,16698,16700,16702],{"class":107,"line":148},[105,16699,5527],{"class":115},[105,16701,16076],{"class":241},[105,16703,665],{"class":115},[105,16705,16706,16708,16710,16712,16714],{"class":107,"line":163},[105,16707,14332],{"class":111},[105,16709,16085],{"class":115},[105,16711,245],{"class":111},[105,16713,16090],{"class":248},[105,16715,145],{"class":115},[105,16717,16718,16720,16723,16725,16727],{"class":107,"line":179},[105,16719,14332],{"class":111},[105,16721,16722],{"class":115}," headers ",[105,16724,245],{"class":111},[105,16726,5044],{"class":129},[105,16728,145],{"class":115},[105,16730,16731],{"class":107,"line":194},[105,16732,307],{"emptyLinePlaceholder":306},[105,16734,16735,16737,16739,16741],{"class":107,"line":209},[105,16736,14348],{"class":111},[105,16738,4379],{"class":115},[105,16740,9158],{"class":129},[105,16742,1000],{"class":115},[105,16744,16745,16747,16749,16751,16753,16755,16757,16759,16761,16763,16765],{"class":107,"line":526},[105,16746,7284],{"class":111},[105,16748,726],{"class":115},[105,16750,16115],{"class":129},[105,16752,286],{"class":115},[105,16754,8451],{"class":129},[105,16756,3973],{"class":115},[105,16758,245],{"class":111},[105,16760,5428],{"class":111},[105,16762,16128],{"class":115},[105,16764,16131],{"class":241},[105,16766,665],{"class":115},[105,16768,16769,16771,16773,16775],{"class":107,"line":531},[105,16770,12388],{"class":111},[105,16772,16140],{"class":115},[105,16774,16143],{"class":111},[105,16776,145],{"class":115},[105,16778,16779,16781,16783],{"class":107,"line":668},[105,16780,16150],{"class":115},[105,16782,16153],{"class":111},[105,16784,12655],{"class":115},[105,16786,16787,16789,16791,16793,16795,16797,16799,16801,16803,16805],{"class":107,"line":673},[105,16788,7284],{"class":111},[105,16790,16162],{"class":129},[105,16792,574],{"class":111},[105,16794,16167],{"class":115},[105,16796,16170],{"class":241},[105,16798,321],{"class":115},[105,16800,16175],{"class":248},[105,16802,16178],{"class":129},[105,16804,16175],{"class":248},[105,16806,327],{"class":115},[105,16808,16809,16811,16813,16815,16817],{"class":107,"line":679},[105,16810,16150],{"class":115},[105,16812,245],{"class":111},[105,16814,16191],{"class":115},[105,16816,16194],{"class":241},[105,16818,665],{"class":115},[105,16820,16821],{"class":107,"line":689},[105,16822,307],{"emptyLinePlaceholder":306},[105,16824,16825,16827,16829,16831,16833,16835],{"class":107,"line":699},[105,16826,12700],{"class":111},[105,16828,4379],{"class":115},[105,16830,568],{"class":111},[105,16832,16207],{"class":129},[105,16834,12710],{"class":111},[105,16836,16212],{"class":115},[105,16838,16839,16841,16844,16846,16849,16851,16853,16856],{"class":107,"line":704},[105,16840,6307],{"class":111},[105,16842,16843],{"class":129}," cells",[105,16845,574],{"class":111},[105,16847,16848],{"class":115}," line.",[105,16850,16170],{"class":241},[105,16852,321],{"class":115},[105,16854,16855],{"class":248},"','",[105,16857,327],{"class":115},[105,16859,16860,16862,16864,16867],{"class":107,"line":710},[105,16861,5980],{"class":111},[105,16863,4379],{"class":115},[105,16865,16866],{"class":111},"!",[105,16868,16869],{"class":115},"headers) {\n",[105,16871,16872,16875,16877],{"class":107,"line":1136},[105,16873,16874],{"class":115},"                headers ",[105,16876,245],{"class":111},[105,16878,16879],{"class":115}," cells;\n",[105,16881,16882,16884,16886],{"class":107,"line":1149},[105,16883,12466],{"class":115},[105,16885,13192],{"class":111},[105,16887,116],{"class":115},[105,16889,16890,16893,16895,16898],{"class":107,"line":3855},[105,16891,16892],{"class":111},"                yield",[105,16894,11354],{"class":115},[105,16896,16897],{"class":241},"fromEntries",[105,16899,583],{"class":115},[105,16901,16902,16905,16907,16909,16911,16913,16915,16917,16919],{"class":107,"line":3865},[105,16903,16904],{"class":115},"                    headers.",[105,16906,5820],{"class":241},[105,16908,7298],{"class":115},[105,16910,5147],{"class":122},[105,16912,286],{"class":115},[105,16914,15169],{"class":122},[105,16916,2854],{"class":115},[105,16918,723],{"class":111},[105,16920,16921],{"class":115}," [h, cells[i]])\n",[105,16923,16924],{"class":107,"line":3875},[105,16925,12461],{"class":115},[105,16927,16928],{"class":107,"line":3884},[105,16929,12488],{"class":115},[105,16931,16932],{"class":107,"line":3889},[105,16933,12493],{"class":115},[105,16935,16936],{"class":107,"line":3900},[105,16937,5161],{"class":115},[105,16939,16940],{"class":107,"line":3917},[105,16941,212],{"class":115},[105,16943,16944],{"class":107,"line":3922},[105,16945,307],{"emptyLinePlaceholder":306},[105,16947,16948],{"class":107,"line":3938},[105,16949,16950],{"class":630},"\u002F\u002F 使用\n",[105,16952,16953,16955,16957,16959,16961,16963,16965,16968],{"class":107,"line":3954},[105,16954,568],{"class":111},[105,16956,6513],{"class":129},[105,16958,574],{"class":111},[105,16960,5428],{"class":111},[105,16962,6000],{"class":241},[105,16964,321],{"class":115},[105,16966,16967],{"class":248},"'\u002Fdata.csv'",[105,16969,327],{"class":115},[105,16971,16972,16974,16976,16978,16980,16983,16985,16987],{"class":107,"line":3965},[105,16973,9914],{"class":111},[105,16975,5428],{"class":111},[105,16977,4379],{"class":115},[105,16979,568],{"class":111},[105,16981,16982],{"class":129}," row",[105,16984,12710],{"class":111},[105,16986,16665],{"class":241},[105,16988,16293],{"class":115},[105,16990,16991,16993,16995,16998],{"class":107,"line":3979},[105,16992,6680],{"class":115},[105,16994,639],{"class":241},[105,16996,16997],{"class":115},"(row);  ",[105,16999,17000],{"class":630},"\u002F\u002F { name: 'Alice', age: '30', city: 'Beijing' }\n",[105,17002,17003],{"class":107,"line":3985},[105,17004,212],{"class":115},[18,17006,17007],{},"SSR流式HTML渲染，如果这个放在web worker之后就需要使用postMessage把HTML片段传回主线程，由主线程来插入DOM，这是因为Worker不能操作DOM，因为DOM不是线程安全的，多线程操作DOM会导致状态不一致，worker是独立环境，也没有DOM api",[18,17009,17010],{},"这个有点像之前微软设计STA模型的时候同样的根本性考虑，为了保护一个非线程安全的共享资源，比如限制它只能被特定的线程访问。DOM和COM组件都不是线程安全的。",[18,17012,17013],{},"它们的解决方案也很相似，两者都采用了代理proxy+消息传递的模式来安全地转发调用，COM STA中，来自其他线程的调用视图访问STA对象时候，COM运行时不会直接调用，而是通过一个代理将调用编组成一条窗口消息，投递到对象所属STA线程的消息队列里，对象线程通过GetMessage\u002FDispatchMessage取出消息并处理这个消息。",[18,17015,17016],{},"Worker也一样，他必须通过postMessage将数据序列化后发送给主线程，主线程收到了消息之后再自己的执行环境下执行真正的DOM操作。",[96,17018,17020],{"className":3259,"code":17019,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fpage');\n\nconst stream = response.body\n    .pipeThrough(new TextDecoderStream())\n    .pipeThrough(new TransformStream({\n        transform(chunk, controller) {\n            \u002F\u002F 在 HTML 到达时逐块插入 DOM\n            document.getElementById('app').insertAdjacentHTML('beforeend', chunk);\n            controller.enqueue(chunk);\n        }\n    }));\n\nawait new Response(stream).text();\n",[24,17021,17022,17041,17045,17055,17069,17083,17097,17102,17128,17136,17140,17144,17148],{"__ignoreMap":101},[105,17023,17024,17026,17028,17030,17032,17034,17036,17039],{"class":107,"line":108},[105,17025,568],{"class":111},[105,17027,6513],{"class":129},[105,17029,574],{"class":111},[105,17031,5428],{"class":111},[105,17033,6000],{"class":241},[105,17035,321],{"class":115},[105,17037,17038],{"class":248},"'\u002Fpage'",[105,17040,327],{"class":115},[105,17042,17043],{"class":107,"line":119},[105,17044,307],{"emptyLinePlaceholder":306},[105,17046,17047,17049,17051,17053],{"class":107,"line":133},[105,17048,568],{"class":111},[105,17050,15423],{"class":129},[105,17052,574],{"class":111},[105,17054,15428],{"class":115},[105,17056,17057,17059,17061,17063,17065,17067],{"class":107,"line":148},[105,17058,15433],{"class":115},[105,17060,15436],{"class":241},[105,17062,321],{"class":115},[105,17064,7430],{"class":111},[105,17066,15443],{"class":241},[105,17068,16069],{"class":115},[105,17070,17071,17073,17075,17077,17079,17081],{"class":107,"line":163},[105,17072,15433],{"class":115},[105,17074,15436],{"class":241},[105,17076,321],{"class":115},[105,17078,7430],{"class":111},[105,17080,15461],{"class":241},[105,17082,3935],{"class":115},[105,17084,17085,17087,17089,17091,17093,17095],{"class":107,"line":179},[105,17086,15472],{"class":241},[105,17088,321],{"class":115},[105,17090,15339],{"class":122},[105,17092,286],{"class":115},[105,17094,15481],{"class":122},[105,17096,1000],{"class":115},[105,17098,17099],{"class":107,"line":194},[105,17100,17101],{"class":630},"            \u002F\u002F 在 HTML 到达时逐块插入 DOM\n",[105,17103,17104,17107,17110,17112,17115,17117,17120,17122,17125],{"class":107,"line":209},[105,17105,17106],{"class":115},"            document.",[105,17108,17109],{"class":241},"getElementById",[105,17111,321],{"class":115},[105,17113,17114],{"class":248},"'app'",[105,17116,5642],{"class":115},[105,17118,17119],{"class":241},"insertAdjacentHTML",[105,17121,321],{"class":115},[105,17123,17124],{"class":248},"'beforeend'",[105,17126,17127],{"class":115},", chunk);\n",[105,17129,17130,17132,17134],{"class":107,"line":526},[105,17131,15488],{"class":115},[105,17133,15491],{"class":241},[105,17135,15540],{"class":115},[105,17137,17138],{"class":107,"line":531},[105,17139,12493],{"class":115},[105,17141,17142],{"class":107,"line":668},[105,17143,15509],{"class":115},[105,17145,17146],{"class":107,"line":673},[105,17147,307],{"emptyLinePlaceholder":306},[105,17149,17150,17152,17154,17156,17158,17160],{"class":107,"line":679},[105,17151,5401],{"class":111},[105,17153,3496],{"class":111},[105,17155,15746],{"class":241},[105,17157,15749],{"class":115},[105,17159,392],{"class":241},[105,17161,665],{"class":115},[18,17163,17164],{},"流式Markdown:",[96,17166,17168],{"className":3259,"code":17167,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Farticle.md');\nconst stream = response.body\n    .pipeThrough(new TextDecoderStream())\n    .pipeThrough(new TransformStream({\n        transform(chunk, controller) {\n            const html = markdownToHTML(chunk);\n            document.getElementById('content').insertAdjacentHTML('beforeend', html);\n            controller.enqueue(chunk);\n        }\n    }));\n",[24,17169,17170,17189,17199,17213,17227,17241,17255,17277,17285,17289],{"__ignoreMap":101},[105,17171,17172,17174,17176,17178,17180,17182,17184,17187],{"class":107,"line":108},[105,17173,568],{"class":111},[105,17175,6513],{"class":129},[105,17177,574],{"class":111},[105,17179,5428],{"class":111},[105,17181,6000],{"class":241},[105,17183,321],{"class":115},[105,17185,17186],{"class":248},"'\u002Farticle.md'",[105,17188,327],{"class":115},[105,17190,17191,17193,17195,17197],{"class":107,"line":119},[105,17192,568],{"class":111},[105,17194,15423],{"class":129},[105,17196,574],{"class":111},[105,17198,15428],{"class":115},[105,17200,17201,17203,17205,17207,17209,17211],{"class":107,"line":133},[105,17202,15433],{"class":115},[105,17204,15436],{"class":241},[105,17206,321],{"class":115},[105,17208,7430],{"class":111},[105,17210,15443],{"class":241},[105,17212,16069],{"class":115},[105,17214,17215,17217,17219,17221,17223,17225],{"class":107,"line":148},[105,17216,15433],{"class":115},[105,17218,15436],{"class":241},[105,17220,321],{"class":115},[105,17222,7430],{"class":111},[105,17224,15461],{"class":241},[105,17226,3935],{"class":115},[105,17228,17229,17231,17233,17235,17237,17239],{"class":107,"line":163},[105,17230,15472],{"class":241},[105,17232,321],{"class":115},[105,17234,15339],{"class":122},[105,17236,286],{"class":115},[105,17238,15481],{"class":122},[105,17240,1000],{"class":115},[105,17242,17243,17245,17248,17250,17253],{"class":107,"line":179},[105,17244,6307],{"class":111},[105,17246,17247],{"class":129}," html",[105,17249,574],{"class":111},[105,17251,17252],{"class":241}," markdownToHTML",[105,17254,15540],{"class":115},[105,17256,17257,17259,17261,17263,17266,17268,17270,17272,17274],{"class":107,"line":194},[105,17258,17106],{"class":115},[105,17260,17109],{"class":241},[105,17262,321],{"class":115},[105,17264,17265],{"class":248},"'content'",[105,17267,5642],{"class":115},[105,17269,17119],{"class":241},[105,17271,321],{"class":115},[105,17273,17124],{"class":248},[105,17275,17276],{"class":115},", html);\n",[105,17278,17279,17281,17283],{"class":107,"line":209},[105,17280,15488],{"class":115},[105,17282,15491],{"class":241},[105,17284,15540],{"class":115},[105,17286,17287],{"class":107,"line":526},[105,17288,12493],{"class":115},[105,17290,17291],{"class":107,"line":531},[105,17292,15509],{"class":115},[18,17294,17295],{},"AI 流式输出",[96,17297,17299],{"className":3259,"code":17298,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Fapi\u002Fchat', {\n    method: 'POST',\n    body: JSON.stringify({ prompt: 'Hello' })\n});\n\nconst reader = response.body\n    .pipeThrough(new TextDecoderStream())\n    .getReader();\n\nwhile (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    \u002F\u002F 逐字输出\n    document.getElementById('output').textContent += value;\n}\n",[24,17300,17301,17320,17330,17349,17353,17357,17367,17381,17389,17393,17404,17428,17438,17443,17462],{"__ignoreMap":101},[105,17302,17303,17305,17307,17309,17311,17313,17315,17318],{"class":107,"line":108},[105,17304,568],{"class":111},[105,17306,6513],{"class":129},[105,17308,574],{"class":111},[105,17310,5428],{"class":111},[105,17312,6000],{"class":241},[105,17314,321],{"class":115},[105,17316,17317],{"class":248},"'\u002Fapi\u002Fchat'",[105,17319,2817],{"class":115},[105,17321,17322,17325,17328],{"class":107,"line":119},[105,17323,17324],{"class":115},"    method: ",[105,17326,17327],{"class":248},"'POST'",[105,17329,2867],{"class":115},[105,17331,17332,17335,17337,17339,17341,17344,17346],{"class":107,"line":133},[105,17333,17334],{"class":115},"    body: ",[105,17336,5378],{"class":129},[105,17338,5381],{"class":115},[105,17340,5384],{"class":241},[105,17342,17343],{"class":115},"({ prompt: ",[105,17345,11036],{"class":248},[105,17347,17348],{"class":115}," })\n",[105,17350,17351],{"class":107,"line":148},[105,17352,3982],{"class":115},[105,17354,17355],{"class":107,"line":163},[105,17356,307],{"emptyLinePlaceholder":306},[105,17358,17359,17361,17363,17365],{"class":107,"line":179},[105,17360,568],{"class":111},[105,17362,16049],{"class":129},[105,17364,574],{"class":111},[105,17366,15428],{"class":115},[105,17368,17369,17371,17373,17375,17377,17379],{"class":107,"line":194},[105,17370,15433],{"class":115},[105,17372,15436],{"class":241},[105,17374,321],{"class":115},[105,17376,7430],{"class":111},[105,17378,15443],{"class":241},[105,17380,16069],{"class":115},[105,17382,17383,17385,17387],{"class":107,"line":209},[105,17384,15433],{"class":115},[105,17386,16076],{"class":241},[105,17388,665],{"class":115},[105,17390,17391],{"class":107,"line":526},[105,17392,307],{"emptyLinePlaceholder":306},[105,17394,17395,17398,17400,17402],{"class":107,"line":531},[105,17396,17397],{"class":111},"while",[105,17399,4379],{"class":115},[105,17401,9158],{"class":129},[105,17403,1000],{"class":115},[105,17405,17406,17408,17410,17412,17414,17416,17418,17420,17422,17424,17426],{"class":107,"line":668},[105,17407,7238],{"class":111},[105,17409,726],{"class":115},[105,17411,16115],{"class":129},[105,17413,286],{"class":115},[105,17415,8451],{"class":129},[105,17417,3973],{"class":115},[105,17419,245],{"class":111},[105,17421,5428],{"class":111},[105,17423,16128],{"class":115},[105,17425,16131],{"class":241},[105,17427,665],{"class":115},[105,17429,17430,17432,17434,17436],{"class":107,"line":673},[105,17431,7263],{"class":111},[105,17433,16140],{"class":115},[105,17435,16143],{"class":111},[105,17437,145],{"class":115},[105,17439,17440],{"class":107,"line":679},[105,17441,17442],{"class":630},"    \u002F\u002F 逐字输出\n",[105,17444,17445,17448,17450,17452,17455,17458,17460],{"class":107,"line":689},[105,17446,17447],{"class":115},"    document.",[105,17449,17109],{"class":241},[105,17451,321],{"class":115},[105,17453,17454],{"class":248},"'output'",[105,17456,17457],{"class":115},").textContent ",[105,17459,16153],{"class":111},[105,17461,12655],{"class":115},[105,17463,17464],{"class":107,"line":699},[105,17465,212],{"class":115},[1908,17467,17468],{"id":17468},"音视频处理",[18,17470,17471],{},"音频流式",[96,17473,17475],{"className":3259,"code":17474,"language":3261,"meta":101,"style":101},"const response = await fetch('\u002Faudio.mp3');\nconst audioContext = new AudioContext();\nconst source = audioContext.createMediaStreamSource(\n    new MediaStream([\u002F* ... *\u002F])\n);\n",[24,17476,17477,17496,17512,17529,17542],{"__ignoreMap":101},[105,17478,17479,17481,17483,17485,17487,17489,17491,17494],{"class":107,"line":108},[105,17480,568],{"class":111},[105,17482,6513],{"class":129},[105,17484,574],{"class":111},[105,17486,5428],{"class":111},[105,17488,6000],{"class":241},[105,17490,321],{"class":115},[105,17492,17493],{"class":248},"'\u002Faudio.mp3'",[105,17495,327],{"class":115},[105,17497,17498,17500,17503,17505,17507,17510],{"class":107,"line":119},[105,17499,568],{"class":111},[105,17501,17502],{"class":129}," audioContext",[105,17504,574],{"class":111},[105,17506,3496],{"class":111},[105,17508,17509],{"class":241}," AudioContext",[105,17511,665],{"class":115},[105,17513,17514,17516,17519,17521,17524,17527],{"class":107,"line":133},[105,17515,568],{"class":111},[105,17517,17518],{"class":129}," source",[105,17520,574],{"class":111},[105,17522,17523],{"class":115}," audioContext.",[105,17525,17526],{"class":241},"createMediaStreamSource",[105,17528,583],{"class":115},[105,17530,17531,17533,17536,17538,17540],{"class":107,"line":148},[105,17532,10282],{"class":111},[105,17534,17535],{"class":241}," MediaStream",[105,17537,6472],{"class":115},[105,17539,729],{"class":630},[105,17541,15791],{"class":115},[105,17543,17544],{"class":107,"line":163},[105,17545,327],{"class":115},[18,17547,17548],{},"视频流式录制：",[96,17550,17552],{"className":3259,"code":17551,"language":3261,"meta":101,"style":101},"const stream = await navigator.mediaDevices.getUserMedia({ video: true });\nconst recorder = new MediaRecorder(stream);\n\nconst writable = new WritableStream({\n    write(chunk) {\n        \u002F\u002F 把录制的数据块上传或保存\n    }\n});\n\nrecorder.ondataavailable = (e) => {\n    writable.getWriter().write(e.data);\n};\n",[24,17553,17554,17578,17595,17599,17613,17623,17628,17632,17636,17640,17660,17675],{"__ignoreMap":101},[105,17555,17556,17558,17560,17562,17564,17567,17570,17573,17575],{"class":107,"line":108},[105,17557,568],{"class":111},[105,17559,15423],{"class":129},[105,17561,574],{"class":111},[105,17563,5428],{"class":111},[105,17565,17566],{"class":115}," navigator.mediaDevices.",[105,17568,17569],{"class":241},"getUserMedia",[105,17571,17572],{"class":115},"({ video: ",[105,17574,9158],{"class":129},[105,17576,17577],{"class":115}," });\n",[105,17579,17580,17582,17585,17587,17589,17592],{"class":107,"line":119},[105,17581,568],{"class":111},[105,17583,17584],{"class":129}," recorder",[105,17586,574],{"class":111},[105,17588,3496],{"class":111},[105,17590,17591],{"class":241}," MediaRecorder",[105,17593,17594],{"class":115},"(stream);\n",[105,17596,17597],{"class":107,"line":133},[105,17598,307],{"emptyLinePlaceholder":306},[105,17600,17601,17603,17605,17607,17609,17611],{"class":107,"line":148},[105,17602,568],{"class":111},[105,17604,15320],{"class":129},[105,17606,574],{"class":111},[105,17608,3496],{"class":111},[105,17610,15327],{"class":241},[105,17612,3935],{"class":115},[105,17614,17615,17617,17619,17621],{"class":107,"line":163},[105,17616,15334],{"class":241},[105,17618,321],{"class":115},[105,17620,15339],{"class":122},[105,17622,1000],{"class":115},[105,17624,17625],{"class":107,"line":179},[105,17626,17627],{"class":630},"        \u002F\u002F 把录制的数据块上传或保存\n",[105,17629,17630],{"class":107,"line":194},[105,17631,5161],{"class":115},[105,17633,17634],{"class":107,"line":209},[105,17635,3982],{"class":115},[105,17637,17638],{"class":107,"line":526},[105,17639,307],{"emptyLinePlaceholder":306},[105,17641,17642,17645,17648,17650,17652,17654,17656,17658],{"class":107,"line":531},[105,17643,17644],{"class":115},"recorder.",[105,17646,17647],{"class":241},"ondataavailable",[105,17649,574],{"class":111},[105,17651,4379],{"class":115},[105,17653,3315],{"class":122},[105,17655,2854],{"class":115},[105,17657,723],{"class":111},[105,17659,116],{"class":115},[105,17661,17662,17665,17668,17670,17672],{"class":107,"line":668},[105,17663,17664],{"class":115},"    writable.",[105,17666,17667],{"class":241},"getWriter",[105,17669,5763],{"class":115},[105,17671,15626],{"class":241},[105,17673,17674],{"class":115},"(e.data);\n",[105,17676,17677],{"class":107,"line":673},[105,17678,4459],{"class":115},[18,17680,17681],{},"vueuse提供了两个处理web worker的组合式函数：useWebWorker和useWebWorkerFn，分别对应托管现成Worker脚本和直接包装函数体两种场景。",[96,17683,17685],{"className":3259,"code":17684,"language":3261,"meta":101,"style":101},"import { useWebWorkerFn } from '@vueuse\u002Fcore'\n\nconst { workerFn } = useWebWorkerFn((numbers) => {\n  \u002F\u002F 这里是耗时的重计算\n  return numbers.sort((a, b) => a - b)\n})\n\n\u002F\u002F 调用 workerFn，返回 Promise\nconst sorted = await workerFn(hugeArray)\n\n",[24,17686,17687,17699,17703,17730,17735,17765,17769,17773,17778],{"__ignoreMap":101},[105,17688,17689,17691,17694,17696],{"class":107,"line":108},[105,17690,3903],{"class":111},[105,17692,17693],{"class":115}," { useWebWorkerFn } ",[105,17695,3909],{"class":111},[105,17697,17698],{"class":248}," '@vueuse\u002Fcore'\n",[105,17700,17701],{"class":107,"line":119},[105,17702,307],{"emptyLinePlaceholder":306},[105,17704,17705,17707,17709,17712,17714,17716,17719,17721,17724,17726,17728],{"class":107,"line":133},[105,17706,568],{"class":111},[105,17708,726],{"class":115},[105,17710,17711],{"class":129},"workerFn",[105,17713,3973],{"class":115},[105,17715,245],{"class":111},[105,17717,17718],{"class":241}," useWebWorkerFn",[105,17720,7298],{"class":115},[105,17722,17723],{"class":122},"numbers",[105,17725,2854],{"class":115},[105,17727,723],{"class":111},[105,17729,116],{"class":115},[105,17731,17732],{"class":107,"line":148},[105,17733,17734],{"class":630},"  \u002F\u002F 这里是耗时的重计算\n",[105,17736,17737,17739,17742,17745,17747,17749,17751,17754,17756,17758,17760,17762],{"class":107,"line":163},[105,17738,4144],{"class":111},[105,17740,17741],{"class":115}," numbers.",[105,17743,17744],{"class":241},"sort",[105,17746,7298],{"class":115},[105,17748,62],{"class":122},[105,17750,286],{"class":115},[105,17752,17753],{"class":122},"b",[105,17755,2854],{"class":115},[105,17757,723],{"class":111},[105,17759,9874],{"class":115},[105,17761,4159],{"class":111},[105,17763,17764],{"class":115}," b)\n",[105,17766,17767],{"class":107,"line":179},[105,17768,2906],{"class":115},[105,17770,17771],{"class":107,"line":194},[105,17772,307],{"emptyLinePlaceholder":306},[105,17774,17775],{"class":107,"line":209},[105,17776,17777],{"class":630},"\u002F\u002F 调用 workerFn，返回 Promise\n",[105,17779,17780,17782,17785,17787,17789,17792],{"class":107,"line":526},[105,17781,568],{"class":111},[105,17783,17784],{"class":129}," sorted",[105,17786,574],{"class":111},[105,17788,5428],{"class":111},[105,17790,17791],{"class":241}," workerFn",[105,17793,17794],{"class":115},"(hugeArray)\n",[14,17796,10033],{"id":10033},[18,17798,17799,17800,17803],{},"闭包主要是通过上下文对象和作用域链实现的，",[21,17801,17802],{},"内部函数引用了外部函数的变量的时候","，v8会把这些变量从栈上搬到堆上的上下文对象中，这样内部的函数可以通过作用域链来访问它们。正常情况下函数执行的时候局部变量会分配在调用栈上，函数一旦返回，变量就会随着栈帧一起销毁。但是闭包会打破这个规则，v8发现一个内部函数引用了外部函数变量，那么就让这个变量在外部函数返回后仍需存活，v8会把这些被引用的变量以及可能其他被引用的变量从栈提升到堆上，封装成一个上下文对象。",[18,17805,17806,17807,17810],{},"可以把上下文理解为一个特殊的环境记录，用来保存那些需要活过外部函数生命周期的变量。每个函数对象内部都有一个隐藏的 ",[24,17808,17809],{},"[[Environment]]","槽，称为Scope内部属性，当函数被创建的时候，这个属性会指向它定义时候所处的词法环境，也就是外层context对象。闭包执行并需要访问外部的变量，v8就会沿着这条链去找。",[18,17812,17813,17814,17817,17818,17821],{},"v8不会盲目保存所有变量，而是依靠惰性解析，v8首次解析代码的时候遇到",[21,17815,17816],{},"函数声明","会跳过其内部代码，",[21,17819,17820],{},"只生成顶层的AST和字节码","，然后进行静态分析，只把内部函数实际引用的变量提升到上下文对象，而不是外部函数所有的局部变量，如果某个变量没有被闭包引用，他依然留在栈上，执行完销毁。",[18,17823,17824],{},"结合之前的js核心运行机制可以推断出：",[18,17826,17827,17828,17831,17832,17835],{},"词法环境和变量环境是抽象概念，v8层面，它们会被具象化为context对象，变量提升到context对象，",[21,17829,17830],{},"发生在外层函数执行的时候","，而不是内层函数查找的时候。V8 在编译外层函数时，就已经通过",[21,17833,17834],{},"静态分析","知道了 哪些变量会被内层函数引用，这些变量应该放在上下文的哪个槽位，所以外层函数一执行，V8 就直接在堆上创建 Context，把这些变量放进去。内层函数创建时，直接拿到这个 Context 的引用。",[18,17837,17838],{},"静态分析和逃逸分析",[18,17840,17841],{},"静态分析和逃逸分析是包含关系：",[96,17843,17846],{"className":17844,"code":17845,"language":392},[390],"编译优化\n  ├── 静态分析（Static Analysis）        ← 大类：编译期分析代码性质\n  │     ├── 逃逸分析（Escape Analysis）   ← 子类：分析变量是否“逃逸”\n  │     ├── 死代码消除\n  │     ├── 常量折叠\n  │     └── 内联分析\n  └── 动态分析（Dynamic Analysis）        ← 运行时分析\n",[24,17847,17845],{"__ignoreMap":101},[18,17849,17850],{},"静态分析是“在编译期分析代码性质”的总称，逃逸分析是其中专门用来判断“变量是否会逃出当前作用域”的技术。逃逸本意是指一个变量或者对象的生命周期是否会超出它被创建时的作用域，变量只在当前函数内使用，函数返回即销毁就叫做不逃逸，反之就是逃逸到堆。变量被传递到其他线程就叫做逃逸到其他线程。",[14,17852,17854],{"id":17853},"gc机制","GC机制",[18,17856,17857],{},"js的垃圾回收是引擎自动管理内存的机制，负责识别并回收不再被使用的内存，防止内存泄漏，v8的gc策略是分代回收+增量标记+并发清理的组合策略。",[18,17859,17860,17861,17864],{},"gc不关心这个变量叫什么，只关心这个对象还能不能访问到，从根对象出发，沿着引用链遍历，",[21,17862,17863],{},"能达到的对象就是可达的","，不能达到的就是不可达，会被回收掉。",[18,17866,17867],{},"根对象包括了window\u002FglobalThis，调用栈上的变量（当前执行函数的局部变量、参数）。闭包捕获的变量、DOM引用。",[18,17869,17870],{},"v8把堆内存分为了新生代和老生代，针对不同生命周期采用不同策略，新生代比较小可能就1-8MB，老生代比较大，可能几百MB甚至GB，新生代的生命周期比较短，老生代周期比较长。",[18,17872,17873,17874,9383,17877,91],{},"新生代使用Scavenge复制算法进行回收，老生代使用mark-sweep+mark-compact。把新生代分为两个半区：",[21,17875,17876],{},"From 空间",[21,17878,17879],{},"To 空间",[96,17881,17884],{"className":17882,"code":17883,"language":392},[390],"From 空间（使用中）    To 空间（空闲）\n┌─────────────┐      ┌─────────────┐\n│ 活对象       │      │             │\n│ 死对象       │      │             │\n└─────────────┘      └─────────────┘\n",[24,17885,17883],{"__ignoreMap":101},[18,17887,17888,17889,91],{},"从根出发，标记from空间中的活对象，把活对象复制到To空间，清空From空间，交换From和To的对象，如果对象经过多次 Scavenge 仍然存活，会被",[21,17890,17891],{},"晋升到老生代",[18,17893,17894,17895,17898,17899,17902,17906,17910],{},"分代思想是个很成熟的模型，就像CLR也是采用的三代模型的gc回收。第一代充当缓冲区，短期存活的对象有机会再0代回收后被快速清理，而不必升到2代，clr有大型对象堆LOH，任何大于85KB的对象会直接分配到LOH上，并被标记为2代， 它",[21,17896,17897],{},"只在完全 GC（第 2 代回收）时才会被清理","，且",[21,17900,17901],{},"默认不会被压缩",[62,17903],{"href":17904,"rel":17905},"https:\u002F\u002Fgithub.com\u002Frambhawan\u002FComputer-Compiler-Lang\u002Fblob\u002F825822d3e676b2dc5a93d649ae8a4555cb606b93\u002FPro%20.Net%20Performance.pdf?_x_output_type_b6db407c4_=embedded_pdf#53#19",[66],[62,17907],{"href":17908,"rel":17909},"https:\u002F\u002Fm.yisu.com\u002Fzixun\u002F102253.html",[66],"。这是因为移动大对象内存的代价很高，V8 没有专门的 LOH，所有对象都在新生代或老生代中处理。",[14,17912,17914],{"id":17913},"ts的类型","TS的类型",[18,17916,17917],{},"在ts之中，函数会被看做值，类型是函数类型(x: number) => string。方法是对象里的函数属性，类型也是函数类型。类方法是类原型上的函数，类型也是函数类型。因此可以推理得出：方法的泛型 = 函数类型的泛型，不是所有方法都有独立的类型系统。",[18,17919,17920,17921,17924],{},"泛型参数 ",[24,17922,17923],{},"\u003CT>","可以写在函数签名上，而不是写在类型别名\u002F接口\u002F类上。我们知道泛型有两种写法：",[96,17926,17928],{"className":3259,"code":17927,"language":3261,"meta":101,"style":101},"type Box\u003CT> = {\n    value: T;\n};\n\u002F\u002F   ^^^ T 是 Box 的类型参数\nconst b: Box\u003Cnumber> = { value: 1 };   \u002F\u002F 用类型时确定 T\n\nfunction identity\u003CT>(x: T): T {\n    \u002F\u002F           ^^^ T 是函数的类型参数\n    return x;\n}\nidentity\u003Cnumber>(1);   \u002F\u002F 调用函数时确定 T\n",[24,17929,17930,17947,17958,17962,17967,17997,18001,18028,18033,18040,18044],{"__ignoreMap":101},[105,17931,17932,17934,17937,17939,17941,17943,17945],{"class":107,"line":108},[105,17933,7243],{"class":111},[105,17935,17936],{"class":241}," Box",[105,17938,234],{"class":115},[105,17940,5008],{"class":241},[105,17942,12206],{"class":115},[105,17944,245],{"class":111},[105,17946,116],{"class":115},[105,17948,17949,17952,17954,17956],{"class":107,"line":119},[105,17950,17951],{"class":122},"    value",[105,17953,5017],{"class":111},[105,17955,5075],{"class":241},[105,17957,145],{"class":115},[105,17959,17960],{"class":107,"line":133},[105,17961,4459],{"class":115},[105,17963,17964],{"class":107,"line":148},[105,17965,17966],{"class":630},"\u002F\u002F   ^^^ T 是 Box 的类型参数\n",[105,17968,17969,17971,17973,17975,17977,17979,17982,17984,17986,17989,17991,17994],{"class":107,"line":163},[105,17970,568],{"class":111},[105,17972,9775],{"class":129},[105,17974,5017],{"class":111},[105,17976,17936],{"class":241},[105,17978,234],{"class":115},[105,17980,17981],{"class":129},"number",[105,17983,12206],{"class":115},[105,17985,245],{"class":111},[105,17987,17988],{"class":115}," { value: ",[105,17990,861],{"class":129},[105,17992,17993],{"class":115}," };   ",[105,17995,17996],{"class":630},"\u002F\u002F 用类型时确定 T\n",[105,17998,17999],{"class":107,"line":179},[105,18000,307],{"emptyLinePlaceholder":306},[105,18002,18003,18005,18008,18010,18012,18014,18016,18018,18020,18022,18024,18026],{"class":107,"line":194},[105,18004,8131],{"class":111},[105,18006,18007],{"class":241}," identity",[105,18009,234],{"class":115},[105,18011,5008],{"class":241},[105,18013,5011],{"class":115},[105,18015,9846],{"class":122},[105,18017,5017],{"class":111},[105,18019,5075],{"class":241},[105,18021,11956],{"class":115},[105,18023,5017],{"class":111},[105,18025,5075],{"class":241},[105,18027,116],{"class":115},[105,18029,18030],{"class":107,"line":209},[105,18031,18032],{"class":630},"    \u002F\u002F           ^^^ T 是函数的类型参数\n",[105,18034,18035,18037],{"class":107,"line":526},[105,18036,10680],{"class":111},[105,18038,18039],{"class":115}," x;\n",[105,18041,18042],{"class":107,"line":531},[105,18043,212],{"class":115},[105,18045,18046,18049,18051,18053,18055,18057,18060],{"class":107,"line":668},[105,18047,18048],{"class":241},"identity",[105,18050,234],{"class":115},[105,18052,17981],{"class":129},[105,18054,5011],{"class":115},[105,18056,861],{"class":129},[105,18058,18059],{"class":115},");   ",[105,18061,18062],{"class":630},"\u002F\u002F 调用函数时确定 T\n",[18,18064,18065],{},"两种泛型位置不同，分别叫做类型级泛型和调用级泛型。",[14,18067,18069],{"id":18068},"ts的基本模式","Ts的基本模式",[18,18071,18072],{},"ts有个非常常见的模式，使用interface来描述实例，一份interface描述构造函数并且包含静态方法，然后class同时满足。",[18,18074,18075],{},"我们使用的Array Function Promise全都是按照这个模式来编写的：",[96,18077,18079],{"className":3259,"code":18078,"language":3261,"meta":101,"style":101},"\u002F\u002F 1. 实例接口\ninterface User {\n    name: string;\n    age: number;\n    greet(): string;\n}\n\n\u002F\u002F 2. 构造函数接口（含静态方法）\ninterface UserConstructor {\n    new (name: string, age: number): User;   \u002F\u002F 构造签名\n    create(name: string, age: number): User; \u002F\u002F 静态方法\n    readonly MAX_AGE: number;                 \u002F\u002F 静态属性\n}\n\n\u002F\u002F 3. 类实现两者\nclass UserImpl implements User {\n    static MAX_AGE = 150;\n    \n    constructor(public name: string, public age: number) {}\n    \n    greet() {\n        return `Hi, I'm ${this.name}`;\n    }\n    \n    static create(name: string, age: number): User {\n        return new UserImpl(name, age);\n    }\n}\n\n\u002F\u002F 4. 用构造函数接口约束类本身\nconst UserClass: UserConstructor = UserImpl;   \u002F\u002F ✅\n",[24,18080,18081,18086,18095,18106,18117,18130,18134,18138,18143,18152,18185,18218,18235,18239,18243,18248,18262,18275,18280,18310,18314,18320,18337,18341,18345,18376,18387,18391,18395,18399,18404],{"__ignoreMap":101},[105,18082,18083],{"class":107,"line":108},[105,18084,18085],{"class":630},"\u002F\u002F 1. 实例接口\n",[105,18087,18088,18090,18093],{"class":107,"line":119},[105,18089,3519],{"class":111},[105,18091,18092],{"class":241}," User",[105,18094,116],{"class":115},[105,18096,18097,18100,18102,18104],{"class":107,"line":133},[105,18098,18099],{"class":122},"    name",[105,18101,5017],{"class":111},[105,18103,5020],{"class":129},[105,18105,145],{"class":115},[105,18107,18108,18111,18113,18115],{"class":107,"line":148},[105,18109,18110],{"class":122},"    age",[105,18112,5017],{"class":111},[105,18114,14902],{"class":129},[105,18116,145],{"class":115},[105,18118,18119,18122,18124,18126,18128],{"class":107,"line":163},[105,18120,18121],{"class":241},"    greet",[105,18123,15129],{"class":115},[105,18125,5017],{"class":111},[105,18127,5020],{"class":129},[105,18129,145],{"class":115},[105,18131,18132],{"class":107,"line":179},[105,18133,212],{"class":115},[105,18135,18136],{"class":107,"line":194},[105,18137,307],{"emptyLinePlaceholder":306},[105,18139,18140],{"class":107,"line":209},[105,18141,18142],{"class":630},"\u002F\u002F 2. 构造函数接口（含静态方法）\n",[105,18144,18145,18147,18150],{"class":107,"line":526},[105,18146,3519],{"class":111},[105,18148,18149],{"class":241}," UserConstructor",[105,18151,116],{"class":115},[105,18153,18154,18156,18158,18160,18162,18164,18166,18169,18171,18173,18175,18177,18179,18182],{"class":107,"line":531},[105,18155,10282],{"class":111},[105,18157,4379],{"class":115},[105,18159,5014],{"class":122},[105,18161,5017],{"class":111},[105,18163,5020],{"class":129},[105,18165,286],{"class":115},[105,18167,18168],{"class":122},"age",[105,18170,5017],{"class":111},[105,18172,14902],{"class":129},[105,18174,11956],{"class":115},[105,18176,5017],{"class":111},[105,18178,18092],{"class":241},[105,18180,18181],{"class":115},";   ",[105,18183,18184],{"class":630},"\u002F\u002F 构造签名\n",[105,18186,18187,18190,18192,18194,18196,18198,18200,18202,18204,18206,18208,18210,18212,18215],{"class":107,"line":668},[105,18188,18189],{"class":241},"    create",[105,18191,321],{"class":115},[105,18193,5014],{"class":122},[105,18195,5017],{"class":111},[105,18197,5020],{"class":129},[105,18199,286],{"class":115},[105,18201,18168],{"class":122},[105,18203,5017],{"class":111},[105,18205,14902],{"class":129},[105,18207,11956],{"class":115},[105,18209,5017],{"class":111},[105,18211,18092],{"class":241},[105,18213,18214],{"class":115},"; ",[105,18216,18217],{"class":630},"\u002F\u002F 静态方法\n",[105,18219,18220,18222,18225,18227,18229,18232],{"class":107,"line":673},[105,18221,10330],{"class":111},[105,18223,18224],{"class":122}," MAX_AGE",[105,18226,5017],{"class":111},[105,18228,14902],{"class":129},[105,18230,18231],{"class":115},";                 ",[105,18233,18234],{"class":630},"\u002F\u002F 静态属性\n",[105,18236,18237],{"class":107,"line":679},[105,18238,212],{"class":115},[105,18240,18241],{"class":107,"line":689},[105,18242,307],{"emptyLinePlaceholder":306},[105,18244,18245],{"class":107,"line":699},[105,18246,18247],{"class":630},"\u002F\u002F 3. 类实现两者\n",[105,18249,18250,18252,18255,18258,18260],{"class":107,"line":704},[105,18251,12196],{"class":111},[105,18253,18254],{"class":241}," UserImpl",[105,18256,18257],{"class":111}," implements",[105,18259,18092],{"class":241},[105,18261,116],{"class":115},[105,18263,18264,18266,18268,18270,18273],{"class":107,"line":710},[105,18265,13680],{"class":111},[105,18267,18224],{"class":122},[105,18269,574],{"class":111},[105,18271,18272],{"class":129}," 150",[105,18274,145],{"class":115},[105,18276,18277],{"class":107,"line":1136},[105,18278,18279],{"class":115},"    \n",[105,18281,18282,18284,18286,18289,18292,18294,18296,18298,18300,18303,18305,18307],{"class":107,"line":1149},[105,18283,12312],{"class":111},[105,18285,321],{"class":115},[105,18287,18288],{"class":111},"public",[105,18290,18291],{"class":122}," name",[105,18293,5017],{"class":111},[105,18295,5020],{"class":129},[105,18297,286],{"class":115},[105,18299,18288],{"class":111},[105,18301,18302],{"class":122}," age",[105,18304,5017],{"class":111},[105,18306,14902],{"class":129},[105,18308,18309],{"class":115},") {}\n",[105,18311,18312],{"class":107,"line":3855},[105,18313,18279],{"class":115},[105,18315,18316,18318],{"class":107,"line":3865},[105,18317,18121],{"class":241},[105,18319,9247],{"class":115},[105,18321,18322,18324,18327,18329,18331,18333,18335],{"class":107,"line":3875},[105,18323,11254],{"class":111},[105,18325,18326],{"class":248}," `Hi, I'm ${",[105,18328,2888],{"class":129},[105,18330,5381],{"class":248},[105,18332,5014],{"class":115},[105,18334,6870],{"class":248},[105,18336,145],{"class":115},[105,18338,18339],{"class":107,"line":3884},[105,18340,5161],{"class":115},[105,18342,18343],{"class":107,"line":3889},[105,18344,18279],{"class":115},[105,18346,18347,18349,18352,18354,18356,18358,18360,18362,18364,18366,18368,18370,18372,18374],{"class":107,"line":3900},[105,18348,13680],{"class":111},[105,18350,18351],{"class":241}," create",[105,18353,321],{"class":115},[105,18355,5014],{"class":122},[105,18357,5017],{"class":111},[105,18359,5020],{"class":129},[105,18361,286],{"class":115},[105,18363,18168],{"class":122},[105,18365,5017],{"class":111},[105,18367,14902],{"class":129},[105,18369,11956],{"class":115},[105,18371,5017],{"class":111},[105,18373,18092],{"class":241},[105,18375,116],{"class":115},[105,18377,18378,18380,18382,18384],{"class":107,"line":3917},[105,18379,11254],{"class":111},[105,18381,3496],{"class":111},[105,18383,18254],{"class":241},[105,18385,18386],{"class":115},"(name, age);\n",[105,18388,18389],{"class":107,"line":3922},[105,18390,5161],{"class":115},[105,18392,18393],{"class":107,"line":3938},[105,18394,212],{"class":115},[105,18396,18397],{"class":107,"line":3954},[105,18398,307],{"emptyLinePlaceholder":306},[105,18400,18401],{"class":107,"line":3965},[105,18402,18403],{"class":630},"\u002F\u002F 4. 用构造函数接口约束类本身\n",[105,18405,18406,18408,18411,18413,18415,18417,18420],{"class":107,"line":3979},[105,18407,568],{"class":111},[105,18409,18410],{"class":129}," UserClass",[105,18412,5017],{"class":111},[105,18414,18149],{"class":241},[105,18416,574],{"class":111},[105,18418,18419],{"class":115}," UserImpl;   ",[105,18421,18422],{"class":630},"\u002F\u002F ✅\n",[18,18424,18425],{},"这样的方式可以让我们解耦接口和实现，还支持依赖注入、工厂函数和类统一。以及还忘记提了，可以仿照Promise一样，使用declare var Promise: PromiseConstructor",[18,18427,18428],{},"这是lib.es2015.promise.d.ts里的做法，用一个var声明把值（构造函数）和类型（实例）分开，这个是ts声明库类型时的经典模式。",[96,18430,18432],{"className":3259,"code":18431,"language":3261,"meta":101,"style":101},"\u002F\u002F ===== 1. 实例接口 =====\ninterface Promise\u003CT> {\n    then\u003CTResult1 = T, TResult2 = never>(\n        onfulfilled?: ((value: T) => TResult1 | PromiseLike\u003CTResult1>) | null,\n        onrejected?: ((reason: any) => TResult2 | PromiseLike\u003CTResult2>) | null\n    ): Promise\u003CTResult1 | TResult2>;\n    catch\u003CTResult = never>(\n        onrejected?: ((reason: any) => TResult | PromiseLike\u003CTResult>) | null\n    ): Promise\u003CT | TResult>;\n    finally(onfinally?: (() => void) | null): Promise\u003CT>;\n    readonly [Symbol.toStringTag]: string;\n}\n\n\u002F\u002F ===== 2. 构造函数接口 =====\ninterface PromiseConstructor {\n    readonly prototype: Promise\u003Cany>;\n    \n    \u002F\u002F new 签名\n    new \u003CT>(\n        executor: (\n            resolve: (value: T | PromiseLike\u003CT>) => void,\n            reject: (reason?: any) => void\n        ) => void\n    ): Promise\u003CT>;\n    \n    \u002F\u002F 静态方法\n    resolve(): Promise\u003Cvoid>;\n    resolve\u003CT>(value: T | PromiseLike\u003CT>): Promise\u003CT>;\n    reject\u003CT = never>(reason?: any): Promise\u003CT>;\n    all\u003CT extends readonly unknown[] | []>(values: T): Promise\u003C...>;\n    race\u003CT>(values: Iterable\u003CT | PromiseLike\u003CT>>): Promise\u003CT>;\n    allSettled\u003CT>(values: Iterable\u003CT | PromiseLike\u003CT>>): Promise\u003C...>;\n    any\u003CT>(values: Iterable\u003CT | PromiseLike\u003CT>>): Promise\u003CT>;\n}\n\n\u002F\u002F ===== 3. 用 declare var 声明\"值\" =====\ndeclare var Promise: PromiseConstructor;\n",[24,18433,18434,18439,18451,18473,18511,18546,18564,18578,18612,18630,18667,18680,18684,18688,18693,18702,18719,18723,18728,18738,18746,18776,18796,18804,18818,18822,18826,18843,18879,18911,18951,18994,19031,19072,19076,19080,19085],{"__ignoreMap":101},[105,18435,18436],{"class":107,"line":108},[105,18437,18438],{"class":630},"\u002F\u002F ===== 1. 实例接口 =====\n",[105,18440,18441,18443,18445,18447,18449],{"class":107,"line":119},[105,18442,3519],{"class":111},[105,18444,5780],{"class":241},[105,18446,234],{"class":115},[105,18448,5008],{"class":241},[105,18450,11862],{"class":115},[105,18452,18453,18455,18457,18459,18461,18463,18465,18467,18469,18471],{"class":107,"line":133},[105,18454,11533],{"class":241},[105,18456,234],{"class":115},[105,18458,11589],{"class":241},[105,18460,574],{"class":111},[105,18462,5075],{"class":241},[105,18464,286],{"class":115},[105,18466,11631],{"class":241},[105,18468,574],{"class":111},[105,18470,11550],{"class":129},[105,18472,12010],{"class":115},[105,18474,18475,18478,18480,18482,18484,18486,18488,18490,18492,18494,18496,18499,18501,18503,18505,18507,18509],{"class":107,"line":148},[105,18476,18477],{"class":122},"        onfulfilled",[105,18479,11562],{"class":111},[105,18481,11565],{"class":115},[105,18483,8451],{"class":122},[105,18485,5017],{"class":111},[105,18487,5075],{"class":241},[105,18489,2854],{"class":115},[105,18491,723],{"class":111},[105,18493,12031],{"class":241},[105,18495,5041],{"class":111},[105,18497,18498],{"class":241}," PromiseLike",[105,18500,234],{"class":115},[105,18502,11589],{"class":241},[105,18504,12042],{"class":115},[105,18506,11581],{"class":111},[105,18508,5044],{"class":129},[105,18510,2867],{"class":115},[105,18512,18513,18516,18518,18520,18522,18524,18526,18528,18530,18532,18534,18536,18538,18540,18542,18544],{"class":107,"line":163},[105,18514,18515],{"class":122},"        onrejected",[105,18517,11562],{"class":111},[105,18519,11565],{"class":115},[105,18521,11611],{"class":122},[105,18523,5017],{"class":111},[105,18525,10435],{"class":129},[105,18527,2854],{"class":115},[105,18529,723],{"class":111},[105,18531,11654],{"class":241},[105,18533,5041],{"class":111},[105,18535,18498],{"class":241},[105,18537,234],{"class":115},[105,18539,11631],{"class":241},[105,18541,12042],{"class":115},[105,18543,11581],{"class":111},[105,18545,11640],{"class":129},[105,18547,18548,18550,18552,18554,18556,18558,18560,18562],{"class":107,"line":179},[105,18549,12087],{"class":115},[105,18551,5017],{"class":111},[105,18553,5780],{"class":241},[105,18555,234],{"class":115},[105,18557,11589],{"class":241},[105,18559,5041],{"class":111},[105,18561,11654],{"class":241},[105,18563,12102],{"class":115},[105,18565,18566,18568,18570,18572,18574,18576],{"class":107,"line":194},[105,18567,12111],{"class":241},[105,18569,234],{"class":115},[105,18571,12116],{"class":241},[105,18573,574],{"class":111},[105,18575,11550],{"class":129},[105,18577,12010],{"class":115},[105,18579,18580,18582,18584,18586,18588,18590,18592,18594,18596,18598,18600,18602,18604,18606,18608,18610],{"class":107,"line":209},[105,18581,18515],{"class":122},[105,18583,11562],{"class":111},[105,18585,11565],{"class":115},[105,18587,11611],{"class":122},[105,18589,5017],{"class":111},[105,18591,10435],{"class":129},[105,18593,2854],{"class":115},[105,18595,723],{"class":111},[105,18597,12143],{"class":241},[105,18599,5041],{"class":111},[105,18601,18498],{"class":241},[105,18603,234],{"class":115},[105,18605,12116],{"class":241},[105,18607,12042],{"class":115},[105,18609,11581],{"class":111},[105,18611,11640],{"class":129},[105,18613,18614,18616,18618,18620,18622,18624,18626,18628],{"class":107,"line":526},[105,18615,12087],{"class":115},[105,18617,5017],{"class":111},[105,18619,5780],{"class":241},[105,18621,234],{"class":115},[105,18623,5008],{"class":241},[105,18625,5041],{"class":111},[105,18627,12143],{"class":241},[105,18629,12102],{"class":115},[105,18631,18632,18635,18637,18640,18642,18645,18647,18649,18651,18653,18655,18657,18659,18661,18663,18665],{"class":107,"line":531},[105,18633,18634],{"class":241},"    finally",[105,18636,321],{"class":115},[105,18638,18639],{"class":122},"onfinally",[105,18641,11562],{"class":111},[105,18643,18644],{"class":115}," (() ",[105,18646,723],{"class":111},[105,18648,5082],{"class":129},[105,18650,2854],{"class":115},[105,18652,11581],{"class":111},[105,18654,5044],{"class":129},[105,18656,11956],{"class":115},[105,18658,5017],{"class":111},[105,18660,5780],{"class":241},[105,18662,234],{"class":115},[105,18664,5008],{"class":241},[105,18666,12102],{"class":115},[105,18668,18669,18671,18674,18676,18678],{"class":107,"line":668},[105,18670,10330],{"class":111},[105,18672,18673],{"class":115}," [Symbol.toStringTag]",[105,18675,5017],{"class":111},[105,18677,5020],{"class":129},[105,18679,145],{"class":115},[105,18681,18682],{"class":107,"line":673},[105,18683,212],{"class":115},[105,18685,18686],{"class":107,"line":679},[105,18687,307],{"emptyLinePlaceholder":306},[105,18689,18690],{"class":107,"line":689},[105,18691,18692],{"class":630},"\u002F\u002F ===== 2. 构造函数接口 =====\n",[105,18694,18695,18697,18700],{"class":107,"line":699},[105,18696,3519],{"class":111},[105,18698,18699],{"class":241}," PromiseConstructor",[105,18701,116],{"class":115},[105,18703,18704,18706,18708,18710,18712,18714,18717],{"class":107,"line":704},[105,18705,10330],{"class":111},[105,18707,10333],{"class":122},[105,18709,5017],{"class":111},[105,18711,5780],{"class":241},[105,18713,234],{"class":115},[105,18715,18716],{"class":129},"any",[105,18718,12102],{"class":115},[105,18720,18721],{"class":107,"line":710},[105,18722,18279],{"class":115},[105,18724,18725],{"class":107,"line":1136},[105,18726,18727],{"class":630},"    \u002F\u002F new 签名\n",[105,18729,18730,18732,18734,18736],{"class":107,"line":1149},[105,18731,10282],{"class":111},[105,18733,14918],{"class":115},[105,18735,5008],{"class":241},[105,18737,12010],{"class":115},[105,18739,18740,18742,18744],{"class":107,"line":3855},[105,18741,12319],{"class":241},[105,18743,5017],{"class":111},[105,18745,12324],{"class":115},[105,18747,18748,18750,18752,18754,18756,18758,18760,18762,18764,18766,18768,18770,18772,18774],{"class":107,"line":3865},[105,18749,12329],{"class":241},[105,18751,5017],{"class":111},[105,18753,4379],{"class":115},[105,18755,8451],{"class":122},[105,18757,5017],{"class":111},[105,18759,5075],{"class":241},[105,18761,5041],{"class":111},[105,18763,18498],{"class":241},[105,18765,234],{"class":115},[105,18767,5008],{"class":241},[105,18769,12042],{"class":115},[105,18771,723],{"class":111},[105,18773,5082],{"class":129},[105,18775,2867],{"class":115},[105,18777,18778,18780,18782,18784,18786,18788,18790,18792,18794],{"class":107,"line":3875},[105,18779,12352],{"class":241},[105,18781,5017],{"class":111},[105,18783,4379],{"class":115},[105,18785,11611],{"class":122},[105,18787,11562],{"class":111},[105,18789,10435],{"class":129},[105,18791,2854],{"class":115},[105,18793,723],{"class":111},[105,18795,12369],{"class":129},[105,18797,18798,18800,18802],{"class":107,"line":3884},[105,18799,12374],{"class":115},[105,18801,723],{"class":111},[105,18803,12369],{"class":129},[105,18805,18806,18808,18810,18812,18814,18816],{"class":107,"line":3889},[105,18807,12087],{"class":115},[105,18809,5017],{"class":111},[105,18811,5780],{"class":241},[105,18813,234],{"class":115},[105,18815,5008],{"class":241},[105,18817,12102],{"class":115},[105,18819,18820],{"class":107,"line":3900},[105,18821,18279],{"class":115},[105,18823,18824],{"class":107,"line":3917},[105,18825,15222],{"class":630},[105,18827,18828,18830,18832,18834,18836,18838,18841],{"class":107,"line":3922},[105,18829,11945],{"class":241},[105,18831,15129],{"class":115},[105,18833,5017],{"class":111},[105,18835,5780],{"class":241},[105,18837,234],{"class":115},[105,18839,18840],{"class":129},"void",[105,18842,12102],{"class":115},[105,18844,18845,18847,18849,18851,18853,18855,18857,18859,18861,18863,18865,18867,18869,18871,18873,18875,18877],{"class":107,"line":3938},[105,18846,11945],{"class":241},[105,18848,234],{"class":115},[105,18850,5008],{"class":241},[105,18852,5011],{"class":115},[105,18854,8451],{"class":122},[105,18856,5017],{"class":111},[105,18858,5075],{"class":241},[105,18860,5041],{"class":111},[105,18862,18498],{"class":241},[105,18864,234],{"class":115},[105,18866,5008],{"class":241},[105,18868,15017],{"class":115},[105,18870,5017],{"class":111},[105,18872,5780],{"class":241},[105,18874,234],{"class":115},[105,18876,5008],{"class":241},[105,18878,12102],{"class":115},[105,18880,18881,18883,18885,18887,18889,18891,18893,18895,18897,18899,18901,18903,18905,18907,18909],{"class":107,"line":3954},[105,18882,11967],{"class":241},[105,18884,234],{"class":115},[105,18886,5008],{"class":241},[105,18888,574],{"class":111},[105,18890,11550],{"class":129},[105,18892,5011],{"class":115},[105,18894,11611],{"class":122},[105,18896,11562],{"class":111},[105,18898,10435],{"class":129},[105,18900,11956],{"class":115},[105,18902,5017],{"class":111},[105,18904,5780],{"class":241},[105,18906,234],{"class":115},[105,18908,5008],{"class":241},[105,18910,12102],{"class":115},[105,18912,18913,18916,18918,18920,18922,18925,18927,18930,18932,18935,18938,18940,18942,18944,18946,18948],{"class":107,"line":3965},[105,18914,18915],{"class":241},"    all",[105,18917,234],{"class":115},[105,18919,5008],{"class":241},[105,18921,3535],{"class":111},[105,18923,18924],{"class":111}," readonly",[105,18926,8456],{"class":129},[105,18928,18929],{"class":115},"[] ",[105,18931,11581],{"class":111},[105,18933,18934],{"class":115}," []>(",[105,18936,18937],{"class":122},"values",[105,18939,5017],{"class":111},[105,18941,5075],{"class":241},[105,18943,11956],{"class":115},[105,18945,5017],{"class":111},[105,18947,5780],{"class":241},[105,18949,18950],{"class":115},"\u003C...>;\n",[105,18952,18953,18956,18958,18960,18962,18964,18966,18969,18971,18973,18975,18977,18979,18981,18984,18986,18988,18990,18992],{"class":107,"line":3979},[105,18954,18955],{"class":241},"    race",[105,18957,234],{"class":115},[105,18959,5008],{"class":241},[105,18961,5011],{"class":115},[105,18963,18937],{"class":122},[105,18965,5017],{"class":111},[105,18967,18968],{"class":241}," Iterable",[105,18970,234],{"class":115},[105,18972,5008],{"class":241},[105,18974,5041],{"class":111},[105,18976,18498],{"class":241},[105,18978,234],{"class":115},[105,18980,5008],{"class":241},[105,18982,18983],{"class":115},">>)",[105,18985,5017],{"class":111},[105,18987,5780],{"class":241},[105,18989,234],{"class":115},[105,18991,5008],{"class":241},[105,18993,12102],{"class":115},[105,18995,18996,18999,19001,19003,19005,19007,19009,19011,19013,19015,19017,19019,19021,19023,19025,19027,19029],{"class":107,"line":3985},[105,18997,18998],{"class":241},"    allSettled",[105,19000,234],{"class":115},[105,19002,5008],{"class":241},[105,19004,5011],{"class":115},[105,19006,18937],{"class":122},[105,19008,5017],{"class":111},[105,19010,18968],{"class":241},[105,19012,234],{"class":115},[105,19014,5008],{"class":241},[105,19016,5041],{"class":111},[105,19018,18498],{"class":241},[105,19020,234],{"class":115},[105,19022,5008],{"class":241},[105,19024,18983],{"class":115},[105,19026,5017],{"class":111},[105,19028,5780],{"class":241},[105,19030,18950],{"class":115},[105,19032,19033,19036,19038,19040,19042,19044,19046,19048,19050,19052,19054,19056,19058,19060,19062,19064,19066,19068,19070],{"class":107,"line":3990},[105,19034,19035],{"class":241},"    any",[105,19037,234],{"class":115},[105,19039,5008],{"class":241},[105,19041,5011],{"class":115},[105,19043,18937],{"class":122},[105,19045,5017],{"class":111},[105,19047,18968],{"class":241},[105,19049,234],{"class":115},[105,19051,5008],{"class":241},[105,19053,5041],{"class":111},[105,19055,18498],{"class":241},[105,19057,234],{"class":115},[105,19059,5008],{"class":241},[105,19061,18983],{"class":115},[105,19063,5017],{"class":111},[105,19065,5780],{"class":241},[105,19067,234],{"class":115},[105,19069,5008],{"class":241},[105,19071,12102],{"class":115},[105,19073,19074],{"class":107,"line":4010},[105,19075,212],{"class":115},[105,19077,19078],{"class":107,"line":4028},[105,19079,307],{"emptyLinePlaceholder":306},[105,19081,19082],{"class":107,"line":4046},[105,19083,19084],{"class":630},"\u002F\u002F ===== 3. 用 declare var 声明\"值\" =====\n",[105,19086,19087,19089,19091,19093,19095,19097],{"class":107,"line":4051},[105,19088,10352],{"class":111},[105,19090,10355],{"class":111},[105,19092,5780],{"class":115},[105,19094,5017],{"class":111},[105,19096,18699],{"class":241},[105,19098,145],{"class":115},[18,19100,19101],{},"这种三段式方法很有用，declare var....这种一般用来告诉ts运行时有个全局变量叫做Promsie，类型为PromiseConsturctor，但是声明不会生成代码，如果没有declare的话，ts就把他当做变量声明了，导致结果就是编译成js，代码执行，全局Promise覆盖为undefined。",[10,19103,2965],{"id":2965},[14,19105,19106],{"id":19106},"vue-router",[18,19108,19109],{},"vue-router是提供了一系列组价比如说router-link，它是官方的路由库，核心职责是把URL和组件映射起来，但URL变化渲染对应的组件。",[96,19111,19113],{"className":3259,"code":19112,"language":3261,"meta":101,"style":101},"const routes = [\n    { path: '\u002F', component: Home },\n    { path: '\u002Fabout', component: About },\n    { path: '\u002Fuser\u002F:id', component: User }\n];\n",[24,19114,19115,19127,19138,19148,19158],{"__ignoreMap":101},[105,19116,19117,19119,19122,19124],{"class":107,"line":108},[105,19118,568],{"class":111},[105,19120,19121],{"class":129}," routes",[105,19123,574],{"class":111},[105,19125,19126],{"class":115}," [\n",[105,19128,19129,19132,19135],{"class":107,"line":119},[105,19130,19131],{"class":115},"    { path: ",[105,19133,19134],{"class":248},"'\u002F'",[105,19136,19137],{"class":115},", component: Home },\n",[105,19139,19140,19142,19145],{"class":107,"line":133},[105,19141,19131],{"class":115},[105,19143,19144],{"class":248},"'\u002Fabout'",[105,19146,19147],{"class":115},", component: About },\n",[105,19149,19150,19152,19155],{"class":107,"line":148},[105,19151,19131],{"class":115},[105,19153,19154],{"class":248},"'\u002Fuser\u002F:id'",[105,19156,19157],{"class":115},", component: User }\n",[105,19159,19160],{"class":107,"line":163},[105,19161,19162],{"class":115},"];\n",[18,19164,19165],{},"当你访问\u002Fabout的时候就会渲染Aboud组件，渲染\u002Fuser\u002F1的时候就会渲染User，id=1。SPA单页应用没有真正的页面跳转，URL变化由Vue Router接管，用JS切换组件，这个就是前端路由。",[96,19167,19169],{"className":559,"code":19168,"language":561,"meta":101,"style":101},"import { createRouter, createWebHistory } from 'vue-router';\n\nconst router = createRouter({\n    history: createWebHistory(),   \u002F\u002F 用 HTML5 History 模式\n    routes\n});\n\nexport default router;\n",[24,19170,19171,19185,19189,19203,19217,19222,19226,19230],{"__ignoreMap":101},[105,19172,19173,19175,19178,19180,19183],{"class":107,"line":108},[105,19174,3903],{"class":111},[105,19176,19177],{"class":115}," { createRouter, createWebHistory } ",[105,19179,3909],{"class":111},[105,19181,19182],{"class":248}," 'vue-router'",[105,19184,145],{"class":115},[105,19186,19187],{"class":107,"line":119},[105,19188,307],{"emptyLinePlaceholder":306},[105,19190,19191,19193,19196,19198,19201],{"class":107,"line":133},[105,19192,568],{"class":111},[105,19194,19195],{"class":129}," router",[105,19197,574],{"class":111},[105,19199,19200],{"class":241}," createRouter",[105,19202,3935],{"class":115},[105,19204,19205,19208,19211,19214],{"class":107,"line":148},[105,19206,19207],{"class":115},"    history: ",[105,19209,19210],{"class":241},"createWebHistory",[105,19212,19213],{"class":115},"(),   ",[105,19215,19216],{"class":630},"\u002F\u002F 用 HTML5 History 模式\n",[105,19218,19219],{"class":107,"line":163},[105,19220,19221],{"class":115},"    routes\n",[105,19223,19224],{"class":107,"line":179},[105,19225,3982],{"class":115},[105,19227,19228],{"class":107,"line":194},[105,19229,307],{"emptyLinePlaceholder":306},[105,19231,19232,19234,19237],{"class":107,"line":209},[105,19233,4997],{"class":111},[105,19235,19236],{"class":111}," default",[105,19238,19239],{"class":115}," router;\n",[18,19241,19242],{},"挂载到app:",[96,19244,19246],{"className":559,"code":19245,"language":561,"meta":101,"style":101},"const app = createApp(App);\napp.use(router);\napp.mount('#app');\n",[24,19247,19248,19263,19274],{"__ignoreMap":101},[105,19249,19250,19252,19255,19257,19260],{"class":107,"line":108},[105,19251,568],{"class":111},[105,19253,19254],{"class":129}," app",[105,19256,574],{"class":111},[105,19258,19259],{"class":241}," createApp",[105,19261,19262],{"class":115},"(App);\n",[105,19264,19265,19268,19271],{"class":107,"line":119},[105,19266,19267],{"class":115},"app.",[105,19269,19270],{"class":241},"use",[105,19272,19273],{"class":115},"(router);\n",[105,19275,19276,19278,19281,19283,19286],{"class":107,"line":133},[105,19277,19267],{"class":115},[105,19279,19280],{"class":241},"mount",[105,19282,321],{"class":115},[105,19284,19285],{"class":248},"'#app'",[105,19287,327],{"class":115},[18,19289,19290],{},"组件渲染的位置：",[96,19292,19294],{"className":2963,"code":19293,"language":2965,"meta":101,"style":101},"\u003Ctemplate>\n    \u003Crouter-view \u002F>   \u003C!-- 当前路由的组件渲染在这 -->\n\u003C\u002Ftemplate>\n",[24,19295,19296,19304,19317],{"__ignoreMap":101},[105,19297,19298,19300,19302],{"class":107,"line":108},[105,19299,234],{"class":115},[105,19301,3676],{"class":237},[105,19303,257],{"class":115},[105,19305,19306,19308,19311,19314],{"class":107,"line":119},[105,19307,3731],{"class":115},[105,19309,19310],{"class":237},"router-view",[105,19312,19313],{"class":115}," \u002F>   ",[105,19315,19316],{"class":630},"\u003C!-- 当前路由的组件渲染在这 -->\n",[105,19318,19319,19321,19323],{"class":107,"line":133},[105,19320,3010],{"class":115},[105,19322,3676],{"class":237},[105,19324,257],{"class":115},[18,19326,19327],{},"router-view是路由的出口，URL匹配到哪个组件，渲染就会在这里。",[18,19329,19330],{},"router-link标签是导航链接。",[18,19332,19333],{},"active-class这个也比较重要，因为用户怎么知道自己在哪个页面，导航栏对应的链接应该高亮，这个就是active-class的由来，当前的URL匹配某个router-link的时候，vue-router就会自动给他的a加一个类，然后你使用CSS让他高亮。",[96,19335,19337],{"className":98,"code":19336,"language":100,"meta":101,"style":101},".router-link-active {\n    color: red;\n    font-weight: bold;\n}\n",[24,19338,19339,19346,19358,19370],{"__ignoreMap":101},[105,19340,19341,19344],{"class":107,"line":108},[105,19342,19343],{"class":241},".router-link-active",[105,19345,116],{"class":115},[105,19347,19348,19351,19353,19356],{"class":107,"line":119},[105,19349,19350],{"class":129},"    color",[105,19352,126],{"class":115},[105,19354,19355],{"class":129},"red",[105,19357,145],{"class":115},[105,19359,19360,19363,19365,19368],{"class":107,"line":133},[105,19361,19362],{"class":129},"    font-weight",[105,19364,126],{"class":115},[105,19366,19367],{"class":129},"bold",[105,19369,145],{"class":115},[105,19371,19372],{"class":107,"line":148},[105,19373,212],{"class":115},[18,19375,19376],{},"动态路由",[18,19378,19379],{},"刚才我们讲了:id的路由形式，这个是动态路由参数，是vue-router的最常用的动态形式，但是这个词其实涵盖了好几种类别。",[18,19381,19382,19383,19386,19387,19389,19390,19393,19394,1347,19397,91],{},":开头的叫做参数占位符，能够匹配任意非\u002F字符串，通过route.params可以访问，?表示可选，+表示一个或者多个，可以匹配 匹配 ",[24,19384,19385],{},"\u002Fuser\u002F1\u002F2\u002F3","。params.ids是数组。",[24,19388,938],{}," 表示",[21,19391,19392],{},"零个或多个","，匹配 ",[24,19395,19396],{},"\u002Fuser",[24,19398,19385],{},[96,19400,19403],{"className":19401,"code":19402,"language":392},[390],"{ path: '\u002Fuser\u002F:id?', component: User }\n{ path: '\u002Fuser\u002F:ids+', component: User }\n{ path: '\u002Fuser\u002F:ids*', component: User }\n",[24,19404,19402],{"__ignoreMap":101},[18,19406,19407],{},"路由也是支持自定义正则的：",[96,19409,19412],{"className":19410,"code":19411,"language":392},[390],"{ path: '\u002Fuser\u002F:id(\\\\d+)', component: User }\n",[24,19413,19411],{"__ignoreMap":101},[18,19415,19416,19417,19420,19421,19424],{},"括号里是",[21,19418,19419],{},"正则","，限制参数格式。",[24,19422,19423],{},"(\\\\d+)"," 只匹配数字。",[18,19426,19427],{},"路由也可以使用alias来设置别名，我们可以做到一个路由可以有多个路径：",[96,19429,19431],{"className":559,"code":19430,"language":561,"meta":101,"style":101},"{\n    path: '\u002Fuser',\n    component: User,\n    alias: '\u002Fu'   \u002F\u002F \u002Fu 也渲染 User\n}\n{\n    path: '\u002Fuser',\n    component: User,\n    alias: ['\u002Fu', '\u002Fmember']   \u002F\u002F 多个别名\n}\n",[24,19432,19433,19437,19449,19457,19470,19474,19478,19488,19494,19514],{"__ignoreMap":101},[105,19434,19435],{"class":107,"line":108},[105,19436,9866],{"class":115},[105,19438,19439,19442,19444,19447],{"class":107,"line":119},[105,19440,19441],{"class":241},"    path",[105,19443,126],{"class":115},[105,19445,19446],{"class":248},"'\u002Fuser'",[105,19448,2867],{"class":115},[105,19450,19451,19454],{"class":107,"line":133},[105,19452,19453],{"class":241},"    component",[105,19455,19456],{"class":115},": User,\n",[105,19458,19459,19462,19464,19467],{"class":107,"line":148},[105,19460,19461],{"class":241},"    alias",[105,19463,126],{"class":115},[105,19465,19466],{"class":248},"'\u002Fu'",[105,19468,19469],{"class":630},"   \u002F\u002F \u002Fu 也渲染 User\n",[105,19471,19472],{"class":107,"line":163},[105,19473,212],{"class":115},[105,19475,19476],{"class":107,"line":179},[105,19477,9866],{"class":115},[105,19479,19480,19482,19484,19486],{"class":107,"line":194},[105,19481,19441],{"class":241},[105,19483,126],{"class":115},[105,19485,19446],{"class":248},[105,19487,2867],{"class":115},[105,19489,19490,19492],{"class":107,"line":209},[105,19491,19453],{"class":241},[105,19493,19456],{"class":115},[105,19495,19496,19498,19501,19503,19505,19508,19511],{"class":107,"line":526},[105,19497,19461],{"class":241},[105,19499,19500],{"class":115},": [",[105,19502,19466],{"class":248},[105,19504,286],{"class":115},[105,19506,19507],{"class":248},"'\u002Fmember'",[105,19509,19510],{"class":115},"]   ",[105,19512,19513],{"class":630},"\u002F\u002F 多个别名\n",[105,19515,19516],{"class":107,"line":531},[105,19517,212],{"class":115},[18,19519,19520],{},"命名路由可以给路由起名字，导航的时候用名字而不是路径。",[96,19522,19524],{"className":559,"code":19523,"language":561,"meta":101,"style":101},"const routes = [\n    { path: '\u002Fuser\u002F:id', name: 'user', component: User }\n];\n\u003Crouter-link :to=\"{ name: 'user', params: { id: 123 } }\">用户\u003C\u002Frouter-link>\n",[24,19525,19526,19536,19549,19553],{"__ignoreMap":101},[105,19527,19528,19530,19532,19534],{"class":107,"line":108},[105,19529,568],{"class":111},[105,19531,19121],{"class":129},[105,19533,574],{"class":111},[105,19535,19126],{"class":115},[105,19537,19538,19540,19542,19545,19547],{"class":107,"line":119},[105,19539,19131],{"class":115},[105,19541,19154],{"class":248},[105,19543,19544],{"class":115},", name: ",[105,19546,5373],{"class":248},[105,19548,19157],{"class":115},[105,19550,19551],{"class":107,"line":133},[105,19552,19162],{"class":115},[105,19554,19555,19557,19560,19564,19567,19570,19572,19575,19578,19581,19583],{"class":107,"line":148},[105,19556,234],{"class":115},[105,19558,19559],{"class":129},"router-link",[105,19561,19563],{"class":19562},"s7hpK"," :to=\"{",[105,19565,19566],{"class":19562}," name:",[105,19568,19569],{"class":248}," 'user'",[105,19571,16650],{"class":19562},[105,19573,19574],{"class":19562}," params:",[105,19576,19577],{"class":115}," { id: ",[105,19579,19580],{"class":129},"123",[105,19582,3973],{"class":115},[105,19584,19585],{"class":19562},"}\">用户\u003C\u002Frouter-link>\n",[18,19587,19588],{},"嵌套路由：",[96,19590,19592],{"className":559,"code":19591,"language":561,"meta":101,"style":101},"{\n    path: '\u002Fuser',\n    component: UserLayout,\n    children: [\n        { path: '', component: UserHome },\n        { path: 'profile', component: UserProfile }\n    ]\n}\n",[24,19593,19594,19598,19608,19615,19623,19633,19643,19648],{"__ignoreMap":101},[105,19595,19596],{"class":107,"line":108},[105,19597,9866],{"class":115},[105,19599,19600,19602,19604,19606],{"class":107,"line":119},[105,19601,19441],{"class":241},[105,19603,126],{"class":115},[105,19605,19446],{"class":248},[105,19607,2867],{"class":115},[105,19609,19610,19612],{"class":107,"line":133},[105,19611,19453],{"class":241},[105,19613,19614],{"class":115},": UserLayout,\n",[105,19616,19617,19620],{"class":107,"line":148},[105,19618,19619],{"class":241},"    children",[105,19621,19622],{"class":115},": [\n",[105,19624,19625,19628,19630],{"class":107,"line":163},[105,19626,19627],{"class":115},"        { path: ",[105,19629,2649],{"class":248},[105,19631,19632],{"class":115},", component: UserHome },\n",[105,19634,19635,19637,19640],{"class":107,"line":179},[105,19636,19627],{"class":115},[105,19638,19639],{"class":248},"'profile'",[105,19641,19642],{"class":115},", component: UserProfile }\n",[105,19644,19645],{"class":107,"line":194},[105,19646,19647],{"class":115},"    ]\n",[105,19649,19650],{"class":107,"line":209},[105,19651,212],{"class":115},[18,19653,19654],{},"这不过我们在nuxt中可能更加习惯使用layout来解决。",[18,19656,19657],{},"路由模式",[18,19659,19660],{},"Vue-Router有两种主要路由模式，外加一个SSR使用的。首先是HASH模式，可以通过createWebHashHistory来实现，特征是url中带#。#后面是路由路径，#后面的变化不会触发页面刷新。 优点有： 兼容性好、不需要服务端配置、部署简单。缺点有：Url丑、seo不友好、锚点错误。",[18,19662,19663],{},"历史模式则是使用html5 history api （pushState\u002FrepelaceState）来管理，url变化也不会刷新页面，监听poststate事件。缺点和优点和上面优点相反，这个需要服务单配置，刷新页面的时候服务端必须要返回index.html，要配置nginx\u002Fapache的fallback。",[18,19665,19666],{},"下面来讲底层原理：",[18,19668,19669,19670,19673],{},"hash模式下 我们知道url组成为 ",[24,19671,19672],{},"URL: example.com\u002F#\u002Fuser\u002F123"," ,#后面的部分变化不会触发页面刷新，#是url的片段标识符fragment，本来用于页面内锚点(阅览文档有时候会发现，就是锚点可以自动滚动那个功能)，浏览器不会吧#后面的内容发给服务端，所以服务端只会永远看到\u002F，不需要配置。静态托管包含github pages\u002Fnetify\u002Fvercel静态模式、对象存储、CDN，特点就是只提供文件，请求什么路径就返回什么文件。不能写服务端配置，没有nginx配置，也没有fallback规则。没有路由重写能力，不能把\u002Fuser\u002F123重写成\u002Findex.html。如果你在静态托管使用历史模式的话，前端跳转到\u002Fuser\u002F123会让url变化无刷新，然后用户手动刷新会发现\u002Fuser\u002F123文件不存在-> 404。哈希不一样，就算你请求example.com\u002F#\u002Fuser\u002F123，浏览器也只会请求example.com，这样永远都能找到了，然后前端去读取片段符号后的内容\u002Fuser\u002F123，来渲染对应组件。",[18,19675,19676,19679],{},[24,19677,19678],{},"URL: example.com\u002Fuser\u002F123","\nhistory不一样，pushState能改url但是不刷新页面，刷新的时候浏览器真的会请求\u002Fuser\u002F123，服务端必须返回index.html",[96,19681,19684],{"className":19682,"code":19683,"language":392},[390],"history.pushState({}, '', '\u002Fa');   \u002F\u002F 不触发 popstate\nhistory.back();                     \u002F\u002F 触发 popstate\n",[24,19685,19683],{"__ignoreMap":101},[18,19687,19688],{},"用户访问的时候nginx不把\u002Fuser\u002F123当文件找，而是返回index.html让前端路由接管，类似于这样：",[96,19690,19694],{"className":19691,"code":19692,"language":19693,"meta":101,"style":101},"language-nginx shiki shiki-themes github-light github-dark","server {\n    listen 80;\n    server_name example.com;\n    root \u002Fvar\u002Fwww\u002Fmy-app;\n    index index.html;\n\n    location \u002F {\n        try_files $uri $uri\u002F \u002Findex.html;\n    }\n}\n","nginx",[24,19695,19696,19703,19713,19721,19729,19737,19741,19750,19758,19762],{"__ignoreMap":101},[105,19697,19698,19701],{"class":107,"line":108},[105,19699,19700],{"class":111},"server",[105,19702,116],{"class":115},[105,19704,19705,19708,19711],{"class":107,"line":119},[105,19706,19707],{"class":111},"    listen ",[105,19709,19710],{"class":129},"80",[105,19712,145],{"class":115},[105,19714,19715,19718],{"class":107,"line":133},[105,19716,19717],{"class":111},"    server_name ",[105,19719,19720],{"class":115},"example.com;\n",[105,19722,19723,19726],{"class":107,"line":148},[105,19724,19725],{"class":111},"    root ",[105,19727,19728],{"class":115},"\u002Fvar\u002Fwww\u002Fmy-app;\n",[105,19730,19731,19734],{"class":107,"line":163},[105,19732,19733],{"class":111},"    index ",[105,19735,19736],{"class":115},"index.html;\n",[105,19738,19739],{"class":107,"line":179},[105,19740,307],{"emptyLinePlaceholder":306},[105,19742,19743,19746,19748],{"class":107,"line":194},[105,19744,19745],{"class":111},"    location",[105,19747,1885],{"class":241},[105,19749,9866],{"class":115},[105,19751,19752,19755],{"class":107,"line":209},[105,19753,19754],{"class":111},"        try_files ",[105,19756,19757],{"class":115},"$uri $uri\u002F \u002Findex.html;\n",[105,19759,19760],{"class":107,"line":526},[105,19761,5161],{"class":115},[105,19763,19764],{"class":107,"line":531},[105,19765,212],{"class":115},[18,19767,19768],{},"实际项目往往还有 API 请求、HTTPS、gzip 等。",[18,19770,19771],{},"导航钩子",[18,19773,19774],{},"导航守卫有三类，分别是全局守卫、路由独享的守卫、组件内守卫。",[18,19776,19777],{},"全局守卫在每次路由的时候都会触发，通常用于全局路由检验，比如说用于全局的权限检验、登录检查或者页面标题更新。",[18,19779,19780,9847,19785,19788,19789,9847,19794,19797,19798,9847,19803,19806,19807,19812],{},[21,19781,19782],{},[24,19783,19784],{},"beforeEach",[21,19786,19787],{},"全局前置守卫","。在导航被确认之前触发，是进行权限验证（如检查登录状态）最常用的钩子。",[21,19790,19791],{},[24,19792,19793],{},"beforeResolve",[21,19795,19796],{},"全局解析守卫","。它在所有组件内守卫和异步路由组件被解析之后、导航被确认之前触发。适合在最终确认导航前执行一些数据获取或权限校验。",[21,19799,19800],{},[24,19801,19802],{},"afterEach",[21,19804,19805],{},"全局后置钩子","。导航完成后触发，",[21,19808,8556,19809,2791],{},[24,19810,19811],{},"next","，不能改变导航本身。常用于日志记录、分析或更新页面标题",[18,19814,19815,19816,19821,19822,19825,19826,31,19829,19832,19833,19836,19837,19840,19841,19844],{},"路由独享守卫定义为具体的路由配置中，只对特定路由生效。",[21,19817,19818],{},[24,19819,19820],{},"beforeEnter","：定义在路由的配置对象里。",[21,19823,19824],{},"仅在进入该路由时触发","，如果只是参数（",[24,19827,19828],{},"params",[24,19830,19831],{},"query","）发生变化（例如从 ",[24,19834,19835],{},"\u002Fusers\u002F1"," 到 ",[24,19838,19839],{},"\u002Fusers\u002F2","），它",[21,19842,19843],{},"不会","被触发。它和全局前置守卫有相同的函数签名",[18,19846,19847,19848,19853,19854,19857,19858,19861,19862,19865,19866,19868,19869,19874,19875,19878,19879,19882,19883,19886,19887,19890,19891,19853,19896,19899,19900,19903],{},"组件内收尾在路由组件内部，用于控制该组件相关的导航：\n",[21,19849,19850],{},[24,19851,19852],{},"beforeRouteEnter","：在",[21,19855,19856],{},"进入","该组件对应的路由前调用。",[21,19859,19860],{},"注意","，此时组件实例",[21,19863,19864],{},"尚未创建","，所以不能访问 this，不过，可以通过给 ",[24,19867,19811],{}," 传一个回调函数来访问最终的组件实例。 ",[21,19870,19871],{},[24,19872,19873],{},"beforeRouteUpdate","：在当前路由改变，但",[21,19876,19877],{},"该组件被复用时","调用。例如，对于一个动态路由 ",[24,19880,19881],{},"\u002Ffoo\u002F:id","，从 ",[24,19884,19885],{},"\u002Ffoo\u002F1"," 导航到 ",[24,19888,19889],{},"\u002Ffoo\u002F2"," 时会触发。",[21,19892,19893],{},[24,19894,19895],{},"beforeRouteLeave",[21,19897,19898],{},"离开","该组件对应的路由时调用。通常用于",[21,19901,19902],{},"防止用户在未保存更改时离开页面","，可以弹出确认框。",[18,19905,19906,19907,19910,19911,1347,19914,19917],{},"在 Vue 3 的 ",[24,19908,19909],{},"\u003Cscript setup>"," 中，组件内守卫通过 ",[24,19912,19913],{},"onBeforeRouteUpdate",[24,19915,19916],{},"onBeforeRouteLeave"," 这两个函数来使用",[18,19919,19920,19921,19927,19928,19930],{},"在nuxt中，导航钩子的实现被称为路由中间件，提供了比原生vue-router守卫更好的开发体验，它最大的特点是",[21,19922,19923,19924,2791],{},"去掉了 ",[24,19925,19926],{},"next()","，改用",[21,19929,11715],{},"来控制导航流程。可以做到发行：返回nothing；重定向：返回navigationTo('\u002Fpath')；终止导航：返回abortNavigation()等。",[18,19932,19933,19934,19937],{},"可以写在页面组件内，仅对当前页面生效，通过definePageMeta来定义；可以放在app\u002Fmiddleware目录里，在多个页面复用，然后在页面中通过 ",[24,19935,19936],{},"definePageMeta({ middleware: 'auth' })"," 引入。全局中间件同样放在middleware目录里，文件名需要以.global.ts结尾，会每次路由变化的时候自动执行",[96,19939,19942],{"className":19940,"code":19941,"language":392},[390],"\u003Cscript setup lang=\"ts\">\ndefinePageMeta({\n  middleware: [\n    function (to, from) {\n      \u002F\u002F 仅对当前页面生效的钩子逻辑\n    }\n  ]\n})\n\u003C\u002Fscript>\n\n\u002F\u002F app\u002Fmiddleware\u002Fauth.ts\nexport default defineNuxtRouteMiddleware((to, from) => {\n  if (!useCookie('token').value) {\n    return navigateTo('\u002Flogin')\n  }\n})\n\n\u002F\u002F app\u002Fmiddleware\u002Fanalytics.global.ts\nexport default defineNuxtRouteMiddleware((to, from) => {\n  \u002F\u002F 每次导航都会执行\n})\n",[24,19943,19941],{"__ignoreMap":101},[18,19945,19946,19947,19950,19951,19953,19954,19957,19958,19960,19961,19964,19965,19968],{},"要说明的是，如果你项目里有 ",[24,19948,19949],{},"app\u002Fpages\u002F"," 目录，Nuxt 底层依然会基于 ",[24,19952,19106],{}," 构建路由实例，",[24,19955,19956],{},"useRouter()"," 返回的也是完整的 Router 实例，它依然提供 ",[24,19959,19784],{}," 等原生守卫方法。但官方明确建议：",[21,19962,19963],{},"绝大多数导航拦截需求都应优先使用路由中间件","，而不是直接调用 ",[24,19966,19967],{},"router.beforeEach","，因为中间件在 SSR 处理、重定向语义和开发体验上都做了更好的封装",[14,19970,19971],{"id":19971},"v-model双向绑定原理",[18,19973,19974],{},"v-model是vue语法糖，本质是:value和@input组合，这里要讲到模板编译和运行时绑定。",[18,19976,19977,19980,19981,19984,19986,19987,19990,19991,19994],{},[24,19978,19979],{},"\u003Cinput v-model=\"msg\">","在编译之后会等价于\n",[24,19982,19983],{},"\u003Cinput :value=\"msg\" @input=\"msg = $event.target.value\">",[8451,19985],{},"=\"msg\"是数据->视图的表现，@input是视图->数据，每次input变化的时候就会把新的值写回msg。",[24,19988,19989],{},"$event"," 是原生 DOM 事件对象，",[24,19992,19993],{},"$event.target.value"," 是输入框的当前值。",[18,19996,19997,19998],{},"Vue 会根据元素类型，自动选",[21,19999,20000],{},"合适的 prop 和事件",[835,20002,20003,20016],{},[838,20004,20005],{},[841,20006,20007,20010,20013],{},[844,20008,20009],{},"元素",[844,20011,20012],{},"绑定的 prop",[844,20014,20015],{},"监听的事件",[854,20017,20018,20033,20049,20064,20079,20094],{},[841,20019,20020,20025,20029],{},[859,20021,20022],{},[24,20023,20024],{},"\u003Cinput type=\"text\">",[859,20026,20027],{},[24,20028,8451],{},[859,20030,20031],{},[24,20032,1044],{},[841,20034,20035,20040,20044],{},[859,20036,20037],{},[24,20038,20039],{},"\u003Cinput type=\"checkbox\">",[859,20041,20042],{},[24,20043,1494],{},[859,20045,20046],{},[24,20047,20048],{},"change",[841,20050,20051,20056,20060],{},[859,20052,20053],{},[24,20054,20055],{},"\u003Cinput type=\"radio\">",[859,20057,20058],{},[24,20059,1494],{},[859,20061,20062],{},[24,20063,20048],{},[841,20065,20066,20071,20075],{},[859,20067,20068],{},[24,20069,20070],{},"\u003Cselect>",[859,20072,20073],{},[24,20074,8451],{},[859,20076,20077],{},[24,20078,20048],{},[841,20080,20081,20086,20090],{},[859,20082,20083],{},[24,20084,20085],{},"\u003Ctextarea>",[859,20087,20088],{},[24,20089,8451],{},[859,20091,20092],{},[24,20093,1044],{},[841,20095,20096,20099,20101],{},[859,20097,20098],{},"比如说",[859,20100],{},[859,20102],{},[96,20104,20107],{"className":20105,"code":20106,"language":392},[390],"\u003Cinput type=\"checkbox\" v-model=\"checked\">\n展开就是：\n\u003Cinput\n    type=\"checkbox\"\n    :checked=\"checked\"\n    @change=\"checked = $event.target.checked\"\n>\nSELECT:\n\u003Cselect v-model=\"selected\">\n    \u003Coption value=\"a\">A\u003C\u002Foption>\n    \u003Coption value=\"b\">B\u003C\u002Foption>\n\u003C\u002Fselect>\n展开就是：\n\u003Cselect\n    :value=\"selected\"\n    @change=\"selected = $event.target.value\"\n>\n",[24,20108,20106],{"__ignoreMap":101},[18,20110,20111,20114,20115,20118],{},[24,20112,20113],{},"v-model"," 支持几个修饰符，改变行为，在v-model之后加上.lazy，就把input事件改为change事件，失焦或者回车才会更新。加上.number，会自动把输入转成数字 ",[24,20116,20117],{},"\u003Cinput v-model.number=\"age\" type=\"number\">","。.trim会自动去掉收尾空格。",[18,20120,20121,20122,20125,20126,20129,20130],{},"@input是vue模板里的事件绑定语法糖，等价于v-on:input，不是原生DOM属性。 ",[24,20123,20124],{},"\u003Cinput @input=\"handler\">"," 展开就是 ",[24,20127,20128],{},"\u003Cinput v-on:input=\"handler\">"," ，",[21,20131,20132,20133,91],{},"它编译后不是 DOM 属性，而是 ",[24,20134,20135],{},"addEventListener('input', handler)",[18,20137,20138],{},"我们知道vue模板最终会编译为render方法，@input会编译为onInput这个prop，大致如下：",[96,20140,20142],{"className":559,"code":20141,"language":561,"meta":101,"style":101},"h('input', {\n    value: msg,\n    onInput: (e) => { msg = e.target.value; }\n})\n",[24,20143,20144,20154,20159,20180],{"__ignoreMap":101},[105,20145,20146,20148,20150,20152],{"class":107,"line":108},[105,20147,5147],{"class":241},[105,20149,321],{"class":115},[105,20151,1526],{"class":248},[105,20153,2817],{"class":115},[105,20155,20156],{"class":107,"line":119},[105,20157,20158],{"class":115},"    value: msg,\n",[105,20160,20161,20164,20166,20168,20170,20172,20175,20177],{"class":107,"line":133},[105,20162,20163],{"class":241},"    onInput",[105,20165,2848],{"class":115},[105,20167,3315],{"class":122},[105,20169,2854],{"class":115},[105,20171,723],{"class":111},[105,20173,20174],{"class":115}," { msg ",[105,20176,245],{"class":111},[105,20178,20179],{"class":115}," e.target.value; }\n",[105,20181,20182],{"class":107,"line":148},[105,20183,2906],{"class":115},[18,20185,20186],{},"onInput不是DOM属性，是vue渲染函数里的一个特殊prop，vue运行时识别到on开头的prop之后会用addEventListener来绑定，就和onClick一样。而$event是DOM事件对象，组件事件 $event是emit的参数。",[18,20188,20189],{},"然后这个vnode对象大致为",[96,20191,20194],{"className":20192,"code":20193,"language":392},[390],"{\n    type: 'input',           \u002F\u002F 标签名 或 组件\n    props: { onInput: fn },  \u002F\u002F 属性\u002F事件\n    children: null,\n    key: null,\n    el: null                 \u002F\u002F 真实 DOM，初始为 null\n}\n",[24,20195,20193],{"__ignoreMap":101},[18,20197,20198],{},"经过@vue\u002Fruntime-core里的patch之后，vnode变成dom：",[96,20200,20202],{"className":559,"code":20201,"language":561,"meta":101,"style":101},"\u002F\u002F 1. 创建\nconst el = document.createElement('input');\n\n\u002F\u002F 2. 设置 value（DOM property，不是 setAttribute）\nel.value = msg;\n\n\u002F\u002F 3. 绑定事件（addEventListener）\nel.addEventListener('input', (e) => {\n    msg = e.target.value;\n});\n\n\u002F\u002F 4. 插入\ncontainer.appendChild(el);\n",[24,20203,20204,20209,20229,20233,20238,20248,20252,20257,20278,20288,20292,20296,20301],{"__ignoreMap":101},[105,20205,20206],{"class":107,"line":108},[105,20207,20208],{"class":630},"\u002F\u002F 1. 创建\n",[105,20210,20211,20213,20216,20218,20220,20223,20225,20227],{"class":107,"line":119},[105,20212,568],{"class":111},[105,20214,20215],{"class":129}," el",[105,20217,574],{"class":111},[105,20219,1518],{"class":115},[105,20221,20222],{"class":241},"createElement",[105,20224,321],{"class":115},[105,20226,1526],{"class":248},[105,20228,327],{"class":115},[105,20230,20231],{"class":107,"line":133},[105,20232,307],{"emptyLinePlaceholder":306},[105,20234,20235],{"class":107,"line":148},[105,20236,20237],{"class":630},"\u002F\u002F 2. 设置 value（DOM property，不是 setAttribute）\n",[105,20239,20240,20243,20245],{"class":107,"line":163},[105,20241,20242],{"class":115},"el.value ",[105,20244,245],{"class":111},[105,20246,20247],{"class":115}," msg;\n",[105,20249,20250],{"class":107,"line":179},[105,20251,307],{"emptyLinePlaceholder":306},[105,20253,20254],{"class":107,"line":194},[105,20255,20256],{"class":630},"\u002F\u002F 3. 绑定事件（addEventListener）\n",[105,20258,20259,20262,20264,20266,20268,20270,20272,20274,20276],{"class":107,"line":209},[105,20260,20261],{"class":115},"el.",[105,20263,3304],{"class":241},[105,20265,321],{"class":115},[105,20267,1526],{"class":248},[105,20269,3312],{"class":115},[105,20271,3315],{"class":122},[105,20273,2854],{"class":115},[105,20275,723],{"class":111},[105,20277,116],{"class":115},[105,20279,20280,20283,20285],{"class":107,"line":526},[105,20281,20282],{"class":115},"    msg ",[105,20284,245],{"class":111},[105,20286,20287],{"class":115}," e.target.value;\n",[105,20289,20290],{"class":107,"line":531},[105,20291,3982],{"class":115},[105,20293,20294],{"class":107,"line":668},[105,20295,307],{"emptyLinePlaceholder":306},[105,20297,20298],{"class":107,"line":673},[105,20299,20300],{"class":630},"\u002F\u002F 4. 插入\n",[105,20302,20303,20306,20308],{"class":107,"line":679},[105,20304,20305],{"class":115},"container.",[105,20307,3588],{"class":241},[105,20309,20310],{"class":115},"(el);\n",[18,20312,20313],{},"从数据->视图的实现是:value是吧，数据变化的时候vue自动重新执行渲染函数，生成新的vnode，和旧的vnode进行对比，将diff应用到真实dom中。",[18,20315,20316,20318],{},[8451,20317],{},"之所以可以绑定到ref和自动解包，运用的是编译器的转换。:value = \"msg\"编译后不是value:msg，而是value:msg.value。",[96,20320,20322],{"className":559,"code":20321,"language":561,"meta":101,"style":101},"\u003Cscript setup>\nimport { ref } from 'vue';\nconst msg = ref('hello');\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cinput :value=\"msg\">\n\u003C\u002Ftemplate>\n\n编译后大致是：\n\nimport { ref, unref } from 'vue';\n\nconst msg = ref('hello');\n\nfunction render() {\n    return h('input', {\n        value: unref(msg)   \u002F\u002F ← 关键：编译器加了 unref\n    });\n}\n\nfunction unref(r) {\n    return isRef(r) ? r.value : r;\n}\n",[24,20323,20324,20334,20339,20344,20352,20356,20364,20373,20378,20382,20387,20391,20404,20408,20420,20424,20433,20446,20460,20465,20469,20473,20482,20503],{"__ignoreMap":101},[105,20325,20326,20328,20330,20332],{"class":107,"line":108},[105,20327,234],{"class":115},[105,20329,2974],{"class":237},[105,20331,2977],{"class":241},[105,20333,257],{"class":115},[105,20335,20336],{"class":107,"line":119},[105,20337,20338],{"class":115},"import { ref } from 'vue';\n",[105,20340,20341],{"class":107,"line":133},[105,20342,20343],{"class":115},"const msg = ref('hello');\n",[105,20345,20346,20348,20350],{"class":107,"line":148},[105,20347,3010],{"class":115},[105,20349,2974],{"class":237},[105,20351,257],{"class":115},[105,20353,20354],{"class":107,"line":163},[105,20355,307],{"emptyLinePlaceholder":306},[105,20357,20358,20360,20362],{"class":107,"line":179},[105,20359,234],{"class":115},[105,20361,3676],{"class":237},[105,20363,257],{"class":115},[105,20365,20366,20368,20370],{"class":107,"line":194},[105,20367,3731],{"class":115},[105,20369,1044],{"class":237},[105,20371,20372],{"class":19562}," :value=\"msg\">\n",[105,20374,20375],{"class":107,"line":209},[105,20376,20377],{"class":19562},"\u003C\u002Ftemplate>\n",[105,20379,20380],{"class":107,"line":526},[105,20381,307],{"emptyLinePlaceholder":306},[105,20383,20384],{"class":107,"line":531},[105,20385,20386],{"class":19562},"编译后大致是：\n",[105,20388,20389],{"class":107,"line":668},[105,20390,307],{"emptyLinePlaceholder":306},[105,20392,20393,20395,20398,20400,20402],{"class":107,"line":673},[105,20394,3903],{"class":241},[105,20396,20397],{"class":115}," { ref, unref } ",[105,20399,3909],{"class":241},[105,20401,3912],{"class":248},[105,20403,145],{"class":19562},[105,20405,20406],{"class":107,"line":679},[105,20407,307],{"emptyLinePlaceholder":306},[105,20409,20410,20412,20415,20417],{"class":107,"line":689},[105,20411,568],{"class":241},[105,20413,20414],{"class":241}," msg",[105,20416,574],{"class":19562},[105,20418,20419],{"class":19562}," ref('hello');\n",[105,20421,20422],{"class":107,"line":699},[105,20423,307],{"emptyLinePlaceholder":306},[105,20425,20426,20428,20431],{"class":107,"line":704},[105,20427,8131],{"class":241},[105,20429,20430],{"class":19562}," render()",[105,20432,116],{"class":115},[105,20434,20435,20438,20440,20442,20444],{"class":107,"line":710},[105,20436,20437],{"class":115},"    return ",[105,20439,5147],{"class":241},[105,20441,321],{"class":115},[105,20443,1526],{"class":248},[105,20445,2817],{"class":115},[105,20447,20448,20451,20454,20457],{"class":107,"line":1136},[105,20449,20450],{"class":115},"        value: ",[105,20452,20453],{"class":241},"unref",[105,20455,20456],{"class":115},"(msg)   ",[105,20458,20459],{"class":630},"\u002F\u002F ← 关键：编译器加了 unref\n",[105,20461,20462],{"class":107,"line":1149},[105,20463,20464],{"class":115},"    });\n",[105,20466,20467],{"class":107,"line":3855},[105,20468,212],{"class":115},[105,20470,20471],{"class":107,"line":3865},[105,20472,307],{"emptyLinePlaceholder":306},[105,20474,20475,20477,20480],{"class":107,"line":3875},[105,20476,8131],{"class":241},[105,20478,20479],{"class":19562}," unref(r)",[105,20481,116],{"class":115},[105,20483,20484,20486,20489,20492,20495,20498,20500],{"class":107,"line":3884},[105,20485,20437],{"class":115},[105,20487,20488],{"class":241},"isRef",[105,20490,20491],{"class":115},"(r) ",[105,20493,20494],{"class":111},"?",[105,20496,20497],{"class":115}," r.value ",[105,20499,5017],{"class":111},[105,20501,20502],{"class":115}," r;\n",[105,20504,20505],{"class":107,"line":3889},[105,20506,212],{"class":115},[18,20508,20509],{},"这个里面主要就看sfc编译器实现了，有兴趣可以看看编译器的源码。",[14,20511,20513],{"id":20512},"mvvm-mvc","mvvm mVC",[18,20515,20516],{},"mvvm和mvc是个经常提起的话题，作为一个合格的客户端开发工程师，那必须熟悉。mvvm原来就三个分离： model、view、viewmodel。",[18,20518,20519],{},"view就对应vue的模板，描述样式。viewmodel要注意一下，原本含义是view和model之间的桥梁，可以把model的数据暴露给view，view的操作也可以传递给model。在vue里，composables是一部分，但是viewmodel还要其他几个组成，比如说script setup里使用的ref reactive 或者watch操作。 script setup整体那就是viewmodel，它把 Model 的数据\"包装\"成响应式，暴露给模板。model是数据+业务逻辑，所以组成就应该是user post实体这类数据结构+ 业务逻辑比如计算、校验、转换和数据操作比如crud、持久化等。",[18,20521,20522],{},"后端中MVC用的比较多，view就是模板，中间层就是Controller，视图开发中mvc有些不一样，在vue\u002Freact这类现代视图开发中mvc的分层思想虽然在，但是控制器手动更新view被淘汰了。\n经典mvc:",[96,20524,20527],{"className":20525,"code":20526,"language":392},[390],"用户操作 → Controller → 改 Model → Model 通知 View → View 更新\n\n\u002F\u002F 经典 MVC 的写法\nclass UserController {\n    handleClick() {\n        const user = userModel.getUser();\n        userView.render(user);   \u002F\u002F 手动调 View 的 render\n    }\n}\n",[24,20528,20526],{"__ignoreMap":101},[18,20530,20531],{},"现代后端也发展了很多，用asp.net来说，2009支持了asp.net mvc，使用mvc来分层、可测试、restful。从2021的.net6开始，又支持了minimal api，极简风格，用于几行代码的服务。2022+之后又增强到可以使用路由组、过滤器、AOT。",[10,20533,20534],{"id":20534},"计算机网络",[14,20536,20538],{"id":20537},"restful-api和graphql","RESTful API和GraphQL",[18,20540,20541,20544],{},[21,20542,20543],{},"RESTful API 和 GraphQL 是两种对立的 API 设计范式"," ，restfulapi的哲学是一切皆资源，用url标识资源：",[96,20546,20549],{"className":20547,"code":20548,"language":392},[390],"\u002Fusers          → 用户集合\n\u002Fusers\u002F123      → 单个用户\n\u002Fusers\u002F123\u002Fposts → 用户的文章\n",[24,20550,20548],{"__ignoreMap":101},[18,20552,20553],{},"http方法表意动词操作资源，比如GET方法读取 GET \u002Fuser\u002F123，POST创建，POST \u002Fusers，PUT表全量更新 PATCH部分更新 DELETE删除。",[18,20555,20556,91,20559,20562],{},[21,20557,20558],{},"方法 + URL = 完整语义",[24,20560,20561],{},"DELETE \u002Fusers\u002F123"," 一看就知道\"删除 123 号用户\"。",[18,20564,20565],{},"每个请求都是独立的，服务端不会保存客户端状态，有统一接口，状态码表意。",[18,20567,20568],{},"REST好用是好用，也有几个缺点，一个是过渡获取，客户端要的字段少服务器返回一堆，比如你可能只要name，但是一次性拿到的是全部（其实和这个要后端端点的设计），一个页面需要多个资源可能要多个请求。后端要版本管理，加字段可能破坏兼容性。",[18,20570,20571],{},"GraphQL核心思想是描述客户端需求之后服务端再返回这些。只需要单一端点：",[96,20573,20576],{"className":20574,"code":20575,"language":392},[390],"POST \u002Fgraphql\n",[24,20577,20575],{"__ignoreMap":101},[18,20579,20580],{},"客户端指定好字段就行：",[96,20582,20586],{"className":20583,"code":20584,"language":20585,"meta":101,"style":101},"language-graphql shiki shiki-themes github-light github-dark","query {\n    user(id: 123) {\n        name\n        posts {\n            title\n        }\n    }\n}\n","graphql",[24,20587,20588,20594,20609,20614,20621,20626,20630,20634],{"__ignoreMap":101},[105,20589,20590,20592],{"class":107,"line":108},[105,20591,19831],{"class":111},[105,20593,116],{"class":115},[105,20595,20596,20599,20601,20603,20605,20607],{"class":107,"line":119},[105,20597,20598],{"class":122},"    user",[105,20600,321],{"class":115},[105,20602,3604],{"class":122},[105,20604,126],{"class":115},[105,20606,19580],{"class":129},[105,20608,1000],{"class":115},[105,20610,20611],{"class":107,"line":133},[105,20612,20613],{"class":122},"        name\n",[105,20615,20616,20619],{"class":107,"line":148},[105,20617,20618],{"class":122},"        posts",[105,20620,116],{"class":115},[105,20622,20623],{"class":107,"line":163},[105,20624,20625],{"class":122},"            title\n",[105,20627,20628],{"class":107,"line":179},[105,20629,12493],{"class":115},[105,20631,20632],{"class":107,"line":194},[105,20633,5161],{"class":115},[105,20635,20636],{"class":107,"line":209},[105,20637,212],{"class":115},[18,20639,20640],{},"直接返回",[96,20642,20645],{"className":20643,"code":20644,"language":392},[390],"{\n    \"data\": {\n        \"user\": {\n            \"name\": \"Alice\",\n            \"posts\": [\n                { \"title\": \"文章 1\" },\n                { \"title\": \"文章 2\" }\n            ]\n        }\n    }\n}\n",[24,20646,20644],{"__ignoreMap":101},[18,20648,20649],{},[21,20650,20651,20652,1347,20654,20657],{},"只要 ",[24,20653,5014],{},[24,20655,20656],{},"posts.title","，就只返回这些。",[18,20659,20660],{},"缺点是缓存复杂，rest可以用HTTP天然缓存，GRAPHQL要客户端缓存，查询有复杂度，嵌套查询可能触发大量数据库查询。",[18,20662,20663,9847],{},[21,20664,20665],{},"很多项目 REST + GraphQL 并存",[442,20667,20668,20671,20674],{},[448,20669,20670],{},"简单接口用 REST",[448,20672,20673],{},"复杂查询用 GraphQL",[448,20675,20676],{},"文件上传用 Request",[18,20678,20679],{},"除此外，还有好几种其他的： gRPC、tRPC、WS、SOAP。",[14,20681,20682],{"id":20682},"请求头里的缓存",[18,20684,20685],{},"这里有个容易搞错的误区，我们知道http请求头里Cache-Control、ETag等控制的是浏览器HTTP缓存，但不是CacheStorage，两者是完全独立的存储系统。",[18,20687,20688],{},"HTTP由浏览器网络层管理，CacheStorage是通过sw管理的。http缓存会由浏览器自动写入完成，读取也是浏览器自动的，存储在磁盘\u002F内存。你可以在devtools里的disk cache里显示。",[18,20690,20691],{},"我们常见的fetch操作会默认走http缓存，如果http缓存有且未过期，就会直接返回缓存，而不经过网络请求。",[18,20693,20694],{},"如果要让资源进入cacheStorage里面的话，需要手动注册sw，在install或者fetch里显示设置cache.put()\u002Fcache.addAll()",[96,20696,20698],{"className":559,"code":20697,"language":561,"meta":101,"style":101},"\u002F\u002F install 时预缓存\nself.addEventListener('install', (event) => {\n    event.waitUntil(\n        caches.open('v1').then(cache => \n            cache.addAll(['\u002Fstyle.css', '\u002Fapp.js'])   \u002F\u002F 显式写入\n        )\n    );\n});\n",[24,20699,20700,20705,20725,20733,20756,20779,20784,20788],{"__ignoreMap":101},[105,20701,20702],{"class":107,"line":108},[105,20703,20704],{"class":630},"\u002F\u002F install 时预缓存\n",[105,20706,20707,20709,20711,20713,20715,20717,20719,20721,20723],{"class":107,"line":119},[105,20708,5598],{"class":115},[105,20710,3304],{"class":241},[105,20712,321],{"class":115},[105,20714,5605],{"class":248},[105,20716,3312],{"class":115},[105,20718,5610],{"class":122},[105,20720,2854],{"class":115},[105,20722,723],{"class":111},[105,20724,116],{"class":115},[105,20726,20727,20729,20731],{"class":107,"line":133},[105,20728,5621],{"class":115},[105,20730,5624],{"class":241},[105,20732,583],{"class":115},[105,20734,20735,20737,20739,20741,20743,20745,20747,20749,20751,20753],{"class":107,"line":148},[105,20736,5631],{"class":115},[105,20738,5634],{"class":241},[105,20740,321],{"class":115},[105,20742,5639],{"class":248},[105,20744,5642],{"class":115},[105,20746,5530],{"class":241},[105,20748,321],{"class":115},[105,20750,5649],{"class":122},[105,20752,5150],{"class":111},[105,20754,20755],{"class":115}," \n",[105,20757,20758,20761,20763,20765,20768,20770,20773,20776],{"class":107,"line":163},[105,20759,20760],{"class":115},"            cache.",[105,20762,5664],{"class":241},[105,20764,6472],{"class":115},[105,20766,20767],{"class":248},"'\u002Fstyle.css'",[105,20769,286],{"class":115},[105,20771,20772],{"class":248},"'\u002Fapp.js'",[105,20774,20775],{"class":115},"])   ",[105,20777,20778],{"class":630},"\u002F\u002F 显式写入\n",[105,20780,20781],{"class":107,"line":179},[105,20782,20783],{"class":115},"        )\n",[105,20785,20786],{"class":107,"line":194},[105,20787,5708],{"class":115},[105,20789,20790],{"class":107,"line":209},[105,20791,3982],{"class":115},[18,20793,20794],{},"至于性能上，数据读取上CacheStorage比http缓存慢，因为多了一层sw的启动和js执行开销。我们做pwa应用的时候可能会使用到。",[14,20796,20797],{"id":20797},"http",[18,20799,20800],{},"HTTP是工作在应用层的协议，用于在客户端比如浏览器和服务器之间传输数据，是web的基石，采用的是请求-响应模型，无状态、可扩展。",[18,20802,20803],{},"状态码来说：",[18,20805,20806],{},"1xx比如101 Switching Protocols。2xx开头是成功的，比如200 OK 201 Created、204 No Content。3xx开头为重定向，301是永久重定向，302是临时的。4xx开头是客户端错误，5开头的是服务端错误。",[18,20808,20809],{},"常见请求头\u002F响应头",[835,20811,20812,20821],{},[838,20813,20814],{},[841,20815,20816,20819],{},[844,20817,20818],{},"头部",[844,20820,2241],{},[854,20822,20823,20833,20843,20853,20863,20873,20886,20896,20907,20917,20927],{},[841,20824,20825,20830],{},[859,20826,20827],{},[24,20828,20829],{},"Host",[859,20831,20832],{},"目标主机（HTTP\u002F1.1 必需）",[841,20834,20835,20840],{},[859,20836,20837],{},[24,20838,20839],{},"Content-Type",[859,20841,20842],{},"报文体的 MIME 类型",[841,20844,20845,20850],{},[859,20846,20847],{},[24,20848,20849],{},"Content-Length",[859,20851,20852],{},"报文体长度",[841,20854,20855,20860],{},[859,20856,20857],{},[24,20858,20859],{},"Accept",[859,20861,20862],{},"客户端可接受的类型",[841,20864,20865,20870],{},[859,20866,20867],{},[24,20868,20869],{},"Authorization",[859,20871,20872],{},"认证凭证（如 Bearer Token）",[841,20874,20875,20883],{},[859,20876,20877,1885,20880],{},[24,20878,20879],{},"Cookie",[24,20881,20882],{},"Set-Cookie",[859,20884,20885],{},"会话状态管理",[841,20887,20888,20893],{},[859,20889,20890],{},[24,20891,20892],{},"Cache-Control",[859,20894,20895],{},"缓存策略",[841,20897,20898,20904],{},[859,20899,20900,1885,20902],{},[24,20901,7672],{},[24,20903,7675],{},[859,20905,20906],{},"协商缓存",[841,20908,20909,20915],{},[859,20910,20911,1885,20913],{},[24,20912,7679],{},[24,20914,7682],{},[859,20916,20906],{},[841,20918,20919,20924],{},[859,20920,20921],{},[24,20922,20923],{},"Access-Control-Allow-Origin",[859,20925,20926],{},"CORS 跨域许可",[841,20928,20929,20932],{},[859,20930,20931],{},"浏览器同源策略限制跨域请求。CORS 通过响应头授权：",[859,20933],{},[96,20935,20938],{"className":20936,"code":20937,"language":392},[390],"Access-Control-Allow-Origin: https:\u002F\u002Fexample.com\nAccess-Control-Allow-Methods: GET, POST\nAccess-Control-Allow-Headers: Content-Type\nAccess-Control-Allow-Credentials: true\n",[24,20939,20937],{"__ignoreMap":101},[18,20941,20942,20945,20946,20949],{},[21,20943,20944],{},"预检请求","：非简单请求（如 PUT、自定义头）会先发 ",[24,20947,20948],{},"OPTIONS"," 请求确认。OPTIONS请求，cors预检请求的响应状态码通常是204 No Content。当使用了非简单方法比如PUT、DELETE、PATCH等的时候就会自动发送这个，或是使用了自定义请求头Authorization、X-Custom-Header也会。",[14,20951,20952],{"id":20952},"http的长连接",[18,20954,20955],{},"http长连接核心是复用一个tcp连接发送多个请求，避免每次都重新握手等。短连接是http\u002F1.0默认，每次都要经过tcp握手。长连接是http\u002F1.1默认，多个请求会复用一个tcp。但是http\u002F1.1会有队头阻塞问题，同一个连接上，前一个请求还没有响应完，后面的请求要等待。",[18,20957,20958],{},"http\u002F2的多路复用解决了这个问题：",[96,20960,20963],{"className":20961,"code":20962,"language":392},[390],"一个 TCP 连接\n    ├── Stream 1: 请求 A\n    ├── Stream 2: 请求 B\n    ├── Stream 3: 请求 C\n    └── 并发传输，互不阻塞\n",[24,20964,20962],{"__ignoreMap":101},[18,20966,20967],{},"通过一个连接并发多个请求，将请求\u002F响应拆成帧，交错传输和头部压缩的方式解决了，http\u002F2的长连接更加高效，一个连接搞定所有请求。",[18,20969,20970],{},"讲一下长连接和头部压缩：",[18,20972,20973],{},"长连接通过Connection: keep-alive声明，依靠tcp keep-alive传输层：",[96,20975,20978],{"className":20976,"code":20977,"language":392},[390],"setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));\n",[24,20979,20977],{"__ignoreMap":101},[18,20981,20982],{},"可以检测连接是否还或者，对端挂了，网络断了。应用层自己会发送心跳包，比如说30s一个，来保持链接活跃。如果说应用崩溃，连接不会通过FIN关闭的话，内核会替他关闭，可能是RST，也可能是FIN，比如说进程如果被SIGKILL 杀死，还有未发送的消息，他就会发RST，处理完了就会发FIN。",[18,20984,20985],{},"头部压缩式http\u002F2的核心优化，因为http\u002F1.1头部每次都会携带大量重复头部：",[96,20987,20990],{"className":20988,"code":20989,"language":392},[390],"GET \u002Findex.html\nHost: example.com\nUser-Agent: Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) ...\nAccept: text\u002Fhtml,application\u002Fxhtml+xml,...\nAccept-Language: zh-CN,zh;q=0.9\nAccept-Encoding: gzip, deflate, br\nCookie: session=abc123; theme=dark; ...\nConnection: keep-alive\n",[24,20991,20989],{"__ignoreMap":101},[18,20993,20994,20995,20998,20999,21002,21003,21006],{},"下一个请求也是一样。每个头部都会重复传递这些，显然会浪费带宽，",[21,20996,20997],{},"HTTP\u002F1.1 用 gzip 压缩 body，但不压缩头部","——头部一直是明文。",[21,21000,21001],{},"HTTP\u002F2 引入头部压缩","——",[21,21004,21005],{},"HPACK","。61个常见头部，然后给编号压缩，这就是静态表，对于连接期间动态维护的头部，使用索引来压缩，这就是静态表。对字符串使用哈夫曼编码，这样就可以让头部压缩很大一部分。",[14,21008,21009],{"id":21009},"https",[18,21011,21012],{},"我们都知道http是明文传输，数据裸奔，中间人可以直接看到密码、cookie等信息 还能篡改，https可以用tls解决这些问题，https分为四层：",[96,21014,21017],{"className":21015,"code":21016,"language":392},[390],"HTTP          ← 应用层\n  ↓\nTLS\u002FSSL       ← 安全层（加解密、认证、完整性）\n  ↓\nTCP           ← 传输层\n  ↓\nIP            ← 网络层\n",[24,21018,21016],{"__ignoreMap":101},[18,21020,21021,21022,21025,21026,21029],{},"https = http + tls，http报文会被tls加密之后走tcp传输。tls使用非对称加密，在握手阶段解决。公钥是公开的，私钥是服务器自己保留，握手阶段会协商出",[21,21023,21024],{},"会话秘钥","，对称加密使用一个秘钥加解密，在",[21,21027,21028],{},"非对称加密协商","的时候，这个秘钥会被协商出来。",[18,21031,21032],{},"我们用tls1.2版本解释：",[96,21034,21037],{"className":21035,"code":21036,"language":392},[390],"客户端                                服务器\n  │                                    │\n  │ ① Client Hello                    │\n  │   - 支持的 TLS 版本                 │\n  │   - 支持的加密套件                   │\n  │   - 客户端随机数（Random1）          │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ② Server Hello                    │\n  │   - 选定的 TLS 版本                 │\n  │   - 选定的加密套件                   │\n  │   - 服务器随机数（Random2）          │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ③ Certificate                     │\n  │   - 服务器证书（含公钥）             │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ④ Server Key Exchange（可选）      │\n  │   - 密钥交换参数                    │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ⑤ Server Hello Done               │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ⑥ 客户端验证证书                    │\n  │   - 检查证书是否可信                 │\n  │   - 检查域名是否匹配                 │\n  │   - 检查是否过期                    │\n  │                                    │\n  │ ⑦ Client Key Exchange             │\n  │   - 用服务器公钥加密\"预主密钥\"       │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ⑧ Change Cipher Spec              │\n  │   - 通知\"后续用协商的密钥加密\"       │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ⑨ Finished                        │\n  │   - 加密的握手摘要                  │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ⑩ Change Cipher Spec              │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ⑪ Finished                        │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ⑫ 开始加密通信（对称加密）           │\n  │ \u003C════════════════════════════════> │\n",[24,21038,21036],{"__ignoreMap":101},[18,21040,21041,21042,21045,21046,21049],{},"客户端会检查证书链，证书是否是可信CA签发，浏览器会内置CA列表。证书里面的域名是否匹配访问的域名，以及是否过期。客户端会生成",[21,21043,21044],{},"预主秘钥","，用于服务器公钥加密，发送给服务器，服务器用",[21,21047,21048],{},"私钥解密","，拿到预主密钥。",[96,21051,21054],{"className":21052,"code":21053,"language":392},[390],"主密钥 = PRF(预主密钥, \"master secret\", Random1 + Random2)\n",[24,21055,21053],{"__ignoreMap":101},[18,21057,21058],{},"主密钥用来生成会话秘钥，也就是对称加密的秘钥，这样握手就完成了。",[14,21060,21061],{"id":21061},"tcp的可靠",[18,21063,21064],{},"tcp工作在不可靠的IP层之上，它之所以可靠是因为依靠 序列号、ACK、超时重传、快速重传、校验和、连接管理。",[18,21066,21067],{},"传输过程中每个字节都有自己的编号：",[96,21069,21072],{"className":21070,"code":21071,"language":392},[390],"发送方：字节 1-1000 是第 1 段，1001-2000 是第 2 段\n接收方：收到后按序号重组，乱序也能还原\n",[24,21073,21071],{"__ignoreMap":101},[18,21075,21076],{},"这样重复的段会按照序号丢弃，乱序到达之后会按照序号重排，确认应答后告诉对方收到了哪些。接收方收到数据之后会回复一个ACK：",[96,21078,21081],{"className":21079,"code":21080,"language":392},[390],"发送方 → [SEQ=1, 1000字节] → 接收方\n发送方 ← [ACK=1001]        ← 接收方\n",[24,21082,21080],{"__ignoreMap":101},[18,21084,21085,21086,21089],{},"ACK=1001 意思是\"1000 及之前的字节我都收到了，下一个期望 1001。然后这样累计确认即可。发送发发完数据之后会启动定时器，接着等待ACK，如果没有就会重传SEQ=1， ",[21,21087,21088],{},"超时时间（RTO）动态计算","：根据 RTT（往返时间）动态调整。快速重传不需要等待超时，收到3个重复ACK就重传。",[96,21091,21094],{"className":21092,"code":21093,"language":392},[390],"发送 [1][2][3][4][5]\n接收方收到 [1][3][4][5]，缺 [2]\n接收方回 ACK=2（重复 3 次）\n发送方收到 3 个 ACK=2 → 立即重传 [2]\n",[24,21095,21093],{"__ignoreMap":101},[18,21097,21098],{},"每个TCP都有校验和，用于检测数据是否损坏。连接管理让双方完成三次握手建立连接、四次挥手关闭，能够确保双方都准备好，确保数据都传输完成。",[18,21100,21101],{},"顺便讲解udp，udp是故意不做可靠性保证的，他把控制器交给了应用层，udp也有校验和，但是没有序列号、ack、重传、排序、去重、流量控制等等行为。",[14,21103,21104],{"id":21104},"tcp窗口",[18,21106,21107],{},"窗口是tcp的流量控制机制，接收方可以提醒发送方自己还能接受多少。接受窗口rwnd是接收方的剩余缓冲区大小：",[96,21109,21112],{"className":21110,"code":21111,"language":392},[390],"接收方缓冲区 64KB\n已用 16KB\n剩余 48KB → 告诉发送方\"窗口 = 48KB\"\n",[24,21113,21111],{"__ignoreMap":101},[18,21115,21116],{},"然后发送方就可以根据窗口大小决定能发多少，这是为了防止接收方被大量的流量淹没，有窗口才能让双方能够按照节奏来发送。",[18,21118,21119],{},"窗口为0的话，发送方就会停止发送，并启动零窗口探测定时器，询问接收方窗口是否开了，当接收方处理完之后窗口才会>0，然后通知发送方继续，如果接收方窗口开了但通知丢了，发送方靠探测恢复。",[18,21121,21122,21123],{},"并且有指标可以计算：  ",[21,21124,21125],{},"吞吐量 = 窗口大小 \u002F RTT",[96,21127,21130],{"className":21128,"code":21129,"language":392},[390],"窗口 64KB，RTT 100ms\n吞吐量 = 64KB \u002F 0.1s = 640KB\u002Fs ≈ 5Mbps\n\n窗口 64KB，RTT 10ms\n吞吐量 = 64KB \u002F 0.01s = 6.4MB\u002Fs ≈ 51Mbps\n",[24,21131,21129],{"__ignoreMap":101},[18,21133,21134],{},"RTT越大，窗口越重要，RTT越大的话，我们自然需要窗口越大，不然吞吐量上不去。",[18,21136,21137],{},"TCP头部窗口字段只有16位，也就是最大64KB，如果还不够，可以使用窗口缩放选项RFC 1323，能够把窗口扩大到8MB，现代TCP窗口都会启用窗口缩放。",[18,21139,21140,21141,21144,21145,21148],{},"tcp窗口分几个层次：内核自动管理、系统参数调整、应用层通过socket选项来间接影响。应用层",[21,21142,21143],{},"看不到也改不了"," TCP 窗口字段本身。窗口是内核根据",[21,21146,21147],{},"接收缓冲区剩余空间","自动计算的。",[18,21150,21151],{},"linux可以通过命令查看：",[96,21153,21156],{"className":21154,"code":21155,"language":392},[390],"# 看系统级参数\nsysctl net.ipv4.tcp_rmem\nsysctl net.ipv4.tcp_wmem\n\n# 输出示例：\n# net.ipv4.tcp_rmem = 4096 131072 6291456\n#                    min  default  max\n",[24,21157,21155],{"__ignoreMap":101},[14,21159,21161],{"id":21160},"三次握手四次挥手","三次握手、四次挥手",[18,21163,21164,21165,9383,21168,91],{},"TCP 的三次握手建立连接，四次挥手关闭连接。这两个过程的核心是",[21,21166,21167],{},"序列号同步",[21,21169,21170],{},"双向确认",[96,21172,21175],{"className":21173,"code":21174,"language":392},[390],"客户端                                服务器\n  │                                    │\n  │ ① SYN                              │\n  │   SEQ=x（客户端初始序列号）           │\n  │   SYN=1                            │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ② SYN + ACK                        │\n  │   SEQ=y（服务器初始序列号）           │\n  │   ACK=x+1（确认收到 x）              │\n  │   SYN=1, ACK=1                     │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ③ ACK                              │\n  │   SEQ=x+1                          │\n  │   ACK=y+1（确认收到 y）              │\n  │   ACK=1                            │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ 连接建立，开始传数据                  │\n",[24,21176,21174],{"__ignoreMap":101},[18,21178,21179],{},"第一次握手客户端进入SYN_SENT状态，发送SYN=1，SEQ=X。第二次服务器发送SYN=1 ACK=X+1  SEQ=Y,服务器进入SYN_RCVD。第三次握手客户端收到Y，连接建立，回复ACK=Y+1，双方进入ESTABLISHED。",[18,21181,21182,21183,21186],{},"初始序列号是随机的，是",[21,21184,21185],{},"防止历史连接的数据混入新连接","。当新连接的序列号和旧连接不同，旧包会被丢弃，ISN生成基于时钟+哈希防止被猜测。",[18,21188,21189],{},"四次挥手用于关闭连接：",[96,21191,21194],{"className":21192,"code":21193,"language":392},[390],"客户端                                服务器\n  │                                    │\n  │ ① FIN                              │\n  │   SEQ=u                            │\n  │   FIN=1                            │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ ② ACK                              │\n  │   SEQ=v                            │\n  │   ACK=u+1                          │\n  │   ACK=1                            │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │   （服务器可能还在发数据）            │\n  │                                    │\n  │ ③ FIN                              │\n  │   SEQ=w                            │\n  │   ACK=u+1                          │\n  │   FIN=1, ACK=1                     │\n  │ \u003C───────────────────────────────── │\n  │                                    │\n  │ ④ ACK                              │\n  │   SEQ=u+1                          │\n  │   ACK=w+1                          │\n  │   ACK=1                            │\n  │ ─────────────────────────────────> │\n  │                                    │\n  │ 连接关闭                            │\n",[24,21195,21193],{"__ignoreMap":101},[18,21197,21198],{},"第一次挥手，客户端发送FIN=1 SEQ=U，客户端进入FIN_WAIT_1，第二次挥手服务端收到，发送ACK=U+1 SEQ=V，服务器进入CLOSE_WAIT，客户端进入FIN_WAIT_2，第三次挥手，服务器已经发完数据，发送FIN=1 ACK=1 SEQ=W ACK=U+1，服务器进入LAST_ACK状态，最后一次客户端收到FIN，发送SEQ=U+1 ACK=W+1，客户端进入TIME_WAIT状态，2MSL之后关闭。",[14,21200,21202],{"id":21201},"sse","SSE",[18,21204,21205],{},"全称叫做服务器推送事件，允许服务器单向、持续地向客户端推送文本数据的技术，基于HTTP协议，专门用于实现服务器到客户端的实时通信。",[18,21207,21208],{},"只有服务器能够主动推送给客户端，而不允许客户端通过同一连接反向发送。使用标准的HTTP请求，无需特殊协议或者端口，只支持UTF8文本，二进制数据需要编码比如base64,。浏览器内置了断线重连机制，默认约3s后重试，相比ws，实现更加简单，适合推送场景。",[18,21210,21211],{},"客户端发起请求的时候会通过EventSource API向服务器发起一个普通HTTP GET请求，服务器响应头设置Content-Type: text\u002Fevent\u002Fstream，之后就可以保持连接不关闭，服务器会按照SSE格式不断向客户端发送数据，客户端接受事件之后浏览器会解析数据流，出发对应的事件回调。",[96,21213,21215],{"className":3259,"code":21214,"language":3261,"meta":101,"style":101},"const eventSource = new EventSource('\u002Fapi\u002Fstream');\n\n\u002F\u002F 接收默认消息\neventSource.onmessage = (event) => {\n    console.log('收到消息:', event.data);\n};\n\n\u002F\u002F 接收自定义事件\neventSource.addEventListener('update', (event) => {\n    console.log('更新事件:', event.data);\n});\n\n\u002F\u002F 错误处理\neventSource.onerror = (err) => {\n    console.error('连接错误:', err);\n};\n\n\u002F\u002F 关闭连接\n\u002F\u002F eventSource.close();\n",[24,21216,21217,21238,21242,21247,21266,21279,21283,21287,21292,21313,21326,21330,21334,21339,21357,21371,21375,21379,21384],{"__ignoreMap":101},[105,21218,21219,21221,21224,21226,21228,21231,21233,21236],{"class":107,"line":108},[105,21220,568],{"class":111},[105,21222,21223],{"class":129}," eventSource",[105,21225,574],{"class":111},[105,21227,3496],{"class":111},[105,21229,21230],{"class":241}," EventSource",[105,21232,321],{"class":115},[105,21234,21235],{"class":248},"'\u002Fapi\u002Fstream'",[105,21237,327],{"class":115},[105,21239,21240],{"class":107,"line":119},[105,21241,307],{"emptyLinePlaceholder":306},[105,21243,21244],{"class":107,"line":133},[105,21245,21246],{"class":630},"\u002F\u002F 接收默认消息\n",[105,21248,21249,21252,21254,21256,21258,21260,21262,21264],{"class":107,"line":148},[105,21250,21251],{"class":115},"eventSource.",[105,21253,5122],{"class":241},[105,21255,574],{"class":111},[105,21257,4379],{"class":115},[105,21259,5610],{"class":122},[105,21261,2854],{"class":115},[105,21263,723],{"class":111},[105,21265,116],{"class":115},[105,21267,21268,21270,21272,21274,21277],{"class":107,"line":163},[105,21269,6680],{"class":115},[105,21271,639],{"class":241},[105,21273,321],{"class":115},[105,21275,21276],{"class":248},"'收到消息:'",[105,21278,6690],{"class":115},[105,21280,21281],{"class":107,"line":179},[105,21282,4459],{"class":115},[105,21284,21285],{"class":107,"line":194},[105,21286,307],{"emptyLinePlaceholder":306},[105,21288,21289],{"class":107,"line":209},[105,21290,21291],{"class":630},"\u002F\u002F 接收自定义事件\n",[105,21293,21294,21296,21298,21300,21303,21305,21307,21309,21311],{"class":107,"line":526},[105,21295,21251],{"class":115},[105,21297,3304],{"class":241},[105,21299,321],{"class":115},[105,21301,21302],{"class":248},"'update'",[105,21304,3312],{"class":115},[105,21306,5610],{"class":122},[105,21308,2854],{"class":115},[105,21310,723],{"class":111},[105,21312,116],{"class":115},[105,21314,21315,21317,21319,21321,21324],{"class":107,"line":531},[105,21316,6680],{"class":115},[105,21318,639],{"class":241},[105,21320,321],{"class":115},[105,21322,21323],{"class":248},"'更新事件:'",[105,21325,6690],{"class":115},[105,21327,21328],{"class":107,"line":668},[105,21329,3982],{"class":115},[105,21331,21332],{"class":107,"line":673},[105,21333,307],{"emptyLinePlaceholder":306},[105,21335,21336],{"class":107,"line":679},[105,21337,21338],{"class":630},"\u002F\u002F 错误处理\n",[105,21340,21341,21343,21345,21347,21349,21351,21353,21355],{"class":107,"line":689},[105,21342,21251],{"class":115},[105,21344,7156],{"class":241},[105,21346,574],{"class":111},[105,21348,4379],{"class":115},[105,21350,5562],{"class":122},[105,21352,2854],{"class":115},[105,21354,723],{"class":111},[105,21356,116],{"class":115},[105,21358,21359,21361,21363,21365,21368],{"class":107,"line":699},[105,21360,6680],{"class":115},[105,21362,7175],{"class":241},[105,21364,321],{"class":115},[105,21366,21367],{"class":248},"'连接错误:'",[105,21369,21370],{"class":115},", err);\n",[105,21372,21373],{"class":107,"line":704},[105,21374,4459],{"class":115},[105,21376,21377],{"class":107,"line":710},[105,21378,307],{"emptyLinePlaceholder":306},[105,21380,21381],{"class":107,"line":1136},[105,21382,21383],{"class":630},"\u002F\u002F 关闭连接\n",[105,21385,21386],{"class":107,"line":1149},[105,21387,21388],{"class":630},"\u002F\u002F eventSource.close();\n",[18,21390,21391],{},"响应头：",[96,21393,21397],{"className":21394,"code":21395,"language":21396,"meta":101,"style":101},"language-cpp shiki shiki-themes github-light github-dark","std::string get_sse_headers() {\n    response \u003C\u003C \"HTTP\u002F1.1 200 OK\\r\\n\"\n        \u003C\u003C \"Content-Type: text\u002Fevent-stream\\r\\n\"   \u002F\u002F 必需：SSE 的 MIME 类型\n        \u003C\u003C \"Cache-Control: no-cache\\r\\n\"           \u002F\u002F 禁止缓存\n        \u003C\u003C \"Connection: keep-alive\\r\\n\"            \u002F\u002F 保持长连接\n        \u003C\u003C \"Access-Control-Allow-Origin: *\\r\\n\"    \u002F\u002F CORS 跨域\n        \u003C\u003C \"\\r\\n\";\n}\n","cpp",[24,21398,21399,21415,21432,21448,21462,21476,21490,21503],{"__ignoreMap":101},[105,21400,21401,21404,21407,21410,21413],{"class":107,"line":108},[105,21402,21403],{"class":241},"std",[105,21405,21406],{"class":115},"::",[105,21408,21409],{"class":241},"string",[105,21411,21412],{"class":241}," get_sse_headers",[105,21414,9247],{"class":115},[105,21416,21417,21420,21423,21426,21429],{"class":107,"line":119},[105,21418,21419],{"class":115},"    response ",[105,21421,21422],{"class":111},"\u003C\u003C",[105,21424,21425],{"class":248}," \"HTTP\u002F1.1 200 OK",[105,21427,21428],{"class":129},"\\r\\n",[105,21430,21431],{"class":248},"\"\n",[105,21433,21434,21437,21440,21442,21445],{"class":107,"line":133},[105,21435,21436],{"class":111},"        \u003C\u003C",[105,21438,21439],{"class":248}," \"Content-Type: text\u002Fevent-stream",[105,21441,21428],{"class":129},[105,21443,21444],{"class":248},"\"",[105,21446,21447],{"class":630},"   \u002F\u002F 必需：SSE 的 MIME 类型\n",[105,21449,21450,21452,21455,21457,21459],{"class":107,"line":148},[105,21451,21436],{"class":111},[105,21453,21454],{"class":248}," \"Cache-Control: no-cache",[105,21456,21428],{"class":129},[105,21458,21444],{"class":248},[105,21460,21461],{"class":630},"           \u002F\u002F 禁止缓存\n",[105,21463,21464,21466,21469,21471,21473],{"class":107,"line":163},[105,21465,21436],{"class":111},[105,21467,21468],{"class":248}," \"Connection: keep-alive",[105,21470,21428],{"class":129},[105,21472,21444],{"class":248},[105,21474,21475],{"class":630},"            \u002F\u002F 保持长连接\n",[105,21477,21478,21480,21483,21485,21487],{"class":107,"line":179},[105,21479,21436],{"class":111},[105,21481,21482],{"class":248}," \"Access-Control-Allow-Origin: *",[105,21484,21428],{"class":129},[105,21486,21444],{"class":248},[105,21488,21489],{"class":630},"    \u002F\u002F CORS 跨域\n",[105,21491,21492,21494,21497,21499,21501],{"class":107,"line":194},[105,21493,21436],{"class":111},[105,21495,21496],{"class":248}," \"",[105,21498,21428],{"class":129},[105,21500,21444],{"class":248},[105,21502,145],{"class":115},[105,21504,21505],{"class":107,"line":209},[105,21506,212],{"class":115},[18,21508,21509,21512,21513,21516,21517,21519,21520,21523],{},[24,21510,21511],{},"Content-Type: text\u002Fevent-stream"," 是 SSE 的",[21,21514,21515],{},"标志性头部","，浏览器据此识别为事件流。 头部以 ",[24,21518,21428],{}," 分隔，最后用一个空行 ",[24,21521,21522],{},"\\r\\n\\r\\n"," 结束。",[96,21525,21527],{"className":21394,"code":21526,"language":21396,"meta":101,"style":101},"void static send_sse_event(SOCKET client, const std::string& event, const std::string data) {\n    std::string message = \"event: \" + event + \"\\r\\n\"\n        \"data: \" + data + \"\\r\\n\\r\\n\";\n    send(client, message.c_str(), message.size(), 0);\n}\n",[24,21528,21529,21568,21599,21619,21643],{"__ignoreMap":101},[105,21530,21531,21533,21536,21539,21542,21544,21547,21549,21551,21554,21557,21559,21561,21563,21565],{"class":107,"line":108},[105,21532,18840],{"class":111},[105,21534,21535],{"class":111}," static",[105,21537,21538],{"class":241}," send_sse_event",[105,21540,21541],{"class":115},"(SOCKET client, ",[105,21543,568],{"class":111},[105,21545,21546],{"class":241}," std",[105,21548,21406],{"class":115},[105,21550,21409],{"class":115},[105,21552,21553],{"class":111},"&",[105,21555,21556],{"class":115}," event, ",[105,21558,568],{"class":111},[105,21560,21546],{"class":241},[105,21562,21406],{"class":115},[105,21564,21409],{"class":115},[105,21566,21567],{"class":115}," data) {\n",[105,21569,21570,21573,21575,21577,21580,21582,21585,21588,21591,21593,21595,21597],{"class":107,"line":119},[105,21571,21572],{"class":241},"    std",[105,21574,21406],{"class":115},[105,21576,21409],{"class":241},[105,21578,21579],{"class":115}," message ",[105,21581,245],{"class":111},[105,21583,21584],{"class":248}," \"event: \"",[105,21586,21587],{"class":111}," +",[105,21589,21590],{"class":115}," event ",[105,21592,1085],{"class":111},[105,21594,21496],{"class":248},[105,21596,21428],{"class":129},[105,21598,21431],{"class":248},[105,21600,21601,21604,21606,21609,21611,21613,21615,21617],{"class":107,"line":133},[105,21602,21603],{"class":248},"        \"data: \"",[105,21605,21587],{"class":111},[105,21607,21608],{"class":115}," data ",[105,21610,1085],{"class":111},[105,21612,21496],{"class":248},[105,21614,21522],{"class":129},[105,21616,21444],{"class":248},[105,21618,145],{"class":115},[105,21620,21621,21624,21627,21630,21633,21636,21639,21641],{"class":107,"line":148},[105,21622,21623],{"class":241},"    send",[105,21625,21626],{"class":115},"(client, message.",[105,21628,21629],{"class":241},"c_str",[105,21631,21632],{"class":115},"(), message.",[105,21634,21635],{"class":241},"size",[105,21637,21638],{"class":115},"(), ",[105,21640,1100],{"class":129},[105,21642,327],{"class":115},[105,21644,21645],{"class":107,"line":163},[105,21646,212],{"class":115},[18,21648,21649],{},"这个是SSE中自定义事件的完整报文格式，由两行字短组成，用空行结束：",[96,21651,21654],{"className":21652,"code":21653,"language":392},[390],"event: heartbeat\\r\\n\ndata: ping count: 5\\r\\n\n\\r\\n\n",[24,21655,21653],{"__ignoreMap":101},[18,21657,21658],{},"浏览器侧：",[96,21660,21662],{"className":3259,"code":21661,"language":3261,"meta":101,"style":101},"const es = new EventSource('\u002Fapi\u002Fstream');\n\n\u002F\u002F 监听默认消息\nes.onmessage = (e) => {\n    console.log('默认消息:', e.data);\n};\n\n\u002F\u002F 监听 heartbeat 自定义事件\nes.addEventListener('heartbeat', (e) => {\n    console.log('心跳事件:', e.data);\n});\n",[24,21663,21664,21683,21687,21692,21711,21724,21728,21732,21737,21758,21771],{"__ignoreMap":101},[105,21665,21666,21668,21671,21673,21675,21677,21679,21681],{"class":107,"line":108},[105,21667,568],{"class":111},[105,21669,21670],{"class":129}," es",[105,21672,574],{"class":111},[105,21674,3496],{"class":111},[105,21676,21230],{"class":241},[105,21678,321],{"class":115},[105,21680,21235],{"class":248},[105,21682,327],{"class":115},[105,21684,21685],{"class":107,"line":119},[105,21686,307],{"emptyLinePlaceholder":306},[105,21688,21689],{"class":107,"line":133},[105,21690,21691],{"class":630},"\u002F\u002F 监听默认消息\n",[105,21693,21694,21697,21699,21701,21703,21705,21707,21709],{"class":107,"line":148},[105,21695,21696],{"class":115},"es.",[105,21698,5122],{"class":241},[105,21700,574],{"class":111},[105,21702,4379],{"class":115},[105,21704,3315],{"class":122},[105,21706,2854],{"class":115},[105,21708,723],{"class":111},[105,21710,116],{"class":115},[105,21712,21713,21715,21717,21719,21722],{"class":107,"line":163},[105,21714,6680],{"class":115},[105,21716,639],{"class":241},[105,21718,321],{"class":115},[105,21720,21721],{"class":248},"'默认消息:'",[105,21723,7136],{"class":115},[105,21725,21726],{"class":107,"line":179},[105,21727,4459],{"class":115},[105,21729,21730],{"class":107,"line":194},[105,21731,307],{"emptyLinePlaceholder":306},[105,21733,21734],{"class":107,"line":209},[105,21735,21736],{"class":630},"\u002F\u002F 监听 heartbeat 自定义事件\n",[105,21738,21739,21741,21743,21745,21748,21750,21752,21754,21756],{"class":107,"line":526},[105,21740,21696],{"class":115},[105,21742,3304],{"class":241},[105,21744,321],{"class":115},[105,21746,21747],{"class":248},"'heartbeat'",[105,21749,3312],{"class":115},[105,21751,3315],{"class":122},[105,21753,2854],{"class":115},[105,21755,723],{"class":111},[105,21757,116],{"class":115},[105,21759,21760,21762,21764,21766,21769],{"class":107,"line":531},[105,21761,6680],{"class":115},[105,21763,639],{"class":241},[105,21765,321],{"class":115},[105,21767,21768],{"class":248},"'心跳事件:'",[105,21770,7136],{"class":115},[105,21772,21773],{"class":107,"line":668},[105,21774,3982],{"class":115},[10,21776,21777],{"id":21777},"跨端和高性能",[14,21779,21780],{"id":21780},"electron",[18,21782,21783],{},"Electron 的 IPC是它的核心架构，主进程是一个node环境，用于管理窗口、声明周期、系统api，能够访问完整的nodejs api，比如说fs、path、child_process，不能直接操作dom，渲染进程是chromium环境，每个窗口一个，能够操作dom、跑前端代码，默认不能访问nodejs。",[18,21785,21786],{},"每个渲染进程在启动前，会加载一个预加载脚本，我们一般叫做preload.js，用于定义能访问的部分nodejs api，是主进程和渲染进程的bridge。",[18,21788,21789],{},"它提供的有三种模式，分别是渲染进程->主进程，有invoke和handle，渲染进程请求、主进程响应：",[96,21791,21793],{"className":559,"code":21792,"language":561,"meta":101,"style":101},"\u002F\u002F 主进程\nconst { ipcMain } = require('electron');\n\nipcMain.handle('read-file', async (event, filePath) => {\n    const content = await fs.promises.readFile(filePath, 'utf-8');\n    return content;   \u002F\u002F 返回给渲染进程\n});\n\n\u002F\u002F 预加载脚本\nconst { contextBridge, ipcRenderer } = require('electron');\n\ncontextBridge.exposeInMainWorld('api', {\n    readFile: (path) => ipcRenderer.invoke('read-file', path)\n});\n\n\u002F\u002F 渲染进程\nconst content = await window.api.readFile('\u002Fpath\u002Fto\u002Ffile');\n",[24,21794,21795,21800,21823,21827,21859,21883,21893,21897,21901,21906,21932,21936,21951,21978,21982,21986,21991],{"__ignoreMap":101},[105,21796,21797],{"class":107,"line":108},[105,21798,21799],{"class":630},"\u002F\u002F 主进程\n",[105,21801,21802,21804,21806,21809,21811,21813,21816,21818,21821],{"class":107,"line":119},[105,21803,568],{"class":111},[105,21805,726],{"class":115},[105,21807,21808],{"class":129},"ipcMain",[105,21810,3973],{"class":115},[105,21812,245],{"class":111},[105,21814,21815],{"class":241}," require",[105,21817,321],{"class":115},[105,21819,21820],{"class":248},"'electron'",[105,21822,327],{"class":115},[105,21824,21825],{"class":107,"line":133},[105,21826,307],{"emptyLinePlaceholder":306},[105,21828,21829,21832,21835,21837,21840,21842,21844,21846,21848,21850,21853,21855,21857],{"class":107,"line":148},[105,21830,21831],{"class":115},"ipcMain.",[105,21833,21834],{"class":241},"handle",[105,21836,321],{"class":115},[105,21838,21839],{"class":248},"'read-file'",[105,21841,286],{"class":115},[105,21843,9239],{"class":111},[105,21845,4379],{"class":115},[105,21847,5610],{"class":122},[105,21849,286],{"class":115},[105,21851,21852],{"class":122},"filePath",[105,21854,2854],{"class":115},[105,21856,723],{"class":111},[105,21858,116],{"class":115},[105,21860,21861,21863,21866,21868,21870,21873,21875,21878,21881],{"class":107,"line":163},[105,21862,7238],{"class":111},[105,21864,21865],{"class":129}," content",[105,21867,574],{"class":111},[105,21869,5428],{"class":111},[105,21871,21872],{"class":115}," fs.promises.",[105,21874,9202],{"class":241},[105,21876,21877],{"class":115},"(filePath, ",[105,21879,21880],{"class":248},"'utf-8'",[105,21882,327],{"class":115},[105,21884,21885,21887,21890],{"class":107,"line":179},[105,21886,10680],{"class":111},[105,21888,21889],{"class":115}," content;   ",[105,21891,21892],{"class":630},"\u002F\u002F 返回给渲染进程\n",[105,21894,21895],{"class":107,"line":194},[105,21896,3982],{"class":115},[105,21898,21899],{"class":107,"line":209},[105,21900,307],{"emptyLinePlaceholder":306},[105,21902,21903],{"class":107,"line":526},[105,21904,21905],{"class":630},"\u002F\u002F 预加载脚本\n",[105,21907,21908,21910,21912,21915,21917,21920,21922,21924,21926,21928,21930],{"class":107,"line":531},[105,21909,568],{"class":111},[105,21911,726],{"class":115},[105,21913,21914],{"class":129},"contextBridge",[105,21916,286],{"class":115},[105,21918,21919],{"class":129},"ipcRenderer",[105,21921,3973],{"class":115},[105,21923,245],{"class":111},[105,21925,21815],{"class":241},[105,21927,321],{"class":115},[105,21929,21820],{"class":248},[105,21931,327],{"class":115},[105,21933,21934],{"class":107,"line":668},[105,21935,307],{"emptyLinePlaceholder":306},[105,21937,21938,21941,21944,21946,21949],{"class":107,"line":673},[105,21939,21940],{"class":115},"contextBridge.",[105,21942,21943],{"class":241},"exposeInMainWorld",[105,21945,321],{"class":115},[105,21947,21948],{"class":248},"'api'",[105,21950,2817],{"class":115},[105,21952,21953,21956,21958,21961,21963,21965,21968,21971,21973,21975],{"class":107,"line":679},[105,21954,21955],{"class":241},"    readFile",[105,21957,2848],{"class":115},[105,21959,21960],{"class":122},"path",[105,21962,2854],{"class":115},[105,21964,723],{"class":111},[105,21966,21967],{"class":115}," ipcRenderer.",[105,21969,21970],{"class":241},"invoke",[105,21972,321],{"class":115},[105,21974,21839],{"class":248},[105,21976,21977],{"class":115},", path)\n",[105,21979,21980],{"class":107,"line":689},[105,21981,3982],{"class":115},[105,21983,21984],{"class":107,"line":699},[105,21985,307],{"emptyLinePlaceholder":306},[105,21987,21988],{"class":107,"line":704},[105,21989,21990],{"class":630},"\u002F\u002F 渲染进程\n",[105,21992,21993,21995,21997,21999,22001,22004,22006,22008,22011],{"class":107,"line":710},[105,21994,568],{"class":111},[105,21996,21865],{"class":129},[105,21998,574],{"class":111},[105,22000,5428],{"class":111},[105,22002,22003],{"class":115}," window.api.",[105,22005,9202],{"class":241},[105,22007,321],{"class":115},[105,22009,22010],{"class":248},"'\u002Fpath\u002Fto\u002Ffile'",[105,22012,327],{"class":115},[18,22014,22015],{},"它的特点是promise风格，异步有返回值。",[18,22017,22018],{},"另一种是渲染进程->主进程，使用send和on完成，是单向的，渲染进程发，主进程收，但是不返回。",[96,22020,22022],{"className":559,"code":22021,"language":561,"meta":101,"style":101},"\u002F\u002F 主进程\nipcMain.on('log', (event, message) => {\n    console.log('渲染进程说:', message);\n    \u002F\u002F 不返回\n});\n\n\u002F\u002F 预加载\ncontextBridge.exposeInMainWorld('api', {\n    log: (msg) => ipcRenderer.send('log', msg)\n});\n\n\u002F\u002F 渲染进程\nwindow.api.log('hello');\n",[24,22023,22024,22028,22055,22069,22074,22078,22082,22087,22099,22125,22129,22133,22137],{"__ignoreMap":101},[105,22025,22026],{"class":107,"line":108},[105,22027,21799],{"class":630},[105,22029,22030,22032,22035,22037,22040,22042,22044,22046,22049,22051,22053],{"class":107,"line":119},[105,22031,21831],{"class":115},[105,22033,22034],{"class":241},"on",[105,22036,321],{"class":115},[105,22038,22039],{"class":248},"'log'",[105,22041,3312],{"class":115},[105,22043,5610],{"class":122},[105,22045,286],{"class":115},[105,22047,22048],{"class":122},"message",[105,22050,2854],{"class":115},[105,22052,723],{"class":111},[105,22054,116],{"class":115},[105,22056,22057,22059,22061,22063,22066],{"class":107,"line":133},[105,22058,6680],{"class":115},[105,22060,639],{"class":241},[105,22062,321],{"class":115},[105,22064,22065],{"class":248},"'渲染进程说:'",[105,22067,22068],{"class":115},", message);\n",[105,22070,22071],{"class":107,"line":148},[105,22072,22073],{"class":630},"    \u002F\u002F 不返回\n",[105,22075,22076],{"class":107,"line":163},[105,22077,3982],{"class":115},[105,22079,22080],{"class":107,"line":179},[105,22081,307],{"emptyLinePlaceholder":306},[105,22083,22084],{"class":107,"line":194},[105,22085,22086],{"class":630},"\u002F\u002F 预加载\n",[105,22088,22089,22091,22093,22095,22097],{"class":107,"line":209},[105,22090,21940],{"class":115},[105,22092,21943],{"class":241},[105,22094,321],{"class":115},[105,22096,21948],{"class":248},[105,22098,2817],{"class":115},[105,22100,22101,22104,22106,22109,22111,22113,22115,22118,22120,22122],{"class":107,"line":526},[105,22102,22103],{"class":241},"    log",[105,22105,2848],{"class":115},[105,22107,22108],{"class":122},"msg",[105,22110,2854],{"class":115},[105,22112,723],{"class":111},[105,22114,21967],{"class":115},[105,22116,22117],{"class":241},"send",[105,22119,321],{"class":115},[105,22121,22039],{"class":248},[105,22123,22124],{"class":115},", msg)\n",[105,22126,22127],{"class":107,"line":531},[105,22128,3982],{"class":115},[105,22130,22131],{"class":107,"line":668},[105,22132,307],{"emptyLinePlaceholder":306},[105,22134,22135],{"class":107,"line":673},[105,22136,21990],{"class":630},[105,22138,22139,22142,22144,22146,22148],{"class":107,"line":679},[105,22140,22141],{"class":115},"window.api.",[105,22143,639],{"class":241},[105,22145,321],{"class":115},[105,22147,3280],{"class":248},[105,22149,327],{"class":115},[18,22151,22152],{},"主进程单向到渲染进程：",[96,22154,22156],{"className":559,"code":22155,"language":561,"meta":101,"style":101},"\u002F\u002F 主进程\nmainWindow.webContents.send('update', { version: '1.0.0' });\n\u002F\u002F 预加载\ncontextBridge.exposeInMainWorld('api', {\n    onUpdate: (callback) => ipcRenderer.on('update', (event, data) => callback(data))\n});\n\u002F\u002F 渲染进程\nwindow.api.onUpdate((data) => {\n    console.log('收到更新:', data);\n});\n",[24,22157,22158,22162,22181,22185,22197,22237,22241,22245,22262,22276],{"__ignoreMap":101},[105,22159,22160],{"class":107,"line":108},[105,22161,21799],{"class":630},[105,22163,22164,22167,22169,22171,22173,22176,22179],{"class":107,"line":119},[105,22165,22166],{"class":115},"mainWindow.webContents.",[105,22168,22117],{"class":241},[105,22170,321],{"class":115},[105,22172,21302],{"class":248},[105,22174,22175],{"class":115},", { version: ",[105,22177,22178],{"class":248},"'1.0.0'",[105,22180,17577],{"class":115},[105,22182,22183],{"class":107,"line":133},[105,22184,22086],{"class":630},[105,22186,22187,22189,22191,22193,22195],{"class":107,"line":148},[105,22188,21940],{"class":115},[105,22190,21943],{"class":241},[105,22192,321],{"class":115},[105,22194,21948],{"class":248},[105,22196,2817],{"class":115},[105,22198,22199,22202,22204,22207,22209,22211,22213,22215,22217,22219,22221,22223,22225,22227,22229,22231,22234],{"class":107,"line":163},[105,22200,22201],{"class":241},"    onUpdate",[105,22203,2848],{"class":115},[105,22205,22206],{"class":122},"callback",[105,22208,2854],{"class":115},[105,22210,723],{"class":111},[105,22212,21967],{"class":115},[105,22214,22034],{"class":241},[105,22216,321],{"class":115},[105,22218,21302],{"class":248},[105,22220,3312],{"class":115},[105,22222,5610],{"class":122},[105,22224,286],{"class":115},[105,22226,5070],{"class":122},[105,22228,2854],{"class":115},[105,22230,723],{"class":111},[105,22232,22233],{"class":241}," callback",[105,22235,22236],{"class":115},"(data))\n",[105,22238,22239],{"class":107,"line":179},[105,22240,3982],{"class":115},[105,22242,22243],{"class":107,"line":194},[105,22244,21990],{"class":630},[105,22246,22247,22249,22252,22254,22256,22258,22260],{"class":107,"line":209},[105,22248,22141],{"class":115},[105,22250,22251],{"class":241},"onUpdate",[105,22253,7298],{"class":115},[105,22255,5070],{"class":122},[105,22257,2854],{"class":115},[105,22259,723],{"class":111},[105,22261,116],{"class":115},[105,22263,22264,22266,22268,22270,22273],{"class":107,"line":526},[105,22265,6680],{"class":115},[105,22267,639],{"class":241},[105,22269,321],{"class":115},[105,22271,22272],{"class":248},"'收到更新:'",[105,22274,22275],{"class":115},", data);\n",[105,22277,22278],{"class":107,"line":531},[105,22279,3982],{"class":115},[18,22281,22282],{},"ipc的底层是chromium的mojo ipc，mojo是chromium的ipc框架，管理二进制序列化、异步、跨进程，electron封装了mojo，能够提供ipcRenderer、ipcMain。",[18,22284,22285,22286,22289,22290,22293],{},"mojo在win32平台的底层传输是基于命名管道实现的，函数在 Windows 下会调用 ",[24,22287,22288],{},"CreateNamedPipeW"," 来创建管道。一个专门抓取 Chromium IPC 的工具也明确指出，它捕获的就是 ",[21,22291,22292],{},"Named Pipe"," 的流量",[18,22295,22296,22297,22300,22301,22304],{},"值得一提的是，从 Chrome 112 开始，Chromium 逐渐用新的 ",[21,22298,22299],{},"ipcz"," 协议替代了 Mojo Core。ipcz 为了优化性能，在 Windows 上可能会更多地使用",[21,22302,22303],{},"共享内存","来传递数据，而不是完全依赖命名管道，但这属于更底层的优化实现。",[18,22306,22307],{},"安全方面：",[18,22309,22310],{},"contextIsolation是默认开启的，渲染进程和预加载脚本上下文隔离。nodeInteration也是默认关闭的，渲染进程肯定不能访问node。sandbox沙箱默认开启，渲染进程在沙箱里。webSecurit默认开启，同源策略。",[18,22312,22313],{},"生命周期方面：",[18,22315,22316],{},"app.whenReady()是app初始化完成的时候，创建窗口。\nwindow-all-closed是所有窗口都关闭，macos不退出。\nbefore-quit: 退出前，可以拦截\nwill-quit:即将推出\nquit:已退出\nready:app就绪\nsecond-instance:第二个实例启动",[18,22318,22319],{},"窗口管理：",[18,22321,22322,22323,31,22325],{},"BrowserWindow:创建窗口\nwebPreferences:安全配置preload、contextisloation、nodeintergration\nloadURL\u002FloadFile：加载远程URL\u002F本地文件\nready-to-show:窗口准备好显示，避免白屏\nframe:false:无边框窗口，自定义标题栏\nshow:false + read-to-show 先隐藏，准备好在显示，可以消除白屏\ntransparent:true透明窗口\nalwaysOnTop：置顶\nwebContents:窗口的web内容，可以",[24,22324,22117],{},[24,22326,22327],{},"openDevTools",[18,22329,22330,22331,31,22333,22335],{},"preload脚本是渲染进程启动前，DOM加载前执行，能够访问",[24,22332,21914],{},[24,22334,21919],{},"、部分 Node API。",[14,22337,22339],{"id":22338},"emscripten","Emscripten",[18,22341,22342],{},"emscripten可以把c\u002Fc++代码翻译为浏览器和nodejs能直接运行的wasm模块，并附带一个js胶水文件。",[18,22344,22345],{},"emscripten的核心工具是emcc，内部串联了多个工具，分别是前端clang\u002Fllvm，可以将我们的c\u002Fc++代码编译为llvm的ir码，目标是WebAssembly平台。后端llvm wasm backend将llvm ir转换为WebAssembly目标文件.o或者.a。最后链接和优化，wasm-ld链接器将所有目标文件打包成一个.wasm二进制模块，之后Binaryen工具链会对这个模块进行优化，让他的体积更小，运行更快。emcc会生成一个js胶水文件，作用是加载、编译、实例化.wasm文件，并处理c\u002Fc++与js之间的所有通信，比如内存分配，函数调用和文件系统模拟。",[18,22347,22348,22349],{},"C\u002FC++ 代码常需要读写文件，但浏览器出于安全考虑，不允许 JS 直接访问本地文件。Emscripten 的解决方案是",[21,22350,22351],{},"虚拟文件系统",[18,22353,22354,22355,22358,22359,31,22362,22365],{},"胶水层代码是自动生成，但什么一些情况下需要手动补充js：\n比如说调用导出函数，我们在前端要自己调用；字符串和数组之间需要转换，c的 ",[24,22356,22357],{},"char*","和js的string不互通，因此需要手动转换，生成的js提供了 ",[24,22360,22361],{},"stringToNewUTF8",[24,22363,22364],{},"UTF8ToString"," 这些工具，你负责组合；然后C要调js函数比如事件回调，你需要在C侧写",[96,22367,22370],{"className":22368,"code":22369,"language":392},[390],"\u002F\u002F C 侧\ntypedef void (*Callback)(int);\nvoid set_callback(Callback cb);\n\n\u002F\u002F JS 侧\nconst callback = Module.addFunction((value) => {\n    console.log('C 调用了 JS:', value);\n}, 'vi');\nModule._set_callback(callback);\n",[24,22371,22369],{"__ignoreMap":101},[18,22373,22374],{},"所以我们一般分为core、common、platform的实现，core是平台无关核心实现，然后common是包装c abi，最后platform是平台适配。",[10,22376,22377],{"id":22377},"认证和授权",[14,22379,22381],{"id":22380},"oauth","OAuth",[18,22383,22384],{},"Oauth2.0是一套授权框架，目标是解决第三方应用如何在不获取用户密码的前提下，代表用户去访问受保护的资源。oauth体系里面，有四个角色，分别是资源所有者、客户端、授权服务器、资源服务器。",[18,22386,22387],{},"资源所有者一般就是用户，是数据拥有者，有权决定是否授权，客户端是想要访问用户数据的第三方应用，比如有个工具想要读取你gayhub仓库列表的项目管理。授权服务器用于验证身份、征得同意、颁发令牌，gayhub也有oauth登录服务。资源服务用于存储受保护的api服务，如github有restapi，需要令牌，不认密码。",[18,22389,22390],{},"然后是授权模式，首先是授权码模式，这个是最安全、最常用的模式。用户需要发请请求，比如阿紫客户端点击使用github登录，重定向到授权服务器中，客户端把用户的浏览器重定向到github的授权服务器，请求用户授权。用户需要登录并授权，同意授权给客户端相应的权限。完成之后授权服务器把浏览器重定向会客户端，并在URL上带上一个授权码，客户端在后台用授权码+客户端秘钥向授权服务器换取访问令牌，客户端就可以通过访问令牌去资源服务器上请求用户的数据了。",[18,22392,22393],{},"隐式模式不推荐，它是令牌慧姐通过URL返回，容易泄漏，无法刷新。密码模式已禁止，用户需要把密码直接给客户端，从根本上违背了OAuth的设计。",[18,22395,22396],{},"客户端机密模式是服务间调用，用户无参与，客户端会以自己的身份获取令牌，用于访问自己的资源，不涉及用户。",[18,22398,22399],{},"OAuth本身留下了很多安全细节，最新实践RFC 9700明确表示强制使用PKCE，所有的客户端类型，尤其是公开客户端SPA 移动APP都需要使用PKCE来防止授权码被拦截。授权服务器必须对重定向uri进行精确的字符串匹配，不能使用通配符或者模式匹配，防止授权码被重定向到恶意地址。",[18,22401,22402,22403,22406,22407,22410,22411,22414],{},"注意： OAuth只负责授权，不负责认证，使用Google登录这种选项实际上是OAuth2.0 + OIDC的组合。",[21,22404,22405],{},"OIDC"," 是 OAuth 2.0 之上的一个",[21,22408,22409],{},"身份层","，它额外提供了一个 ",[21,22412,22413],{},"ID 令牌 (ID Token)"," ，这是一个包含用户身份信息（如姓名、邮箱）的 JWT，用来证明用户身份，之后会讲。",[18,22416,22417],{},"为什么访问资源需要客户端秘钥，这个是为了证明客户端身份，因为授权服务器需要确认客户端是否真的是我们自己规定的客户端，如果说授权码被拦截也无所谓，有了客户端秘钥才能换令牌。",[14,22419,22405],{"id":22420},"oidc",[18,22422,22423,22424,22427],{},"然后我们来谈谈oidc部分，它是在oauth2之上的身份认证层，核心目标是让客户端能验证用户，它在 OAuth 2.0 的授权流程上，额外返回一个 ",[21,22425,22426],{},"ID Token","（JWT）。",[18,22429,22430],{},"因为oauth2只会给access_token，也就是用于访问资源的访问令牌，oidc额外返回了id_token，id_token是jwt，能够包含用户信息，客户端解 id_token。然后客户端是可以验证id token里面的签名的，需要使用JWKS，我们可以通过一个get请求，去访问公钥集合：",[96,22432,22435],{"className":22433,"code":22434,"language":392},[390],"GET https:\u002F\u002Fauth.example.com\u002F.well-known\u002Fjwks.json\n\n{\n    \"keys\": [\n        {\n            \"kty\": \"RSA\",\n            \"kid\": \"key-1\",\n            \"use\": \"sig\",\n            \"n\": \"...\",\n            \"e\": \"AQAB\"\n        }\n    ]\n}\n",[24,22436,22434],{"__ignoreMap":101},[18,22438,22439,22440,9847],{},"并且scope里面必须包含openid，不然就不是oidc。",[21,22441,22442],{},"OIDC 定义了标准 scope",[835,22444,22445,22457],{},[838,22446,22447],{},[841,22448,22449,22452,22454],{},[844,22450,22451],{},"scope",[844,22453,1169],{},[844,22455,22456],{},"返回的 claim",[854,22458,22459,22477,22498,22515,22529],{},[841,22460,22461,22466,22472],{},[859,22462,22463],{},[24,22464,22465],{},"openid",[859,22467,22468,22471],{},[21,22469,22470],{},"必须","，表示 OIDC",[859,22473,22474],{},[24,22475,22476],{},"sub",[841,22478,22479,22484,22487],{},[859,22480,22481],{},[24,22482,22483],{},"profile",[859,22485,22486],{},"基本资料",[859,22488,22489,31,22491,31,22494,22497],{},[24,22490,5014],{},[24,22492,22493],{},"family_name",[24,22495,22496],{},"picture"," 等",[841,22499,22500,22505,22508],{},[859,22501,22502],{},[24,22503,22504],{},"email",[859,22506,22507],{},"邮箱",[859,22509,22510,31,22512],{},[24,22511,22504],{},[24,22513,22514],{},"email_verified",[841,22516,22517,22522,22525],{},[859,22518,22519],{},[24,22520,22521],{},"address",[859,22523,22524],{},"地址",[859,22526,22527],{},[24,22528,22521],{},[841,22530,22531,22536,22539],{},[859,22532,22533],{},[24,22534,22535],{},"phone",[859,22537,22538],{},"电话",[859,22540,22541,31,22544],{},[24,22542,22543],{},"phone_number",[24,22545,22546],{},"phone_number_verified",[18,22548,22549],{},"oidc的使用场景：",[18,22551,22552],{},"SSO 单点登录，用户登录一次，可以多个应用共享身份，每个应用用oidc来验证用户。比如企业sso，一个idp，多个应用。",[18,22554,22555],{},"社交登录：使用google登录，是使用oauth2.0+oidc，拿id_token->验证用户。",[18,22557,22558],{},"移动App：调用系统浏览器访问授权服务器返回id_token。然后客户端是可以验证id",[10,22560,22561],{"id":22561},"工程",[14,22563,22564],{"id":22564},"微前端",[18,22566,22567],{},"这是一种将大型前端应用拆分成多个小型、独立、可独立开发和部署的子应用的架构模式，核心理念借鉴了后端微服务，可以把一个庞大的单体前端，拆分为多个可以独立开发、测试、部署的小块，最后在运行时或者构建的时候组合为一个完整应用。",[18,22569,22570],{},"这样的优点很明显，子应用独立构建，互不影响。多团队协作，技术栈可以不同，但是技术栈升级困难，发布风险高，耦合严重。",[18,22572,22573],{},"子应用会被作为npm包引入，构建的时候打包在一起。实现方案也有很多种，比如可以用iframe，每个子应用一个，简单但是通信、样式、路由受限。",[18,22575,22576],{},"可以用web component，自定义元素封装子应用，元素隔离，但是生态兼容性有限。",[18,22578,22579],{},"webpack5支持module federation，构建的时候+运行时结合，生态比较成熟。",[18,22581,22582],{},"qiankun是一个微前端实现库，能够无痛构建一个生产可用的微前端框架系统，qiankun原子蚂蚁金融科技基于微前端架构的云产品统一接入平台，现在已经非常完备了。qiankun 对于用户而言只是一个类似 jQuery 的库，你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计，使得微应用的接入像使用 iframe 一样简单。",[3633,22584,22585],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}",{"title":101,"searchDepth":119,"depth":119,"links":22587},[22588,22593,22596,22603,22614,22635,22640,22651,22655,22659],{"id":12,"depth":119,"text":12,"children":22589},[22590,22591,22592],{"id":16,"depth":133,"text":16},{"id":53,"depth":133,"text":53},{"id":100,"depth":133,"text":830},{"id":2775,"depth":119,"text":2776,"children":22594},[22595],{"id":2931,"depth":133,"text":2932},{"id":227,"depth":119,"text":3030,"children":22597},[22598,22599,22600,22601,22602],{"id":3033,"depth":133,"text":3034},{"id":3147,"depth":133,"text":3147},{"id":3360,"depth":133,"text":3361},{"id":3505,"depth":133,"text":3506},{"id":3652,"depth":133,"text":3652},{"id":4950,"depth":119,"text":4950,"children":22604},[22605,22606,22607,22608,22609,22610,22611,22612,22613],{"id":5316,"depth":133,"text":5317},{"id":5446,"depth":133,"text":5447},{"id":7007,"depth":133,"text":7008},{"id":7590,"depth":133,"text":6741},{"id":7629,"depth":133,"text":7630},{"id":7642,"depth":133,"text":7643},{"id":7655,"depth":133,"text":7655},{"id":7742,"depth":133,"text":7742},{"id":7802,"depth":133,"text":7802},{"id":7820,"depth":119,"text":7821,"children":22615},[22616,22617,22618,22619,22620,22621,22622,22623,22624,22625,22626,22627,22628,22629,22630,22631,22632,22633,22634],{"id":7824,"depth":133,"text":7825},{"id":8047,"depth":133,"text":8047},{"id":8526,"depth":133,"text":8526},{"id":9164,"depth":133,"text":9164},{"id":9689,"depth":133,"text":9690},{"id":10138,"depth":133,"text":10138},{"id":10369,"depth":133,"text":10369},{"id":10388,"depth":133,"text":10388},{"id":8263,"depth":133,"text":8263},{"id":11298,"depth":133,"text":11298},{"id":11492,"depth":133,"text":11492},{"id":13900,"depth":133,"text":13900},{"id":14492,"depth":133,"text":14492},{"id":15260,"depth":133,"text":15260},{"id":15266,"depth":133,"text":15266},{"id":10033,"depth":133,"text":10033},{"id":17853,"depth":133,"text":17854},{"id":17913,"depth":133,"text":17914},{"id":18068,"depth":133,"text":18069},{"id":2965,"depth":119,"text":2965,"children":22636},[22637,22638,22639],{"id":19106,"depth":133,"text":19106},{"id":19971,"depth":133,"text":19971},{"id":20512,"depth":133,"text":20513},{"id":20534,"depth":119,"text":20534,"children":22641},[22642,22643,22644,22645,22646,22647,22648,22649,22650],{"id":20537,"depth":133,"text":20538},{"id":20682,"depth":133,"text":20682},{"id":20797,"depth":133,"text":20797},{"id":20952,"depth":133,"text":20952},{"id":21009,"depth":133,"text":21009},{"id":21061,"depth":133,"text":21061},{"id":21104,"depth":133,"text":21104},{"id":21160,"depth":133,"text":21161},{"id":21201,"depth":133,"text":21202},{"id":21777,"depth":119,"text":21777,"children":22652},[22653,22654],{"id":21780,"depth":133,"text":21780},{"id":22338,"depth":133,"text":22339},{"id":22377,"depth":119,"text":22377,"children":22656},[22657,22658],{"id":22380,"depth":133,"text":22381},{"id":22420,"depth":133,"text":22405},{"id":22561,"depth":119,"text":22561,"children":22660},[22661],{"id":22564,"depth":133,"text":22564},"md",{},"\u002Frambling\u002Fweb面试笔记",{"description":101},"rambling\u002Fweb面试笔记","0urHupTava-FtbqmKizEWs3xi7RbjLxy6U7vPO0G22M",1791740440725]