[{"data":1,"prerenderedAt":2665},["ShallowReactive",2],{"page-\u002Fbrowser\u002Fchrome-edge类的浏览器相关技术面试总结":3},{"id":4,"title":5,"body":6,"description":12,"extension":2656,"meta":2657,"navigation":111,"path":2661,"seo":2662,"stem":2663,"__hash__":2664},"content\u002Fbrowser\u002Fchrome-edge类的浏览器相关技术面试总结.md","Chrome Edge类的浏览器相关技术面试总结",{"type":7,"value":8,"toc":2645},"minimark",[9,13,18,21,24,37,40,43,54,171,175,178,181,184,191,194,197,205,208,211,318,321,465,472,609,619,666,669,771,774,1013,1016,1168,1171,1384,1387,1464,1467,1494,1500,1518,1529,1800,1804,1807,1814,2182,2187,2327,2330,2333,2413,2416,2419,2422,2428,2431,2437,2440,2446,2449,2455,2459,2462,2465,2468,2472,2478,2481,2484,2487,2490,2493,2496,2513,2516,2522,2525,2531,2542,2545,2548,2551,2557,2563,2566,2569,2572,2578,2603,2609,2620,2623,2626,2629,2632,2635,2641],[10,11,12],"p",{},"我这里整理了面试中可能会考到的浏览器的部分知识和理论，后续会更新一些新的知识上去",[14,15,17],"h3",{"id":16},"localstorage加密邪教技术","localStorage加密邪教技术",[10,19,20],{},"cookie和LocalStorage是web开发中最常用的两个存储方式，cookie主要保存在内存中，生命周期和浏览器相关，浏览器关闭之后直接消失（会话cookie），持久cookie是保存在硬盘的特定文件里，生命周期是设置的过期时间。",[10,22,23],{},"在Chrome这种浏览器中，持久化cookie存储在用户数据目录里面的cookies SQLITE数据库里，例如 C:\\Users\\用户名\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\cookies",[10,25,26,27,31,32,36],{},"然后看看localStorage，这个始终是存储在硬盘里的独立区域的，Chrome呢，存储在User Data\\Default\\Local Storage\\目录里， ",[28,29,30],"strong",{},"Firefox","：存储在 ",[33,34,35],"code",{},"webappsstore.sqlite"," 文件中。",[10,38,39],{},"cookie的原理是http状态管理，服务器在http响应头里可以加Set-Cookie，浏览器收到后就会保存，每一次向同源服务器发请求的时候浏览器都会自动把Cookie塞进Cookie请求头里，每个Cookie最大4KB，每次请求都会携带，太大会拖慢网络，他有安全属性，HttpOnly让JS读不到，Secure只走HTTPS，SameSite可以控制跨站发送。",[10,41,42],{},"localStorage的原理是Web Storage API，是纯客户端，数据只会在浏览器本地，永远不会自动发给服务器，也是按源来隔离的，提供了一些同步API，可以通过setItem、getItem、removeItem等方法直接读写键值对。容量更大，通常为5MB左右，远大于Cookie的4KB，只存字符串，存对象需要先JSON.stringify，取出来再JSON.parse。",[10,44,45,46,49,50,53],{},"聊完了之前技术，有一个好玩的东西，有些库",[33,47,48],{},"secure-ls","、",[33,51,52],{},"@cyberadityacode\u002Fsecure-ls"," 等可以让我们在localSotrage中存储加密数据，用起来和普通的localStorage差不多，原理是在存入前加密，库会封装setItem和getItem，传入的对象会被AES之类的算法加密成密文，再存进localStorage中，读取的时候自动解密，给我们原始对象。",[55,56,61],"pre",{"className":57,"code":58,"language":59,"meta":60,"style":60},"language-ts shiki shiki-themes github-light github-dark","\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","ts","",[33,62,63,72,106,113,119,141],{"__ignoreMap":60},[64,65,68],"span",{"class":66,"line":67},"line",1,[64,69,71],{"class":70},"sJ8bj","\u002F\u002F 普通 localStorage\n",[64,73,75,79,83,86,90,93,97,100,103],{"class":66,"line":74},2,[64,76,78],{"class":77},"sVt8B","localStorage.",[64,80,82],{"class":81},"sScJk","setItem",[64,84,85],{"class":77},"(",[64,87,89],{"class":88},"sZZnC","'user'",[64,91,92],{"class":77},", ",[64,94,96],{"class":95},"sj4cs","JSON",[64,98,99],{"class":77},".",[64,101,102],{"class":81},"stringify",[64,104,105],{"class":77},"(user))\n",[64,107,109],{"class":66,"line":108},3,[64,110,112],{"emptyLinePlaceholder":111},true,"\n",[64,114,116],{"class":66,"line":115},4,[64,117,118],{"class":70},"\u002F\u002F 加密库的用法（API 类似，但底层自动加解密）\n",[64,120,122,126,129,131,133,135,138],{"class":66,"line":121},5,[64,123,125],{"class":124},"szBVR","await",[64,127,128],{"class":77}," storage.",[64,130,82],{"class":81},[64,132,85],{"class":77},[64,134,89],{"class":88},[64,136,137],{"class":77},", user)  ",[64,139,140],{"class":70},"\u002F\u002F 存进去的是密文\n",[64,142,144,147,150,153,156,158,161,163,165,168],{"class":66,"line":143},6,[64,145,146],{"class":124},"const",[64,148,149],{"class":95}," user",[64,151,152],{"class":124}," =",[64,154,155],{"class":124}," await",[64,157,128],{"class":77},[64,159,160],{"class":81},"getItem",[64,162,85],{"class":77},[64,164,89],{"class":88},[64,166,167],{"class":77},")  ",[64,169,170],{"class":70},"\u002F\u002F 取出来是明文对象\n",[14,172,174],{"id":173},"service-worker","Service Worker",[10,176,177],{},"HTML5的离线存储，也叫做Application Cache，也在已经废弃，继任者叫做Service Worker，ServiceWorker是一个在浏览器后台运行的独立脚本，充当网络请求的代理，可以拦截页面所有请求，并决定是返回缓存资源还是去网络获取，使用分为三步：",[10,179,180],{},"注册、安装于缓存、拦截和响应。",[10,182,183],{},"注册会在js中注册一个service worker的脚本，安装和缓存会在serviceWorker的install事件中，将需要离线使用的核心资源比如html css js加入到缓存，拦截和响应会在fetch事件中，拦截页面的网络请求，可以制定策略，比如说缓存优先，看看有没有资源，没有再去请求网络，或者网络优先，先请求，失败再回退缓存。",[10,185,186,187,190],{},"相比 AppCache，Service Worker 提供了",[28,188,189],{},"更精细、更灵活的控制能力","。你可以精确管理缓存的版本、更新策略，并且它完全通过 JavaScript 编程实现，不再依赖那个容易出错的 manifest 文件。",[10,192,193],{},"service worker不会阻塞主线程，也不能访问dom，靠事件唤醒，空闲时候被终止，能够拦截fetch，再决定怎么响应。注册之后可以长期存在，页面关了也在，必须https或者localhost，只能控制注册路径和子路径。",[10,195,196],{},"有service worker之后可以决定",[55,198,203],{"className":199,"code":201,"language":202},[200],"language-text","页面 → 浏览器 → Service Worker（拦截）→ 决定：\n                    ├─ 用缓存响应\n                    ├─ 走网络\n                    └─ 自定义响应\n","text",[33,204,201],{"__ignoreMap":60},[10,206,207],{},"Service Worker 的生命周期和普通脚本完全不同，分几个阶段：",[10,209,210],{},"首先是注册：",[55,212,216],{"className":213,"code":214,"language":215,"meta":60,"style":60},"language-js shiki shiki-themes github-light github-dark","\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","js",[33,217,218,223,240,256,287,313],{"__ignoreMap":60},[64,219,220],{"class":66,"line":67},[64,221,222],{"class":70},"\u002F\u002F 主线程\n",[64,224,225,228,231,234,237],{"class":66,"line":74},[64,226,227],{"class":124},"if",[64,229,230],{"class":77}," (",[64,232,233],{"class":88},"'serviceWorker'",[64,235,236],{"class":124}," in",[64,238,239],{"class":77}," navigator) {\n",[64,241,242,245,248,250,253],{"class":66,"line":108},[64,243,244],{"class":77},"    navigator.serviceWorker.",[64,246,247],{"class":81},"register",[64,249,85],{"class":77},[64,251,252],{"class":88},"'\u002Fsw.js'",[64,254,255],{"class":77},")\n",[64,257,258,261,264,266,270,273,276,279,281,284],{"class":66,"line":115},[64,259,260],{"class":77},"        .",[64,262,263],{"class":81},"then",[64,265,85],{"class":77},[64,267,269],{"class":268},"s4XuR","reg",[64,271,272],{"class":124}," =>",[64,274,275],{"class":77}," console.",[64,277,278],{"class":81},"log",[64,280,85],{"class":77},[64,282,283],{"class":88},"'注册成功'",[64,285,286],{"class":77},", reg))\n",[64,288,289,291,294,296,299,301,303,305,307,310],{"class":66,"line":121},[64,290,260],{"class":77},[64,292,293],{"class":81},"catch",[64,295,85],{"class":77},[64,297,298],{"class":268},"err",[64,300,272],{"class":124},[64,302,275],{"class":77},[64,304,278],{"class":81},[64,306,85],{"class":77},[64,308,309],{"class":88},"'注册失败'",[64,311,312],{"class":77},", err));\n",[64,314,315],{"class":66,"line":143},[64,316,317],{"class":77},"}\n",[10,319,320],{},"注册的时候文件不会加载，不会立即生效，然后install的时候才会。",[55,322,324],{"className":213,"code":323,"language":215,"meta":60,"style":60},"\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",[33,325,326,331,359,370,397,411,419,427,435,441,447,453,459],{"__ignoreMap":60},[64,327,328],{"class":66,"line":67},[64,329,330],{"class":70},"\u002F\u002F sw.js\n",[64,332,333,336,339,341,344,347,350,353,356],{"class":66,"line":74},[64,334,335],{"class":77},"self.",[64,337,338],{"class":81},"addEventListener",[64,340,85],{"class":77},[64,342,343],{"class":88},"'install'",[64,345,346],{"class":77},", (",[64,348,349],{"class":268},"event",[64,351,352],{"class":77},") ",[64,354,355],{"class":124},"=>",[64,357,358],{"class":77}," {\n",[64,360,361,364,367],{"class":66,"line":108},[64,362,363],{"class":77},"    event.",[64,365,366],{"class":81},"waitUntil",[64,368,369],{"class":77},"(\n",[64,371,372,375,378,380,383,386,388,390,393,395],{"class":66,"line":115},[64,373,374],{"class":77},"        caches.",[64,376,377],{"class":81},"open",[64,379,85],{"class":77},[64,381,382],{"class":88},"'v1'",[64,384,385],{"class":77},").",[64,387,263],{"class":81},[64,389,85],{"class":77},[64,391,392],{"class":268},"cache",[64,394,272],{"class":124},[64,396,358],{"class":77},[64,398,399,402,405,408],{"class":66,"line":121},[64,400,401],{"class":124},"            return",[64,403,404],{"class":77}," cache.",[64,406,407],{"class":81},"addAll",[64,409,410],{"class":77},"([\n",[64,412,413,416],{"class":66,"line":143},[64,414,415],{"class":88},"                '\u002F'",[64,417,418],{"class":77},",\n",[64,420,422,425],{"class":66,"line":421},7,[64,423,424],{"class":88},"                '\u002Findex.html'",[64,426,418],{"class":77},[64,428,430,433],{"class":66,"line":429},8,[64,431,432],{"class":88},"                '\u002Fstyle.css'",[64,434,418],{"class":77},[64,436,438],{"class":66,"line":437},9,[64,439,440],{"class":88},"                '\u002Fapp.js'\n",[64,442,444],{"class":66,"line":443},10,[64,445,446],{"class":77},"            ]);\n",[64,448,450],{"class":66,"line":449},11,[64,451,452],{"class":77},"        })\n",[64,454,456],{"class":66,"line":455},12,[64,457,458],{"class":77},"    );\n",[64,460,462],{"class":66,"line":461},13,[64,463,464],{"class":77},"});\n",[10,466,467,468,471],{},"install事件在sw首次注册的时候或者文件内容变化的时候触发，通常在预缓存静态资源。 ",[33,469,470],{},"event.waitUntil(promise)","让浏览器\"等这个 Promise 完成再算安装成功\"。然后是activate激活阶段：",[55,473,475],{"className":213,"code":474,"language":215,"meta":60,"style":60},"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",[33,476,477,498,506,526,540,566,592,597,601,605],{"__ignoreMap":60},[64,478,479,481,483,485,488,490,492,494,496],{"class":66,"line":67},[64,480,335],{"class":77},[64,482,338],{"class":81},[64,484,85],{"class":77},[64,486,487],{"class":88},"'activate'",[64,489,346],{"class":77},[64,491,349],{"class":268},[64,493,352],{"class":77},[64,495,355],{"class":124},[64,497,358],{"class":77},[64,499,500,502,504],{"class":66,"line":74},[64,501,363],{"class":77},[64,503,366],{"class":81},[64,505,369],{"class":77},[64,507,508,510,513,516,518,520,522,524],{"class":66,"line":108},[64,509,374],{"class":77},[64,511,512],{"class":81},"keys",[64,514,515],{"class":77},"().",[64,517,263],{"class":81},[64,519,85],{"class":77},[64,521,512],{"class":268},[64,523,272],{"class":124},[64,525,358],{"class":77},[64,527,528,530,533,535,538],{"class":66,"line":115},[64,529,401],{"class":124},[64,531,532],{"class":95}," Promise",[64,534,99],{"class":77},[64,536,537],{"class":81},"all",[64,539,369],{"class":77},[64,541,542,545,548,550,553,555,558,561,564],{"class":66,"line":121},[64,543,544],{"class":77},"                keys.",[64,546,547],{"class":81},"filter",[64,549,85],{"class":77},[64,551,552],{"class":268},"k",[64,554,272],{"class":124},[64,556,557],{"class":77}," k ",[64,559,560],{"class":124},"!==",[64,562,563],{"class":88}," 'v1'",[64,565,255],{"class":77},[64,567,568,571,574,576,578,580,583,586,589],{"class":66,"line":143},[64,569,570],{"class":77},"                    .",[64,572,573],{"class":81},"map",[64,575,85],{"class":77},[64,577,552],{"class":268},[64,579,272],{"class":124},[64,581,582],{"class":77}," caches.",[64,584,585],{"class":81},"delete",[64,587,588],{"class":77},"(k))   ",[64,590,591],{"class":70},"\u002F\u002F 清理旧缓存\n",[64,593,594],{"class":66,"line":421},[64,595,596],{"class":77},"            );\n",[64,598,599],{"class":66,"line":429},[64,600,452],{"class":77},[64,602,603],{"class":66,"line":437},[64,604,458],{"class":77},[64,606,607],{"class":66,"line":443},[64,608,464],{"class":77},[10,610,611,614,615,618],{},[33,612,613],{},"activate"," 在安装成功后、SW 正式接管页面前触发。通常在这里",[28,616,617],{},"清理旧版本缓存","。最后是接管：",[55,620,622],{"className":213,"code":621,"language":215,"meta":60,"style":60},"self.addEventListener('activate', (event) => {\n    event.waitUntil(self.clients.claim());   \u002F\u002F 立即接管\n});\n",[33,623,624,644,662],{"__ignoreMap":60},[64,625,626,628,630,632,634,636,638,640,642],{"class":66,"line":67},[64,627,335],{"class":77},[64,629,338],{"class":81},[64,631,85],{"class":77},[64,633,487],{"class":88},[64,635,346],{"class":77},[64,637,349],{"class":268},[64,639,352],{"class":77},[64,641,355],{"class":124},[64,643,358],{"class":77},[64,645,646,648,650,653,656,659],{"class":66,"line":74},[64,647,363],{"class":77},[64,649,366],{"class":81},[64,651,652],{"class":77},"(self.clients.",[64,654,655],{"class":81},"claim",[64,657,658],{"class":77},"());   ",[64,660,661],{"class":70},"\u002F\u002F 立即接管\n",[64,663,664],{"class":66,"line":108},[64,665,464],{"class":77},[10,667,668],{},"默认情况下，sw安装不会立即开始接管已经打开的窗口，需要等待页面刷新，使用这个情况我们可以在这里激活后直接接管。\n之后service worker就可以帮我们来完成一系列工作了，比如说拦截请求：",[55,670,672],{"className":213,"code":671,"language":215,"meta":60,"style":60},"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",[33,673,674,695,704,725,730,744,749,759,763,767],{"__ignoreMap":60},[64,675,676,678,680,682,685,687,689,691,693],{"class":66,"line":67},[64,677,335],{"class":77},[64,679,338],{"class":81},[64,681,85],{"class":77},[64,683,684],{"class":88},"'fetch'",[64,686,346],{"class":77},[64,688,349],{"class":268},[64,690,352],{"class":77},[64,692,355],{"class":124},[64,694,358],{"class":77},[64,696,697,699,702],{"class":66,"line":74},[64,698,363],{"class":77},[64,700,701],{"class":81},"respondWith",[64,703,369],{"class":77},[64,705,706,708,711,714,716,718,721,723],{"class":66,"line":108},[64,707,374],{"class":77},[64,709,710],{"class":81},"match",[64,712,713],{"class":77},"(event.request).",[64,715,263],{"class":81},[64,717,85],{"class":77},[64,719,720],{"class":268},"cached",[64,722,272],{"class":124},[64,724,358],{"class":77},[64,726,727],{"class":66,"line":115},[64,728,729],{"class":70},"            \u002F\u002F 缓存命中 → 用缓存\n",[64,731,732,735,738,741],{"class":66,"line":121},[64,733,734],{"class":124},"            if",[64,736,737],{"class":77}," (cached) ",[64,739,740],{"class":124},"return",[64,742,743],{"class":77}," cached;\n",[64,745,746],{"class":66,"line":143},[64,747,748],{"class":70},"            \u002F\u002F 没命中 → 走网络\n",[64,750,751,753,756],{"class":66,"line":421},[64,752,401],{"class":124},[64,754,755],{"class":81}," fetch",[64,757,758],{"class":77},"(event.request);\n",[64,760,761],{"class":66,"line":429},[64,762,452],{"class":77},[64,764,765],{"class":66,"line":437},[64,766,458],{"class":77},[64,768,769],{"class":66,"line":443},[64,770,464],{"class":77},[10,772,773],{},"我们可以在这里设置一个缓存优先策略\u002F网络优先策略，这个是fetch事件的常见写法，适合需要最新数据但是也要离线兜底的接口。",[55,775,777],{"className":213,"code":776,"language":215,"meta":60,"style":60},"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",[33,778,779,799,807,825,839,843,847,855,859,879,887,895,911,916,936,966,975,981,1003,1008],{"__ignoreMap":60},[64,780,781,783,785,787,789,791,793,795,797],{"class":66,"line":67},[64,782,335],{"class":77},[64,784,338],{"class":81},[64,786,85],{"class":77},[64,788,684],{"class":88},[64,790,346],{"class":77},[64,792,349],{"class":268},[64,794,352],{"class":77},[64,796,355],{"class":124},[64,798,358],{"class":77},[64,800,801,803,805],{"class":66,"line":74},[64,802,363],{"class":77},[64,804,701],{"class":81},[64,806,369],{"class":77},[64,808,809,811,813,815,817,819,821,823],{"class":66,"line":108},[64,810,374],{"class":77},[64,812,710],{"class":81},[64,814,713],{"class":77},[64,816,263],{"class":81},[64,818,85],{"class":77},[64,820,720],{"class":268},[64,822,272],{"class":124},[64,824,358],{"class":77},[64,826,827,829,832,835,837],{"class":66,"line":115},[64,828,401],{"class":124},[64,830,831],{"class":77}," cached ",[64,833,834],{"class":124},"||",[64,836,755],{"class":81},[64,838,758],{"class":77},[64,840,841],{"class":66,"line":121},[64,842,452],{"class":77},[64,844,845],{"class":66,"line":143},[64,846,458],{"class":77},[64,848,849,852],{"class":66,"line":421},[64,850,851],{"class":77},"}); ",[64,853,854],{"class":70},"\u002F\u002F 缓存\n",[64,856,857],{"class":66,"line":429},[64,858,112],{"emptyLinePlaceholder":111},[64,860,861,863,865,867,869,871,873,875,877],{"class":66,"line":437},[64,862,335],{"class":77},[64,864,338],{"class":81},[64,866,85],{"class":77},[64,868,684],{"class":88},[64,870,346],{"class":77},[64,872,349],{"class":268},[64,874,352],{"class":77},[64,876,355],{"class":124},[64,878,358],{"class":77},[64,880,881,883,885],{"class":66,"line":443},[64,882,363],{"class":77},[64,884,701],{"class":81},[64,886,369],{"class":77},[64,888,889,892],{"class":66,"line":449},[64,890,891],{"class":81},"        fetch",[64,893,894],{"class":77},"(event.request)\n",[64,896,897,900,902,904,907,909],{"class":66,"line":455},[64,898,899],{"class":77},"            .",[64,901,263],{"class":81},[64,903,85],{"class":77},[64,905,906],{"class":268},"response",[64,908,272],{"class":124},[64,910,358],{"class":77},[64,912,913],{"class":66,"line":461},[64,914,915],{"class":70},"                \u002F\u002F 网络成功，更新缓存\n",[64,917,919,922,925,927,930,933],{"class":66,"line":918},14,[64,920,921],{"class":124},"                const",[64,923,924],{"class":95}," clone",[64,926,152],{"class":124},[64,928,929],{"class":77}," response.",[64,931,932],{"class":81},"clone",[64,934,935],{"class":77},"();\n",[64,937,939,942,944,946,948,950,952,954,956,958,960,963],{"class":66,"line":938},15,[64,940,941],{"class":77},"                caches.",[64,943,377],{"class":81},[64,945,85],{"class":77},[64,947,382],{"class":88},[64,949,385],{"class":77},[64,951,263],{"class":81},[64,953,85],{"class":77},[64,955,392],{"class":268},[64,957,272],{"class":124},[64,959,404],{"class":77},[64,961,962],{"class":81},"put",[64,964,965],{"class":77},"(event.request, clone));\n",[64,967,969,972],{"class":66,"line":968},16,[64,970,971],{"class":124},"                return",[64,973,974],{"class":77}," response;\n",[64,976,978],{"class":66,"line":977},17,[64,979,980],{"class":77},"            })\n",[64,982,984,986,988,991,993,995,997,1000],{"class":66,"line":983},18,[64,985,899],{"class":77},[64,987,293],{"class":81},[64,989,990],{"class":77},"(() ",[64,992,355],{"class":124},[64,994,582],{"class":77},[64,996,710],{"class":81},[64,998,999],{"class":77},"(event.request))   ",[64,1001,1002],{"class":70},"\u002F\u002F 网络失败用缓存\n",[64,1004,1006],{"class":66,"line":1005},19,[64,1007,458],{"class":77},[64,1009,1011],{"class":66,"line":1010},20,[64,1012,464],{"class":77},[10,1014,1015],{},"如果是能够短暂接受旧数据，但是还是想尽快响应的，场景，可以用缓存优先+后台更新：",[55,1017,1019],{"className":213,"code":1018,"language":215,"meta":60,"style":60},"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",[33,1020,1021,1041,1049,1067,1091,1105,1131,1137,1142,1156,1160,1164],{"__ignoreMap":60},[64,1022,1023,1025,1027,1029,1031,1033,1035,1037,1039],{"class":66,"line":67},[64,1024,335],{"class":77},[64,1026,338],{"class":81},[64,1028,85],{"class":77},[64,1030,684],{"class":88},[64,1032,346],{"class":77},[64,1034,349],{"class":268},[64,1036,352],{"class":77},[64,1038,355],{"class":124},[64,1040,358],{"class":77},[64,1042,1043,1045,1047],{"class":66,"line":74},[64,1044,363],{"class":77},[64,1046,701],{"class":81},[64,1048,369],{"class":77},[64,1050,1051,1053,1055,1057,1059,1061,1063,1065],{"class":66,"line":108},[64,1052,374],{"class":77},[64,1054,710],{"class":81},[64,1056,713],{"class":77},[64,1058,263],{"class":81},[64,1060,85],{"class":77},[64,1062,720],{"class":268},[64,1064,272],{"class":124},[64,1066,358],{"class":77},[64,1068,1069,1072,1075,1077,1079,1081,1083,1085,1087,1089],{"class":66,"line":115},[64,1070,1071],{"class":124},"            const",[64,1073,1074],{"class":95}," fetchPromise",[64,1076,152],{"class":124},[64,1078,755],{"class":81},[64,1080,713],{"class":77},[64,1082,263],{"class":81},[64,1084,85],{"class":77},[64,1086,906],{"class":268},[64,1088,272],{"class":124},[64,1090,358],{"class":77},[64,1092,1093,1095,1097,1099,1101,1103],{"class":66,"line":121},[64,1094,921],{"class":124},[64,1096,924],{"class":95},[64,1098,152],{"class":124},[64,1100,929],{"class":77},[64,1102,932],{"class":81},[64,1104,935],{"class":77},[64,1106,1107,1109,1111,1113,1115,1117,1119,1121,1123,1125,1127,1129],{"class":66,"line":143},[64,1108,941],{"class":77},[64,1110,377],{"class":81},[64,1112,85],{"class":77},[64,1114,382],{"class":88},[64,1116,385],{"class":77},[64,1118,263],{"class":81},[64,1120,85],{"class":77},[64,1122,392],{"class":268},[64,1124,272],{"class":124},[64,1126,404],{"class":77},[64,1128,962],{"class":81},[64,1130,965],{"class":77},[64,1132,1133,1135],{"class":66,"line":421},[64,1134,971],{"class":124},[64,1136,974],{"class":77},[64,1138,1139],{"class":66,"line":429},[64,1140,1141],{"class":77},"            });\n",[64,1143,1144,1146,1148,1150,1153],{"class":66,"line":437},[64,1145,401],{"class":124},[64,1147,831],{"class":77},[64,1149,834],{"class":124},[64,1151,1152],{"class":77}," fetchPromise;   ",[64,1154,1155],{"class":70},"\u002F\u002F 立即返回缓存，后台更新\n",[64,1157,1158],{"class":66,"line":443},[64,1159,452],{"class":77},[64,1161,1162],{"class":66,"line":449},[64,1163,458],{"class":77},[64,1165,1166],{"class":66,"line":455},[64,1167,464],{"class":77},[10,1169,1170],{},"sw里面可以用CacheStorage来管理缓存：",[55,1172,1174],{"className":213,"code":1173,"language":215,"meta":60,"style":60},"\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",[33,1175,1176,1181,1203,1207,1212,1228,1250,1264,1268,1273,1294,1312,1316,1321,1335,1339,1344,1361,1365,1370],{"__ignoreMap":60},[64,1177,1178],{"class":66,"line":67},[64,1179,1180],{"class":70},"\u002F\u002F 打开\u002F创建缓存\n",[64,1182,1183,1185,1188,1190,1192,1194,1196,1198,1200],{"class":66,"line":74},[64,1184,146],{"class":124},[64,1186,1187],{"class":95}," cache",[64,1189,152],{"class":124},[64,1191,155],{"class":124},[64,1193,582],{"class":77},[64,1195,377],{"class":81},[64,1197,85],{"class":77},[64,1199,382],{"class":88},[64,1201,1202],{"class":77},");\n",[64,1204,1205],{"class":66,"line":108},[64,1206,112],{"emptyLinePlaceholder":111},[64,1208,1209],{"class":66,"line":115},[64,1210,1211],{"class":70},"\u002F\u002F 添加\n",[64,1213,1214,1216,1218,1221,1223,1226],{"class":66,"line":121},[64,1215,125],{"class":124},[64,1217,404],{"class":77},[64,1219,1220],{"class":81},"add",[64,1222,85],{"class":77},[64,1224,1225],{"class":88},"'\u002Findex.html'",[64,1227,1202],{"class":77},[64,1229,1230,1232,1234,1236,1239,1242,1244,1247],{"class":66,"line":143},[64,1231,125],{"class":124},[64,1233,404],{"class":77},[64,1235,407],{"class":81},[64,1237,1238],{"class":77},"([",[64,1240,1241],{"class":88},"'\u002Fa.js'",[64,1243,92],{"class":77},[64,1245,1246],{"class":88},"'\u002Fb.css'",[64,1248,1249],{"class":77},"]);\n",[64,1251,1252,1254,1256,1258,1261],{"class":66,"line":421},[64,1253,125],{"class":124},[64,1255,404],{"class":77},[64,1257,962],{"class":81},[64,1259,1260],{"class":77},"(request, response);   ",[64,1262,1263],{"class":70},"\u002F\u002F 手动放入\n",[64,1265,1266],{"class":66,"line":429},[64,1267,112],{"emptyLinePlaceholder":111},[64,1269,1270],{"class":66,"line":437},[64,1271,1272],{"class":70},"\u002F\u002F 查询\n",[64,1274,1275,1277,1280,1282,1284,1286,1288,1290,1292],{"class":66,"line":443},[64,1276,146],{"class":124},[64,1278,1279],{"class":95}," response",[64,1281,152],{"class":124},[64,1283,155],{"class":124},[64,1285,404],{"class":77},[64,1287,710],{"class":81},[64,1289,85],{"class":77},[64,1291,1225],{"class":88},[64,1293,1202],{"class":77},[64,1295,1296,1298,1301,1303,1305,1307,1310],{"class":66,"line":449},[64,1297,146],{"class":124},[64,1299,1300],{"class":95}," responses",[64,1302,152],{"class":124},[64,1304,155],{"class":124},[64,1306,404],{"class":77},[64,1308,1309],{"class":81},"matchAll",[64,1311,935],{"class":77},[64,1313,1314],{"class":66,"line":455},[64,1315,112],{"emptyLinePlaceholder":111},[64,1317,1318],{"class":66,"line":461},[64,1319,1320],{"class":70},"\u002F\u002F 删除\n",[64,1322,1323,1325,1327,1329,1331,1333],{"class":66,"line":918},[64,1324,125],{"class":124},[64,1326,404],{"class":77},[64,1328,585],{"class":81},[64,1330,85],{"class":77},[64,1332,1225],{"class":88},[64,1334,1202],{"class":77},[64,1336,1337],{"class":66,"line":938},[64,1338,112],{"emptyLinePlaceholder":111},[64,1340,1341],{"class":66,"line":968},[64,1342,1343],{"class":70},"\u002F\u002F 列出所有缓存\n",[64,1345,1346,1348,1351,1353,1355,1357,1359],{"class":66,"line":977},[64,1347,146],{"class":124},[64,1349,1350],{"class":95}," keys",[64,1352,152],{"class":124},[64,1354,155],{"class":124},[64,1356,582],{"class":77},[64,1358,512],{"class":81},[64,1360,935],{"class":77},[64,1362,1363],{"class":66,"line":983},[64,1364,112],{"emptyLinePlaceholder":111},[64,1366,1367],{"class":66,"line":1005},[64,1368,1369],{"class":70},"\u002F\u002F 删除整个缓存\n",[64,1371,1372,1374,1376,1378,1380,1382],{"class":66,"line":1010},[64,1373,125],{"class":124},[64,1375,582],{"class":77},[64,1377,585],{"class":81},[64,1379,85],{"class":77},[64,1381,382],{"class":88},[64,1383,1202],{"class":77},[10,1385,1386],{},"sw和页面不能直接通信，需要使用postMessage：",[55,1388,1390],{"className":213,"code":1389,"language":215,"meta":60,"style":60},"\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",[33,1391,1392,1396,1407,1415,1419,1423,1445,1460],{"__ignoreMap":60},[64,1393,1394],{"class":66,"line":67},[64,1395,222],{"class":70},[64,1397,1398,1401,1404],{"class":66,"line":74},[64,1399,1400],{"class":77},"navigator.serviceWorker.controller.",[64,1402,1403],{"class":81},"postMessage",[64,1405,1406],{"class":77},"({\n",[64,1408,1409,1412],{"class":66,"line":108},[64,1410,1411],{"class":77},"    type: ",[64,1413,1414],{"class":88},"'SKIP_WAITING'\n",[64,1416,1417],{"class":66,"line":115},[64,1418,464],{"class":77},[64,1420,1421],{"class":66,"line":121},[64,1422,112],{"emptyLinePlaceholder":111},[64,1424,1425,1428,1430,1432,1435,1437,1439,1441,1443],{"class":66,"line":143},[64,1426,1427],{"class":77},"navigator.serviceWorker.",[64,1429,338],{"class":81},[64,1431,85],{"class":77},[64,1433,1434],{"class":88},"'message'",[64,1436,346],{"class":77},[64,1438,349],{"class":268},[64,1440,352],{"class":77},[64,1442,355],{"class":124},[64,1444,358],{"class":77},[64,1446,1447,1450,1452,1454,1457],{"class":66,"line":421},[64,1448,1449],{"class":77},"    console.",[64,1451,278],{"class":81},[64,1453,85],{"class":77},[64,1455,1456],{"class":88},"'收到 SW 消息'",[64,1458,1459],{"class":77},", event.data);\n",[64,1461,1462],{"class":66,"line":429},[64,1463,464],{"class":77},[10,1465,1466],{},"这个架构设计其实普遍常见，可以看做后端架构里的Worker，比如说Cloudflare Worker或者 Web Workers、后台任务队列：",[1468,1469,1470,1476,1481,1486,1491],"ul",{},[1471,1472,1473],"li",{},[28,1474,1475],{},"独立执行单元",[1471,1477,1478],{},[28,1479,1480],{},"事件驱动",[1471,1482,1483],{},[28,1484,1485],{},"无状态或短生命周期",[1471,1487,1488],{},[28,1489,1490],{},"通过消息通信",[1471,1492,1493],{},"**不直接访问主进程资源",[10,1495,1496,1499],{},[28,1497,1498],{},"Service Worker 完全符合这个模型","。",[10,1501,1502,1503,1506,1507,1510,1511,49,1514,1517],{},"提一嘴，",[33,1504,1505],{},"CacheStorage"," 存在",[28,1508,1509],{},"磁盘","上，和 ",[33,1512,1513],{},"IndexedDB",[33,1515,1516],{},"localStorage"," 一样是持久化存储。",[10,1519,1520,1521,1524,1525,1528],{},"在vite项目中，我们可以使用vue专用sw包装的插件vite-plugin-pwa，它通过一个虚拟模块 ",[33,1522,1523],{},"virtual:pwa-register\u002Fvue","，提供了一个专门给 Vue 3 用的 ",[33,1526,1527],{},"useRegisterSW"," 函数：",[55,1530,1534],{"className":1531,"code":1532,"language":1533,"meta":60,"style":60},"language-vue shiki shiki-themes github-light github-dark","\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","vue",[33,1535,1536,1560,1574,1578,1584,1594,1605,1615,1627,1640,1658,1663,1668,1677,1681,1690,1716,1737,1753,1782,1791],{"__ignoreMap":60},[64,1537,1538,1541,1545,1548,1551,1554,1557],{"class":66,"line":67},[64,1539,1540],{"class":77},"\u003C",[64,1542,1544],{"class":1543},"s9eBZ","script",[64,1546,1547],{"class":81}," setup",[64,1549,1550],{"class":81}," lang",[64,1552,1553],{"class":77},"=",[64,1555,1556],{"class":88},"\"ts\"",[64,1558,1559],{"class":77},">\n",[64,1561,1562,1565,1568,1571],{"class":66,"line":74},[64,1563,1564],{"class":124},"import",[64,1566,1567],{"class":77}," { useRegisterSW } ",[64,1569,1570],{"class":124},"from",[64,1572,1573],{"class":88}," 'virtual:pwa-register\u002Fvue'\n",[64,1575,1576],{"class":66,"line":108},[64,1577,112],{"emptyLinePlaceholder":111},[64,1579,1580,1582],{"class":66,"line":115},[64,1581,146],{"class":124},[64,1583,358],{"class":77},[64,1585,1586,1589,1591],{"class":66,"line":121},[64,1587,1588],{"class":95},"  offlineReady",[64,1590,92],{"class":77},[64,1592,1593],{"class":70},"\u002F\u002F 应用准备好离线使用（Ref\u003Cboolean>）\n",[64,1595,1596,1599,1602],{"class":66,"line":143},[64,1597,1598],{"class":95},"  needRefresh",[64,1600,1601],{"class":77},",  ",[64,1603,1604],{"class":70},"\u002F\u002F 有新版本可用（Ref\u003Cboolean>）\n",[64,1606,1607,1610,1612],{"class":66,"line":421},[64,1608,1609],{"class":95},"  updateServiceWorker",[64,1611,92],{"class":77},[64,1613,1614],{"class":70},"\u002F\u002F 更新 SW 并刷新页面（函数）\n",[64,1616,1617,1620,1622,1625],{"class":66,"line":429},[64,1618,1619],{"class":77},"} ",[64,1621,1553],{"class":124},[64,1623,1624],{"class":81}," useRegisterSW",[64,1626,1406],{"class":77},[64,1628,1629,1632,1634,1637],{"class":66,"line":437},[64,1630,1631],{"class":81},"  onRegisteredSW",[64,1633,85],{"class":77},[64,1635,1636],{"class":268},"swUrl",[64,1638,1639],{"class":77},") {\n",[64,1641,1642,1644,1646,1648,1651,1653,1656],{"class":66,"line":443},[64,1643,1449],{"class":77},[64,1645,278],{"class":81},[64,1647,85],{"class":77},[64,1649,1650],{"class":88},"`SW 已注册: ${",[64,1652,1636],{"class":77},[64,1654,1655],{"class":88},"}`",[64,1657,255],{"class":77},[64,1659,1660],{"class":66,"line":449},[64,1661,1662],{"class":77},"  },\n",[64,1664,1665],{"class":66,"line":455},[64,1666,1667],{"class":77},"})\n",[64,1669,1670,1673,1675],{"class":66,"line":461},[64,1671,1672],{"class":77},"\u003C\u002F",[64,1674,1544],{"class":1543},[64,1676,1559],{"class":77},[64,1678,1679],{"class":66,"line":918},[64,1680,112],{"emptyLinePlaceholder":111},[64,1682,1683,1685,1688],{"class":66,"line":938},[64,1684,1540],{"class":77},[64,1686,1687],{"class":1543},"template",[64,1689,1559],{"class":77},[64,1691,1692,1695,1698,1701,1703,1706,1709,1711,1714],{"class":66,"line":968},[64,1693,1694],{"class":77},"  \u003C",[64,1696,1697],{"class":1543},"div",[64,1699,1700],{"class":81}," v-if",[64,1702,1553],{"class":77},[64,1704,1705],{"class":88},"\"offlineReady || needRefresh\"",[64,1707,1708],{"class":81}," class",[64,1710,1553],{"class":77},[64,1712,1713],{"class":88},"\"pwa-toast\"",[64,1715,1559],{"class":77},[64,1717,1718,1721,1723,1725,1727,1730,1733,1735],{"class":66,"line":977},[64,1719,1720],{"class":77},"    \u003C",[64,1722,64],{"class":1543},[64,1724,1700],{"class":81},[64,1726,1553],{"class":77},[64,1728,1729],{"class":88},"\"offlineReady\"",[64,1731,1732],{"class":77},">应用已可离线使用\u003C\u002F",[64,1734,64],{"class":1543},[64,1736,1559],{"class":77},[64,1738,1739,1741,1743,1746,1749,1751],{"class":66,"line":983},[64,1740,1720],{"class":77},[64,1742,64],{"class":1543},[64,1744,1745],{"class":81}," v-else",[64,1747,1748],{"class":77},">有新内容可用，点击刷新\u003C\u002F",[64,1750,64],{"class":1543},[64,1752,1559],{"class":77},[64,1754,1755,1757,1760,1762,1764,1767,1770,1772,1775,1778,1780],{"class":66,"line":1005},[64,1756,1720],{"class":77},[64,1758,1759],{"class":1543},"button",[64,1761,1700],{"class":81},[64,1763,1553],{"class":77},[64,1765,1766],{"class":88},"\"needRefresh\"",[64,1768,1769],{"class":81}," @click",[64,1771,1553],{"class":77},[64,1773,1774],{"class":88},"\"updateServiceWorker()\"",[64,1776,1777],{"class":77},">刷新\u003C\u002F",[64,1779,1759],{"class":1543},[64,1781,1559],{"class":77},[64,1783,1784,1787,1789],{"class":66,"line":1010},[64,1785,1786],{"class":77},"  \u003C\u002F",[64,1788,1697],{"class":1543},[64,1790,1559],{"class":77},[64,1792,1794,1796,1798],{"class":66,"line":1793},21,[64,1795,1672],{"class":77},[64,1797,1687],{"class":1543},[64,1799,1559],{"class":77},[14,1801,1803],{"id":1802},"web-worker","Web Worker",[10,1805,1806],{},"刚才提到的service worker常用于网络代理层，web worker常用于后台计算线程，能够把耗时计算移出主线程，生命周期是随着页面的创建和销毁。这个是html5提供的一个api，允许我们在后台线程运行js代码，从而避免主线程，它是浏览器实现多线程的标准方式。",[10,1808,1809,1810,1813],{},"因为Js是单线程，主线程会负责执行js代码，处理dom渲染，响应用户交互。如果主线程执行一个耗时任务比如大量计算、处理大数组，页面就会卡死，用户点击没反应、动画掉帧。Web Worker 把耗时任务放到",[28,1811,1812],{},"独立线程","，主线程继续响应用户，两者互不阻塞。",[55,1815,1817],{"className":213,"code":1816,"language":215,"meta":60,"style":60},"\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",[33,1818,1819,1824,1846,1850,1855,1897,1901,1906,1926,1940,1945,1949,1954,1973,1988,1992,1996,2001,2006,2010,2015,2020,2039,2064,2069,2086,2092,2144,2149,2155,2171,2177],{"__ignoreMap":60},[64,1820,1821],{"class":66,"line":67},[64,1822,1823],{"class":70},"\u002F\u002F 1. 创建 Worker\n",[64,1825,1826,1828,1831,1833,1836,1839,1841,1844],{"class":66,"line":74},[64,1827,146],{"class":124},[64,1829,1830],{"class":95}," worker",[64,1832,152],{"class":124},[64,1834,1835],{"class":124}," new",[64,1837,1838],{"class":81}," Worker",[64,1840,85],{"class":77},[64,1842,1843],{"class":88},"'worker.js'",[64,1845,1202],{"class":77},[64,1847,1848],{"class":66,"line":108},[64,1849,112],{"emptyLinePlaceholder":111},[64,1851,1852],{"class":66,"line":115},[64,1853,1854],{"class":70},"\u002F\u002F 2. 发送数据给 Worker\n",[64,1856,1857,1860,1862,1865,1868,1871,1874,1876,1879,1881,1884,1886,1889,1891,1894],{"class":66,"line":121},[64,1858,1859],{"class":77},"worker.",[64,1861,1403],{"class":81},[64,1863,1864],{"class":77},"({ type: ",[64,1866,1867],{"class":88},"'compute'",[64,1869,1870],{"class":77},", data: [",[64,1872,1873],{"class":95},"1",[64,1875,92],{"class":77},[64,1877,1878],{"class":95},"2",[64,1880,92],{"class":77},[64,1882,1883],{"class":95},"3",[64,1885,92],{"class":77},[64,1887,1888],{"class":95},"4",[64,1890,92],{"class":77},[64,1892,1893],{"class":95},"5",[64,1895,1896],{"class":77},"] });\n",[64,1898,1899],{"class":66,"line":143},[64,1900,112],{"emptyLinePlaceholder":111},[64,1902,1903],{"class":66,"line":421},[64,1904,1905],{"class":70},"\u002F\u002F 3. 接收 Worker 的结果\n",[64,1907,1908,1910,1913,1915,1917,1920,1922,1924],{"class":66,"line":429},[64,1909,1859],{"class":77},[64,1911,1912],{"class":81},"onmessage",[64,1914,152],{"class":124},[64,1916,230],{"class":77},[64,1918,1919],{"class":268},"e",[64,1921,352],{"class":77},[64,1923,355],{"class":124},[64,1925,358],{"class":77},[64,1927,1928,1930,1932,1934,1937],{"class":66,"line":437},[64,1929,1449],{"class":77},[64,1931,278],{"class":81},[64,1933,85],{"class":77},[64,1935,1936],{"class":88},"'结果:'",[64,1938,1939],{"class":77},", e.data);\n",[64,1941,1942],{"class":66,"line":443},[64,1943,1944],{"class":77},"};\n",[64,1946,1947],{"class":66,"line":449},[64,1948,112],{"emptyLinePlaceholder":111},[64,1950,1951],{"class":66,"line":455},[64,1952,1953],{"class":70},"\u002F\u002F 4. 监听错误\n",[64,1955,1956,1958,1961,1963,1965,1967,1969,1971],{"class":66,"line":461},[64,1957,1859],{"class":77},[64,1959,1960],{"class":81},"onerror",[64,1962,152],{"class":124},[64,1964,230],{"class":77},[64,1966,298],{"class":268},[64,1968,352],{"class":77},[64,1970,355],{"class":124},[64,1972,358],{"class":77},[64,1974,1975,1977,1980,1982,1985],{"class":66,"line":918},[64,1976,1449],{"class":77},[64,1978,1979],{"class":81},"error",[64,1981,85],{"class":77},[64,1983,1984],{"class":88},"'Worker 错误:'",[64,1986,1987],{"class":77},", err.message);\n",[64,1989,1990],{"class":66,"line":938},[64,1991,1944],{"class":77},[64,1993,1994],{"class":66,"line":968},[64,1995,112],{"emptyLinePlaceholder":111},[64,1997,1998],{"class":66,"line":977},[64,1999,2000],{"class":70},"\u002F\u002F 5. 终止 Worker\n",[64,2002,2003],{"class":66,"line":983},[64,2004,2005],{"class":70},"\u002F\u002F worker.terminate();\n",[64,2007,2008],{"class":66,"line":1005},[64,2009,112],{"emptyLinePlaceholder":111},[64,2011,2012],{"class":66,"line":1010},[64,2013,2014],{"class":70},"\u002F\u002F worker.js\n",[64,2016,2017],{"class":66,"line":1793},[64,2018,2019],{"class":70},"\u002F\u002F 接收主线程的消息\n",[64,2021,2023,2025,2027,2029,2031,2033,2035,2037],{"class":66,"line":2022},22,[64,2024,335],{"class":77},[64,2026,1912],{"class":81},[64,2028,152],{"class":124},[64,2030,230],{"class":77},[64,2032,1919],{"class":268},[64,2034,352],{"class":77},[64,2036,355],{"class":124},[64,2038,358],{"class":77},[64,2040,2042,2045,2048,2051,2053,2056,2059,2061],{"class":66,"line":2041},23,[64,2043,2044],{"class":124},"    const",[64,2046,2047],{"class":77}," { ",[64,2049,2050],{"class":95},"type",[64,2052,92],{"class":77},[64,2054,2055],{"class":95},"data",[64,2057,2058],{"class":77}," } ",[64,2060,1553],{"class":124},[64,2062,2063],{"class":77}," e.data;\n",[64,2065,2067],{"class":66,"line":2066},24,[64,2068,112],{"emptyLinePlaceholder":111},[64,2070,2072,2075,2078,2081,2084],{"class":66,"line":2071},25,[64,2073,2074],{"class":124},"    if",[64,2076,2077],{"class":77}," (type ",[64,2079,2080],{"class":124},"===",[64,2082,2083],{"class":88}," 'compute'",[64,2085,1639],{"class":77},[64,2087,2089],{"class":66,"line":2088},26,[64,2090,2091],{"class":70},"        \u002F\u002F 执行耗时计算\n",[64,2093,2095,2098,2101,2103,2106,2109,2112,2115,2117,2120,2122,2124,2127,2130,2133,2136,2139,2142],{"class":66,"line":2094},27,[64,2096,2097],{"class":124},"        const",[64,2099,2100],{"class":95}," result",[64,2102,152],{"class":124},[64,2104,2105],{"class":77}," data.",[64,2107,2108],{"class":81},"reduce",[64,2110,2111],{"class":77},"((",[64,2113,2114],{"class":268},"sum",[64,2116,92],{"class":77},[64,2118,2119],{"class":268},"n",[64,2121,352],{"class":77},[64,2123,355],{"class":124},[64,2125,2126],{"class":77}," sum ",[64,2128,2129],{"class":124},"+",[64,2131,2132],{"class":77}," n ",[64,2134,2135],{"class":124},"*",[64,2137,2138],{"class":77}," n, ",[64,2140,2141],{"class":95},"0",[64,2143,1202],{"class":77},[64,2145,2147],{"class":66,"line":2146},28,[64,2148,112],{"emptyLinePlaceholder":111},[64,2150,2152],{"class":66,"line":2151},29,[64,2153,2154],{"class":70},"        \u002F\u002F 把结果发回主线程\n",[64,2156,2158,2161,2163,2165,2168],{"class":66,"line":2157},30,[64,2159,2160],{"class":77},"        self.",[64,2162,1403],{"class":81},[64,2164,1864],{"class":77},[64,2166,2167],{"class":88},"'result'",[64,2169,2170],{"class":77},", value: result });\n",[64,2172,2174],{"class":66,"line":2173},31,[64,2175,2176],{"class":77},"    }\n",[64,2178,2180],{"class":66,"line":2179},32,[64,2181,1944],{"class":77},[10,2183,2184,2186],{},[33,2185,1403],{}," 支持两种传输方式，分别是结构化克隆、转移所有权。默认是结构化克隆：",[55,2188,2190],{"className":213,"code":2189,"language":215,"meta":60,"style":60},"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",[33,2191,2192,2200,2209,2219,2237,2247,2272,2293,2306,2323],{"__ignoreMap":60},[64,2193,2194,2196,2198],{"class":66,"line":67},[64,2195,1859],{"class":77},[64,2197,1403],{"class":81},[64,2199,1406],{"class":77},[64,2201,2202,2205,2207],{"class":66,"line":74},[64,2203,2204],{"class":77},"    num: ",[64,2206,1873],{"class":95},[64,2208,418],{"class":77},[64,2210,2211,2214,2217],{"class":66,"line":108},[64,2212,2213],{"class":77},"    str: ",[64,2215,2216],{"class":88},"'hello'",[64,2218,418],{"class":77},[64,2220,2221,2224,2226,2228,2230,2232,2234],{"class":66,"line":115},[64,2222,2223],{"class":77},"    arr: [",[64,2225,1873],{"class":95},[64,2227,92],{"class":77},[64,2229,1878],{"class":95},[64,2231,92],{"class":77},[64,2233,1883],{"class":95},[64,2235,2236],{"class":77},"],\n",[64,2238,2239,2242,2244],{"class":66,"line":121},[64,2240,2241],{"class":77},"    obj: { a: ",[64,2243,1873],{"class":95},[64,2245,2246],{"class":77}," },\n",[64,2248,2249,2252,2255,2258,2261,2264,2266,2269],{"class":66,"line":143},[64,2250,2251],{"class":77},"    map: ",[64,2253,2254],{"class":124},"new",[64,2256,2257],{"class":81}," Map",[64,2259,2260],{"class":77},"([[",[64,2262,2263],{"class":88},"'k'",[64,2265,92],{"class":77},[64,2267,2268],{"class":88},"'v'",[64,2270,2271],{"class":77},"]]),\n",[64,2273,2274,2277,2279,2282,2284,2286,2288,2290],{"class":66,"line":421},[64,2275,2276],{"class":77},"    set: ",[64,2278,2254],{"class":124},[64,2280,2281],{"class":81}," Set",[64,2283,1238],{"class":77},[64,2285,1873],{"class":95},[64,2287,92],{"class":77},[64,2289,1878],{"class":95},[64,2291,2292],{"class":77},"]),\n",[64,2294,2295,2298,2300,2303],{"class":66,"line":429},[64,2296,2297],{"class":77},"    date: ",[64,2299,2254],{"class":124},[64,2301,2302],{"class":81}," Date",[64,2304,2305],{"class":77},"(),\n",[64,2307,2308,2311,2313,2316,2318,2321],{"class":66,"line":437},[64,2309,2310],{"class":77},"    buffer: ",[64,2312,2254],{"class":124},[64,2314,2315],{"class":81}," ArrayBuffer",[64,2317,85],{"class":77},[64,2319,2320],{"class":95},"8",[64,2322,255],{"class":77},[64,2324,2325],{"class":66,"line":443},[64,2326,464],{"class":77},[10,2328,2329],{},"不支持dom节点、error对象（部分）、函数。",[10,2331,2332],{},"转移所有权有点像c++的移动move概念。对于大块数据比如ArrayBuffer，可以用转移替代复制，实现零拷贝：",[55,2334,2336],{"className":213,"code":2335,"language":215,"meta":60,"style":60},"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",[33,2337,2338,2373,2377,2382,2391,2395,2400],{"__ignoreMap":60},[64,2339,2340,2342,2345,2347,2349,2351,2353,2356,2359,2362,2364,2367,2370],{"class":66,"line":67},[64,2341,146],{"class":124},[64,2343,2344],{"class":95}," buffer",[64,2346,152],{"class":124},[64,2348,1835],{"class":124},[64,2350,2315],{"class":81},[64,2352,85],{"class":77},[64,2354,2355],{"class":95},"1024",[64,2357,2358],{"class":124}," *",[64,2360,2361],{"class":95}," 1024",[64,2363,2358],{"class":124},[64,2365,2366],{"class":95}," 100",[64,2368,2369],{"class":77},"); ",[64,2371,2372],{"class":70},"\u002F\u002F 100MB\n",[64,2374,2375],{"class":66,"line":74},[64,2376,112],{"emptyLinePlaceholder":111},[64,2378,2379],{"class":66,"line":108},[64,2380,2381],{"class":70},"\u002F\u002F 转移所有权，主线程不再拥有该 buffer\n",[64,2383,2384,2386,2388],{"class":66,"line":115},[64,2385,1859],{"class":77},[64,2387,1403],{"class":81},[64,2389,2390],{"class":77},"({ buffer }, [buffer]);\n",[64,2392,2393],{"class":66,"line":121},[64,2394,112],{"emptyLinePlaceholder":111},[64,2396,2397],{"class":66,"line":143},[64,2398,2399],{"class":70},"\u002F\u002F 此时 buffer 在主线程已不可用\n",[64,2401,2402,2405,2407,2410],{"class":66,"line":421},[64,2403,2404],{"class":77},"console.",[64,2406,278],{"class":81},[64,2408,2409],{"class":77},"(buffer.byteLength); ",[64,2411,2412],{"class":70},"\u002F\u002F 0\n",[10,2414,2415],{},"处理大数组、图像数据、音视频时，转移比复制快得多。典型的使用场景就有wasm，我们一般就是在worker中运行wasm模块的。可以用于图像处理、像素操作、canvas离屏渲染。还能用于轮询和长连接，后台定时请求、websocket心跳，这也是为了主线程不会去做大json解析、二进制处理处理占用主线程。",[14,2417,1513],{"id":2418},"indexeddb",[10,2420,2421],{},"这个是浏览器的嵌入式数据库，用kv存储，支持索引、事务、游标。能存储任意结构化数据，localStorage只能存储5-10MB，但是这个可以存储上GB的信息量。一个indexedDB可以创建多个数据库：",[55,2423,2426],{"className":2424,"code":2425,"language":202},[200],"const request = indexedDB.open('myDB', 1);   \u002F\u002F 名字 + 版本号\n",[33,2427,2425],{"__ignoreMap":60},[10,2429,2430],{},"他可以使用对象仓库，类似于表：",[55,2432,2435],{"className":2433,"code":2434,"language":202},[200],"db.createObjectStore('users', { keyPath: 'id' });\n",[33,2436,2434],{"__ignoreMap":60},[10,2438,2439],{},"可以使用索引加速查询：",[55,2441,2444],{"className":2442,"code":2443,"language":202},[200],"store.createIndex('email', 'email', { unique: true });\nstore.createIndex('age', 'age');\nstore.createIndex('name_age', ['name', 'age']);   \u002F\u002F 复合索引\n",[33,2445,2443],{"__ignoreMap":60},[10,2447,2448],{},"支持事务，能确保原子性和自动提交，出错能自动回滚：",[55,2450,2453],{"className":2451,"code":2452,"language":202},[200],"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",[33,2454,2452],{"__ignoreMap":60},[14,2456,2458],{"id":2457},"ajax技术","Ajax技术",[10,2460,2461],{},"Ajax是一组技术的组合，用于在不刷新页面的前提下用js异步向服务器请求数据并且更新页面的方法。",[10,2463,2464],{},"我们已知，HTML\u002FCSS用于展示内容，DOM动态更新页面，JS用于逻辑控制，那么XMLHttpRequest可以用于一步请求，XML\u002FJSON可以用于数据展示。",[10,2466,2467],{},"我们都知道现代ajax技术经过了发展从XMLHttpRequest变成了fetch，又变成了axios。XMLHttpRequest是1999发明，es6之后，发布了fetch。",[14,2469,2471],{"id":2470},"pwa","PWA",[10,2473,2474,2477],{},[28,2475,2476],{},"Progressive Web App"," 是一组能力的集合，核心是Service Worker + Web App Manifest。他可以做到离线可用，断网也可以打开，可安装，桌面\u002F主屏有图标，独立窗口。可以推送通知，可以后台同步。",[10,2479,2480],{},"service worker之前讲过，是一个跑在浏览器后台独立线程的脚本，可以拦截页面的网络请求、缓存资源、离线响应。",[14,2482,2483],{"id":2483},"浏览器缓存技术",[10,2485,2486],{},"浏览器本身给我们提供了一些缓存技术，最基础的缓存是HTTP响应头机制，通过强缓存可以将带内容哈希的静态资源比如app.a1b2c3.js缓存。",[10,2488,2489],{},"Cache-Control: 这个头我们在缓存里最常用，可以设置max-age= 单位s来以高优先级设置缓存资源，也可以用immutable设置资源永不变，刷新也不请求。头部可以设置Expires来设置绝对时间过期，优先级比较低。",[10,2491,2492],{},"Cache-Control: public, max-age=31536000, immutable 比如这样。",[10,2494,2495],{},"浏览器可以发送请求询问服务器是否可用，这个叫做协商缓存：",[10,2497,2498,2501,2502,2505,2506,2501,2509,2512],{},[33,2499,2500],{},"ETag"," \u002F ",[33,2503,2504],{},"If-None-Match"," 可以基于内容哈希，精度比较高，",[33,2507,2508],{},"Last-Modified",[33,2510,2511],{},"If-Modified-Since","基于时间戳，精度比较低。响应为304 Not Modified，不传响应体，比较节省带宽。 ETag是服务器为了某个资源生成的唯一标识符，通常基于文件内容计算比如哈希值，这样客户端在下次请求的时候就会通知服务器本地存有这个版本，并判断是否资源最新的。",[10,2514,2515],{},"这是协商的完整过程：",[55,2517,2520],{"className":2518,"code":2519,"language":202},[200],"第一次请求：\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",[33,2521,2519],{"__ignoreMap":60},[10,2523,2524],{},"适用于HTML文件、API响应等需要及时更新的资源：",[55,2526,2529],{"className":2527,"code":2528,"language":202},[200],"Cache-Control: no-cache\nETag: \"abc123\"\n",[33,2530,2528],{"__ignoreMap":60},[10,2532,2533,2534,2537,2538,2541],{},"vary: Origin表示响应内容依赖请求的Origin头，如果同一个URL被不同 ",[33,2535,2536],{},"Origin"," 请求，浏览器会分别缓存。这在开发环境下常见，因为 Vite 的 dev server 需要处理 CORS。生产环境下，如果 CDN 配置不当，",[33,2539,2540],{},"vary: Origin"," 可能导致缓存命中率下降。",[10,2543,2544],{},"缓存策略组合：",[10,2546,2547],{},"HTML这种资源推荐使用no-cache+ ETag每次协商，js\u002Fcss推荐用max-age和immutable进行缓存。图片和字体也可以通过max-age+ETag进行缓存。API响应可以通过no-store或者短max-age + ETag进行缓存。",[10,2549,2550],{},"内存缓存是浏览器自己自动管理的，同一页面重复请求同一资源的时候就会直接从内存读取，关闭标签页就会失效。浏览器在收到一个资源的时候，会根据响应头决定存储在哪里。内存缓存的存储位置是RAM，而非DISK，读取速度很快，像js css 图片资源等高频使用的资源可以存，生命周期是当前标签页会话，关闭就失效。磁盘缓存的位置是硬盘，生命周期是由Cache-Control \u002F Expires 决定，读取速度较快，像大文件，不常变动的资源可以缓存。service worker缓存存储的位置是Cache API，独立于HTTP缓存，需要手动控制生命周期，像PWA、离线资源可以存。",[55,2552,2555],{"className":2553,"code":2554,"language":202},[200],"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",[33,2556,2554],{"__ignoreMap":60},[10,2558,2559,2560,1499],{},"public, max-age=2592000, immutable 会让我们强缓存30天永不变，cf-cache-status: HIT表示这是Cloudflare CDN边缘节点已缓存。etag是协商的缓存标识，last-modified是最后的修改时间。这个组合会让浏览器把图片存入磁盘缓存，下次请求同一URL时候浏览器直接读磁盘，不发网络请求，状态码会显示 ",[33,2561,2562],{},"200 (from disk cache)",[14,2564,2565],{"id":2565},"标签页和v8",[10,2567,2568],{},"每个标签页其实不会分配一个独立的v8实例，v8实例是绑定在渲染进程RendererProcess上的，而不是直接绑定在标签页上。正确的关系是，一个v8实例可能服务多个标签页和iframe。在早期，确实一个标签页一个进程，现在策略已经变了，如果我们打开两个标签都访问同一个网站比如example.com，chrome可能让它们共享一个渲染进程，也就是共享一个v8实例，用来节省内存。如果网站嵌入了跨站iframe，chrome会为了跨站ifame单独创建一个渲染进程也就是OOPIF，跨进程iframe，一个标签页内部可能对于好几个渲染进程，有好几个v8实例。",[10,2570,2571],{},"可以查看chromium.org内看文档：离线处理架构OOPIFs：",[55,2573,2576],{"className":2574,"code":2575,"language":202},[200],"这种技术允许页面的子框架由与父框架不同的进程来渲染。采用独立进程内嵌框架的做法主要是出于安全考虑，比如 Site Isolation 项目所提出的解决方案——通过这种方式，即使存在跨域内嵌框架，也能让某个渲染进程专注于处理某个特定的网站内容。不过，独立进程内嵌框架也是一种通用的机制，其应用范围不仅限于安全相关场景，还可以用于其他功能，例如 Chrome 应用程序中的\u003Cwebview>标签。\n",[33,2577,2575],{"__ignoreMap":60},[10,2579,2580,2581,2584,2585,1499,2588,2591,2592,2595,2596,2598,2599,2602],{},"OOPIF 要求 Chromium 的架构从“以标签页为中心”转向“",[28,2582,2583],{},"以 frame 为中心","”，因为一个 frame 在其生命周期内可能被渲染到不同进程中。浏览器进程现在直接追踪",[28,2586,2587],{},"完整的 frame 树",[33,2589,2590],{},"WebContents"," 持有一个 ",[33,2593,2594],{},"FrameTreeNode"," 树，镜像当前页面的 frame 结构。每个 ",[33,2597,2594],{}," 包含该 frame 的元信息（名称、origin 等），其 ",[33,2600,2601],{},"RenderFrameHostManager"," 负责处理该 frame 的跨进程导航，并支持状态复制和消息路由",[55,2604,2607],{"className":2605,"code":2606,"language":202},[200],"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",[33,2608,2606],{"__ignoreMap":60},[10,2610,2611,2612,2615,2616,2619],{},"这地方了解着玩玩就行，不用记忆太深，quad是合成器提交给gpu的最小绘制单元，可以理解为一个带纹理矩形，描述了哪块的纹理，用什么变换，绘制到屏幕哪个位置。渲染进程的合成器不直接操作 GPU，而是把一帧画面",[28,2613,2614],{},"拆解成一组 quad","，打包成 ",[33,2617,2618],{},"CompositorFrame","，通过 IPC 发给浏览器进程或 GPU 进程。GPU 再按顺序把这些 quad 绘制出来，合成最终画面。",[10,2621,2622],{},"再深入挖掘就是架构图形栈知识了，不讲不讲...",[14,2624,2625],{"id":2625},"viz进程架构",[10,2627,2628],{},"viz是chromium渲染管线现代化的核心，主要为了解决GPU进程职责过重，站点隔离下的性能和安全瓶颈，以及图形栈的服务化需求。",[10,2630,2631],{},"在 Spectre 等侧信道攻击的背景下，跨站 iframe 需要严格的进程隔离（OOPIF）。VIZ将核心的合成和光栅化集中到独立进程中，使得来自不同渲染进程的合成器帧可以在一个安全的、收信任环境下统一聚合，从而支持“无额外性能代价的站点隔离”。Viz 是 Chromium 向 Mojo 服务化架构转型的一部分。它将合成、GL、命中测试、媒体等能力封装为标准服务，通过 Mojo 接口对外提供，使得 Browser 进程不再是唯一的核心枢纽，架构上更清晰、可维护性更强。",[10,2633,2634],{},"skia是chromium的底层2D图形库，负责我们几乎我们所有看到的像素绘制，从文字到图形、形状、路径、渐变、阴影、图层混合等。",[55,2636,2639],{"className":2637,"code":2638,"language":202},[200],"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",[33,2640,2638],{"__ignoreMap":60},[2642,2643,2644],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}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 .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":60,"searchDepth":74,"depth":74,"links":2646},[2647,2648,2649,2650,2651,2652,2653,2654,2655],{"id":16,"depth":108,"text":17},{"id":173,"depth":108,"text":174},{"id":1802,"depth":108,"text":1803},{"id":2418,"depth":108,"text":1513},{"id":2457,"depth":108,"text":2458},{"id":2470,"depth":108,"text":2471},{"id":2483,"depth":108,"text":2483},{"id":2565,"depth":108,"text":2565},{"id":2625,"depth":108,"text":2625},"md",{"date":2658,"tags":2659},"2026-12-10",[2660],"browser","\u002Fbrowser\u002Fchrome-edge类的浏览器相关技术面试总结",{"description":12},"browser\u002Fchrome-edge类的浏览器相关技术面试总结","vbGa5XKnTx5IMi39be0JSvg4yTwrB1OUM0jnYFlU6jg",1791871170402]