文章目录
- 前言
- 一、同源策略与跨域
- 1.1 同源定义
- 1.2 跨域解决方案
- 二、跨标签页通信
- 2.1 BroadcastChannel
- 2.2 localStorage 事件
- 2.3 SharedWorker
- 2.4 方案对比
- 三、SSE vs WebSocket
- 3.1 SSE(Server-Sent Events)
- 3.2 WebSocket
- 3.3 区别
- 四、离线存储方案选型
- 4.1 方案对比
- 4.2 选型指南
- 五、「从输入 URL 到页面显示」
- 5.1 完整流程
- 5.2 面试回答要点
- 六、高频手写题汇总
- 6.1 防抖
- 6.2 节流
- 6.3 深拷贝
- 6.4 Promise.all
- 6.5 发布订阅
- 七、易混淆点
- 八、思考与练习
- 总结
前言
本篇是系列文章的收尾,汇总面试高频手写题和综合性问题,帮助系统性复习。内容涵盖:
- 同源策略与跨域
- 跨标签页通信
- SSE vs WebSocket
- 离线存储方案选型
- 「从输入 URL 到页面显示」串联题
一、同源策略与跨域
1.1 同源定义
// 同源:协议、域名、端口完全相同https://example.com:443/path ↓ ↓ ↓ 协议 域名 端口// 不同源的情况:// 协议不同:http vs https// 域名不同:example.com vs api.example.com// 端口不同::80 vs :80801.2 跨域解决方案
// 1. CORS(最常用)// 服务端设置响应头Access-Control-Allow-Origin:https://example.com Access-Control-Allow-Methods:GET,POST,PUT,DELETEAccess-Control-Allow-Headers:Content-Type,Authorization Access-Control-Allow-Credentials:true// 允许携带 Cookie// 2. 代理// 开发环境:devServer proxydevServer:{proxy:{'/api':'http://localhost:8080'}}// 生产环境:Nginx 反向代理location/api{proxy_pass http://backend:8080;}// 3. JSONP(只支持 GET)functionjsonp(url,callback){constscript=document.createElement('script')script.src=`${url}?callback=${callback}`document.body.appendChild(script)}// 4. postMessage(跨窗口通信)otherWindow.postMessage(data,targetOrigin)二、跨标签页通信
2.1 BroadcastChannel
// 标签页 Aconstchannel=newBroadcastChannel('my-channel')channel.postMessage({type:'update',data:'Hello'})// 标签页 Bconstchannel=newBroadcastChannel('my-channel')channel.onmessage=(event)=>{console.log(event.data)// { type: 'update', data: 'Hello' }}2.2 localStorage 事件
// 标签页 AlocalStorage.setItem('message',JSON.stringify({text:'Hello'}))// 标签页 Bwindow.addEventListener('storage',(event)=>{if(event.key==='message'){console.log(JSON.parse(event.newValue))}})2.3 SharedWorker
// worker.jsconstports=[]onconnect=(e)=>{constport=e.ports[0]ports.push(port)port.onmessage=(event)=>{// 广播给其他标签页ports.forEach(p=>{if(p!==port)p.postMessage(event.data)})}}// 标签页constworker=newSharedWorker('worker.js')worker.port.onmessage=(e)=>console.log(e.data)worker.port.postMessage('Hello from tab A')2.4 方案对比
| 方案 | 适用场景 | 兼容性 |
|---|---|---|
| BroadcastChannel | 同源标签页通信 | 现代浏览器 |
| localStorage | 简单数据同步 | 全兼容 |
| SharedWorker | 复杂逻辑共享 | 现代浏览器 |
| postMessage | 跨域窗口通信 | 全兼容 |
三、SSE vs WebSocket
3.1 SSE(Server-Sent Events)
// 客户端constsource=newEventSource('/api/events')source.onmessage=(event)=>{console.log(event.data)}source.addEventListener('custom',(event)=>{console.log(event.data)})source.onerror=(event)=>{console.log('连接错误')}// 服务端(Node.js)app.get('/api/events',(req,res)=>{res.setHeader('Content-Type','text/event-stream')res.setHeader('Cache-Control','no-cache')res.setHeader('Connection','keep-alive')setInterval(()=>{res.write(`data:${JSON.stringify({time:Date.now()})}\n\n`)},1000)})3.2 WebSocket
// 客户端constws=newWebSocket('ws://localhost:8080')ws.onopen=()=>{ws.send('Hello Server')}ws.onmessage=(event)=>{console.log(event.data)}ws.onclose=()=>{console.log('连接关闭')}// 服务端(Node.js + ws)constWebSocket=require('ws')constwss=newWebSocket.Server({port:8080})wss.on('connection',(ws)=>{ws.on('message',(message)=>{ws.send(`Echo:${message}`)})})3.3 区别
| 对比项 | SSE | WebSocket |
|---|---|---|
| 方向 | 服务端 → 客户端(单向) | 双向通信 |
| 协议 | HTTP | 独立协议(ws://) |
| 数据格式 | 文本 | 文本 + 二进制 |
| 自动重连 | 支持 | 需手动实现 |
| 适用场景 | 通知、实时数据推送 | 聊天、游戏、协作编辑 |
四、离线存储方案选型
4.1 方案对比
| 方案 | 容量 | 过期机制 | 适用场景 |
|---|---|---|---|
| Cookie | 4KB | 设置过期时间 | 认证状态、会话管理 |
| localStorage | 5-10MB | 永久(需手动清除) | 用户偏好、缓存数据 |
| sessionStorage | 5-10MB | 会话结束 | 表单暂存、页面状态 |
| IndexedDB | 无限制 | 无 | 大量结构化数据、离线应用 |
| Cache API | 无限制 | 手动管理 | 资源缓存、Service Worker |
4.2 选型指南
// 1. 小量简单数据 → Cookiedocument.cookie='session=abc; max-age=3600; secure; httponly'// 2. 中量键值对 → localStoragelocalStorage.setItem('theme','dark')localStorage.setItem('lang','zh-CN')// 3. 会话级数据 → sessionStoragesessionStorage.setItem('formData',JSON.stringify(formData))// 4. 大量结构化数据 → IndexedDBconstrequest=indexedDB.open('myDB',1)request.onupgradeneeded=(event)=>{constdb=event.target.result db.createObjectStore('users',{keyPath:'id'})}// 5. 资源缓存 → Cache API + Service Workercaches.open('v1').then(cache=>{cache.addAll(['/index.html','/styles.css','/app.js'])})五、「从输入 URL 到页面显示」
5.1 完整流程
1. URL 解析 - 浏览器解析 URL(协议、域名、端口、路径、参数) 2. DNS 解析 - 浏览器缓存 → 系统缓存 → 路由器 → ISP → 根域名服务器 - 递归查询,获取 IP 地址 3. 建立 TCP 连接 - 三次握手:SYN → SYN+ACK → ACK - HTTPS 还需要 TLS 握手 4. 发送 HTTP 请求 - 请求行:方法、路径、协议版本 - 请求头:Host、Cookie、Content-Type 等 - 请求体:POST 数据 5. 服务器处理 - 负载均衡 → Web 服务器 → 应用服务器 → 数据库 - 返回 HTTP 响应 6. 浏览器渲染 - 解析 HTML → 构建 DOM 树 - 解析 CSS → 构建 CSSOM 树 - 合并 DOM + CSSOM → 渲染树 - 布局(Layout)→ 绘制(Paint)→ 合成(Composite) 7. JavaScript 执行 - 下载、解析、执行 JS - 可能修改 DOM/CSSOM,触发重排/重绘5.2 面试回答要点
// 回答结构:// 1. 网络阶段// - DNS 解析(迭代/递归查询)// - TCP 三次握手// - TLS 握手(HTTPS)// - HTTP 请求/响应// 2. 解析阶段// - HTML 解析 → DOM 树// - CSS 解析 → CSSOM 树// - 合并 → 渲染树// 3. 渲染阶段// - 布局(计算位置大小)// - 绘制(生成像素)// - 合成(GPU 合成图层)// 4. 优化点// - DNS 预解析:<link rel="dns-prefetch">// - 预连接:<link rel="preconnect">// - 资源提示:preload、prefetch// - 关键渲染路径优化六、高频手写题汇总
6.1 防抖
functiondebounce(fn,delay){lettimer=nullreturnfunction(...args){clearTimeout(timer)timer=setTimeout(()=>fn.apply(this,args),delay)}}6.2 节流
functionthrottle(fn,interval){letlastTime=0returnfunction(...args){constnow=Date.now()if(now-lastTime>=interval){lastTime=nowfn.apply(this,args)}}}6.3 深拷贝
functiondeepClone(obj,map=newWeakMap()){if(obj===null||typeofobj!=='object')returnobjif(map.has(obj))returnmap.get(obj)constresult=Array.isArray(obj)?[]:{}map.set(obj,result)for(constkeyofReflect.ownKeys(obj)){result[key]=deepClone(obj[key],map)}returnresult}6.4 Promise.all
functionpromiseAll(promises){returnnewPromise((resolve,reject)=>{constresults=[]letcount=0promises.forEach((promise,index)=>{Promise.resolve(promise).then((value)=>{results[index]=valueif(++count===promises.length)resolve(results)},reject)})if(promises.length===0)resolve(results)})}6.5 发布订阅
classEventEmitter{events={}on(event,callback){if(!this.events[event])this.events[event]=[]this.events[event].push(callback)return()=>this.off(event,callback)}off(event,callback){if(!this.events[event])returnthis.events[event]=this.events[event].filter(cb=>cb!==callback)}emit(event,...args){if(!this.events[event])returnthis.events[event].forEach(cb=>cb(...args))}}七、易混淆点
- 同源策略限制:限制 AJAX 跨域请求,但不限制
<script>、<img>、<link>等标签的跨域加载。 - SSE vs WebSocket:SSE 是单向(服务端 → 客户端),WebSocket 是双向。SSE 基于 HTTP,WebSocket 是独立协议。
- localStorage vs sessionStorage:localStorage 永久存储(需手动清除),sessionStorage 会话结束自动清除。
- Cookie vs Token:Cookie 自动携带,Token 需手动设置请求头。Cookie 有大小限制,Token 无限制。
八、思考与练习
1.同源策略限制了什么?不限制什么?
解析:
- 限制:AJAX 跨域请求、Cookie 读取、DOM 访问
- 不限制:
<script>、<img>、<link>、<video>等标签的跨域加载
2.跨标签页通信有哪些方案?
解析:
- BroadcastChannel:同源标签页专用
- localStorage + storage 事件:简单数据同步
- SharedWorker:复杂逻辑共享
- postMessage:跨域窗口通信
3.从输入 URL 到页面显示经历了哪些步骤?
解析:
- URL 解析
- DNS 解析
- TCP 三次握手(HTTPS 还需 TLS 握手)
- HTTP 请求/响应
- HTML 解析 → DOM 树
- CSS 解析 → CSSOM 树
- 合并 → 渲染树
- 布局 → 绘制 → 合成
4.localStorage 和 sessionStorage 的区别?
解析:
- localStorage:永久存储,需手动清除,同源所有标签页共享
- sessionStorage:会话级存储,标签页关闭自动清除,仅当前标签页可用
5.SSE 和 WebSocket 的区别?
解析:
- SSE:单向(服务端 → 客户端),基于 HTTP,自动重连,适用于通知、实时数据推送
- WebSocket:双向通信,独立协议,需手动重连,适用于聊天、游戏、协作编辑
总结
- 同源策略:协议、域名、端口相同,限制 AJAX 跨域
- 跨域方案:CORS(最常用)、代理、JSONP、postMessage
- 跨标签通信:BroadcastChannel、localStorage、SharedWorker、postMessage
- SSE vs WebSocket:前者单向基于 HTTP,后者双向独立协议
- 离线存储:Cookie(4KB)、localStorage/sessionStorage(5-10MB)、IndexedDB(无限制)
- URL 到页面:DNS → TCP → HTTP → 解析 → 渲染