一、跨域共享Token实现单点登录
在前端开发中,当你有多个不同源的网站需要共享登录状态时,可以通过以下几种方式实现单点登录(SSO):
1. 使用子域共享Cookie
如果这些网站共享一个顶级域名(如a.example.com和b.example.com),可以:
- 将Token存储在顶级域名下的Cookie中(设置domain为
.example.com
) - 这样所有子域都能访问这个Cookie
// 设置跨子域的Cookie
document.cookie = `token=${token}; domain=.example.com; path=/; secure; samesite=lax`;
2. 使用OAuth 2.0隐式授权流
-
用户登录第一个网站(A)后获得Token
-
当访问第二个网站(B)时:
- B检测本地无Token,重定向到认证服务器
- 认证服务器发现用户已登录(A),直接返回Token给B
- B使用Token完成登录
3. 使用PostMessage跨域通信
在已登录的网站A:
// 当检测到用户跳转到网站B时
window.open('https://b.example.com');
window.addEventListener('load', () => {
window.opener.postMessage({ token: 'your_token' }, 'https://b.example.com');
});
在网站B:
window.addEventListener('message', (event) => {
if (event.origin === 'https://a.example.com') {
const token = event.data.token;
// 使用token登录
}
});
4. 使用中间页面传递Token
- 从网站A跳转到一个中间页面(同源的)
- 中间页面将Token存储在localStorage或通过URL传递给网站B
5. 后端协助的Token验证
- 网站A登录后,将Token发送到后端存储
- 访问网站B时,B后端与A后端通信验证用户状态
- 如果已登录,B后端生成一个针对B的Token返回给前端
安全注意事项
- 始终使用HTTPS传输Token
- 考虑Token的过期时间
- 实现CSRF保护机制
- 对于敏感操作要求重新认证
二、首屏加载速度优化
问题:首页加载时间超过5秒,Lighthouse评分仅45分
解决方案:
- 图片懒加载与优化:
<!-- 使用IntersectionObserver实现懒加载 -->
<img data-src="product.jpg" class="lazyload" />
<script>
document.addEventListener("DOMContentLoaded", () => {
const lazyImages = [].slice.call(document.querySelectorAll("img.lazyload"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach((lazyImage) => {
lazyImageObserver.observe(lazyImage);
});
}
});
</script>
- 关键CSS内联:
<style>
/* 提取首屏关键CSS内联 */
.hero-section, .main-nav { /* ... */ }
</style>
<link rel="stylesheet" href="non-critical.css" media="print" onload="this.media='all'">
- 代码分割+预加载:
// Vue路由懒加载+预加载
const routes = [
{
path: '/product/:id',
component: () => import(/* webpackPrefetch: true */ './views/Product.vue')
}
]
效果:首屏加载时间从5s降至1.8s,Lighthouse评分提升至92
三、页面卡顿优化
现象:长时间使用后页面变卡,Chrome任务管理器显示内存持续增长
1. 内存泄露
排查步骤:
-
Chrome DevTools内存快照对比:
- 页面初始状态拍快照
- 执行疑似泄漏操作后拍快照
- 比较两个快照的对象分配情况
-
发现的问题:
// 错误示例:事件监听未清除
mounted() {
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
// 忘记移除监听器
}
- 解决方案:
// Vue解决方案
mounted() {
this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$el)
},
beforeDestroy() {
this.resizeObserver.disconnect()
}
// React解决方案
useEffect(() => {
const handleResize = () => { /*...*/ }
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
进阶工具:
# 使用Chrome的Performance Monitor持续监控
# 使用Heap Snapshots查找分离的DOM树
2. 大型响应式对象
-
发现的问题:
- 排查发现开发人员创建了 Cesium 地图核心对象的 Vue 响应式对象,该对象是 Cesium 的核心对象,包含很多属性及对象,Vue 的深度响应式转换需要递归地遍历对象的所有属性,这会消耗较多的时间和内存,导致页面卡顿
-
解决方案:
- 使用 shallowRef 进行浅响应式跟踪,Vue 不会对对象内部的属性变化进行响应式处理
四、WebWorker优化长任务
1. 大数据表格处理
问题:万行数据排序导致UI冻结3秒
解决方案:
- 主线程代码:
// 创建WebWorker
const worker = new Worker('sort.worker.js')
// 发送数据到Worker
function sortBigData(data) {
return new Promise((resolve) => {
worker.postMessage({ type: 'SORT', data })
worker.onmessage = (e) => {
if (e.data.type === 'SORT_DONE') {
resolve(e.data.result)
}
}
})
}
// 使用示例
const bigData = [...Array(100000).keys()].map(x => ({id: x, value: Math.random()}))
sortBigData(bigData).then(sortedData => {
this.sortedData = sortedData
})
- Worker线程代码 (sort.worker.js) :
// 复杂的排序算法
function complexSort(arr) {
return arr.sort((a, b) => {
// 复杂的多条件排序逻辑
return a.value - b.value || a.id - b.id
})
}
self.onmessage = function(e) {
if (e.data.type === 'SORT') {
const sorted = complexSort(e.data.data)
self.postMessage({
type: 'SORT_DONE',
result: sorted
})
}
}
优化效果:UI保持流畅,排序操作不再阻塞主线程
2. Canvas图像处理
问题:高分辨率图片滤镜处理导致页面卡顿
WebWorker方案:
// 主线程
const canvasWorker = new Worker('canvas.worker.js')
const offscreenCanvas = document.querySelector('canvas').transferControlToOffscreen()
canvasWorker.postMessage({
type: 'PROCESS_IMAGE',
canvas: offscreenCanvas,
imageData: imageData
}, [offscreenCanvas])
// Worker线程
onmessage = function(e) {
if (e.data.type === 'PROCESS_IMAGE') {
const { canvas, imageData } = e.data
const ctx = canvas.getContext('2d')
// 耗时的像素处理
for(let i = 0; i < imageData.data.length; i += 4) {
// 应用滤镜算法...
}
ctx.putImageData(imageData, 0, 0)
}
}
3. 地图要素数据推送处理
问题:接收多个地理要素数据推送时,如轨迹展示等功能,频繁处理实时数据导致页面卡顿
WebWorker方案:使用 webWorker 进行数据处理,提示地图流畅度
注意事项:
- Worker间通信数据会被复制(结构化克隆算法),大数据考虑Transferable Objects
- 避免频繁通信,批量处理数据
- Worker中无法访问DOM,但可通过OffscreenCanvas操作Canvas
五、WebSocket 高并发会引发哪些问题及解决方案
连接管理问题
1. 连接数限制
问题表现:
- 浏览器对同一域名有6-8个TCP连接限制
- 超过限制后新连接被阻塞或失败
解决方案:
// 连接池管理
class WSPool {
constructor(maxConnections = 4) {
this.pool = new Array(maxConnections).fill(null);
this.queue = [];
}
acquire() {
return new Promise(resolve => {
const freeIndex = this.pool.indexOf(null);
if(freeIndex !== -1) {
const ws = new WebSocket(url);
this.pool[freeIndex] = ws;
resolve(ws);
} else {
this.queue.push(resolve);
}
});
}
release(ws) {
const index = this.pool.indexOf(ws);
if(index !== -1) {
this.pool[index] = null;
if(this.queue.length) {
this.acquire().then(this.queue.shift());
}
}
}
}
2. 心跳中断
问题表现:
- 大量连接导致心跳包未能及时发送
- 被服务器误判为死连接
解决方案:
// 自适应心跳
let heartbeatInterval = 30000; // 初始30秒
function setupHeartbeat(ws) {
const adjustInterval = () => {
const latency = measureLatency(); // 测量实际网络延迟
heartbeatInterval = Math.min(
Math.max(latency * 3, 5000),
60000
);
};
const ping = () => {
if(ws.bufferedAmount > 0) {
// 网络拥塞时跳过本次心跳
adjustInterval();
return;
}
ws.send(JSON.stringify({type: 'ping'}));
adjustInterval();
};
return setInterval(ping, heartbeatInterval);
}
消息处理问题
1. 消息风暴
问题表现:
- 短时间内收到大量消息导致UI冻结
- 主线程被阻塞无法响应用户操作
解决方案:
// 基于requestAnimationFrame的节流处理
class MessageScheduler {
constructor() {
this.queue = [];
this.isProcessing = false;
}
add(message) {
this.queue.push(message);
if(!this.isProcessing) {
this.isProcessing = true;
requestAnimationFrame(this.process.bind(this));
}
}
process() {
const startTime = performance.now();
let processed = 0;
while(this.queue.length && performance.now() - startTime < 8) {
// 每帧最多处理8ms
const msg = this.queue.shift();
handleMessage(msg);
processed++;
}
if(this.queue.length) {
requestAnimationFrame(this.process.bind(this));
} else {
this.isProcessing = false;
}
if(processed > 50) {
adjustThrottleRate(); // 动态调整处理速率
}
}
}
2. 乱序问题
问题表现:
- 消息到达顺序与发送顺序不一致
- 导致UI状态错乱
解决方案:
// 基于序列号的排序缓冲区
class MessageSequencer {
constructor() {
this.nextSeq = 0;
this.buffer = new Map();
this.timer = null;
}
receive(msg) {
if(msg.seq === this.nextSeq) {
this.deliver(msg);
} else {
this.buffer.set(msg.seq, msg);
this.scheduleDeliver();
}
}
deliver(msg) {
handleMessage(msg);
this.nextSeq++;
// 检查缓冲区是否有后续消息
while(this.buffer.has(this.nextSeq)) {
const nextMsg = this.buffer.get(this.nextSeq);
this.buffer.delete(this.nextSeq);
this.deliver(nextMsg);
}
}
scheduleDeliver() {
if(!this.timer) {
this.timer = setTimeout(() => {
this.timer = null;
this.flushBuffer();
}, 100); // 最大等待100ms
}
}
flushBuffer() {
// 强制处理已缓冲的消息(可能产生跳跃)
const sorted = Array.from(this.buffer.entries())
.sort((a,b) => a[0] - b[0]);
sorted.forEach(([seq, msg]) => {
if(seq >= this.nextSeq) {
this.deliver(msg);
}
});
}
}
资源消耗问题
1. 内存泄漏
问题表现:
- 未清理的消息缓存导致内存持续增长
- 长时间运行后标签页崩溃
解决方案:
// 自动内存管理
class MessageCache {
constructor(maxSize = 1000) {
this.cache = new Map();
this.accessTimes = new Map();
this.maxSize = maxSize;
setInterval(this.cleanup.bind(this), 60000);
}
add(id, message) {
if(this.cache.size >= this.maxSize) {
this.evictOldest();
}
this.cache.set(id, message);
this.accessTimes.set(id, Date.now());
}
get(id) {
const message = this.cache.get(id);
if(message) {
this.accessTimes.set(id, Date.now());
}
return message;
}
evictOldest() {
const oldest = Array.from(this.accessTimes.entries())
.sort((a,b) => a[1] - b[1])[0];
if(oldest) {
this.cache.delete(oldest[0]);
this.accessTimes.delete(oldest[0]);
}
}
cleanup() {
const cutoff = Date.now() - 3600000; // 1小时未访问
for(const [id, time] of this.accessTimes) {
if(time < cutoff) {
this.cache.delete(id);
this.accessTimes.delete(id);
}
}
}
}
2. CPU过载
问题表现:
- 消息处理消耗大量CPU资源
- 移动设备发热严重
解决方案:
// Web Worker分流处理
const worker = new Worker('message-processor.js');
// 主线程
websocket.onmessage = (event) => {
worker.postMessage({
type: 'raw',
data: event.data
});
};
worker.onmessage = (event) => {
switch(event.data.type) {
case 'render':
updateUI(event.data.payload);
break;
case 'alert':
showAlert(event.data.message);
break;
}
};
// message-processor.js
self.onmessage = async (event) => {
if(event.data.type === 'raw') {
const msg = await parseMessage(event.data.data);
const result = await processComplexLogic(msg);
self.postMessage({
type: 'render',
payload: result
});
}
};
用户体验问题
1. UI闪烁
问题表现:
- 高频更新导致界面不断重绘
- 用户无法聚焦阅读内容
解决方案:
// 视觉稳定技术
class StableRenderer {
constructor() {
this.pendingUpdates = new Map();
this.rafId = null;
}
scheduleUpdate(key, html) {
this.pendingUpdates.set(key, html);
if(!this.rafId) {
this.rafId = requestAnimationFrame(() => {
this.applyUpdates();
this.rafId = null;
});
}
}
applyUpdates() {
// 批量DOM操作
const fragment = document.createDocumentFragment();
this.pendingUpdates.forEach((html, key) => {
const element = document.getElementById(key);
if(element) {
element.innerHTML = html;
fragment.appendChild(element.cloneNode(true));
}
});
// 使用requestPostAnimationFrame减少闪烁
requestPostAnimationFrame(() => {
this.pendingUpdates.forEach((html, key) => {
const element = document.getElementById(key);
if(element) {
element.parentNode.replaceChild(
fragment.querySelector(`#${key}`),
element
);
}
});
this.pendingUpdates.clear();
});
}
}
2. 操作冲突
问题表现:
- 用户输入被实时更新覆盖
- 表单提交时显示旧数据
解决方案:
// 编辑锁机制
class EditLock {
constructor() {
this.lockedElements = new WeakMap();
}
acquire(element) {
if(!this.lockedElements.has(element)) {
const lock = {
version: 0,
isLocked: false
};
this.lockedElements.set(element, lock);
return lock;
}
return this.lockedElements.get(element);
}
checkConflict(element, serverVersion) {
const lock = this.acquire(element);
if(lock.isLocked && lock.version !== serverVersion) {
showConflictDialog({
local: lock.version,
remote: serverVersion
});
return true;
}
return false;
}
}
// 使用示例
inputElement.addEventListener('focus', () => {
const lock = editLock.acquire(inputElement);
lock.isLocked = true;
lock.version++;
});
websocket.onmessage = (event) => {
if(editLock.checkConflict(inputElement, event.data.version)) {
return; // 忽略冲突更新
}
// 正常处理消息
};
监控与调优
1. 性能指标采集
const metrics = {
messageRate: new RollingWindow(60), // 60秒滑动窗口
processingTime: new Histogram([5, 10, 25, 50, 100])
};
websocket.onmessage = (event) => {
const start = performance.now();
metrics.messageRate.add(1);
processMessage(event.data);
const duration = performance.now() - start;
metrics.processingTime.record(duration);
if(duration > 50) {
reportLongTask(event.data, duration);
}
};
setInterval(() => {
const rate = metrics.messageRate.sum();
const p95 = metrics.processingTime.percentile(95);
if(rate > 1000 || p95 > 30) {
adjustThrottleLevel();
}
}, 10000);
2. 动态策略调整
// 根据网络条件调整策略
function adaptStrategy() {
const connection = navigator.connection;
if(connection) {
switch(connection.effectiveType) {
case '4g':
setBatchSize(50);
setThrottleDelay(10);
break;
case '3g':
setBatchSize(20);
setThrottleDelay(30);
break;
case '2g':
setBatchSize(5);
setThrottleDelay(100);
useTextCompression();
break;
}
if(connection.saveData) {
enableDataSaverMode();
}
}
}
// 监听网络变化
navigator.connection.addEventListener('change', adaptStrategy);
降级方案
1. WebSocket不可用时的降级
function connectWithFallback() {
const ws = new WebSocket(url);
ws.onerror = () => {
if(!this.hasRetried) {
this.hasRetried = true;
setTimeout(() => connectWithFallback(), 1000);
} else {
startLongPolling();
}
};
}
function startLongPolling() {
let abortController = new AbortController();
async function poll() {
try {
const response = await fetch('/poll', {
signal: abortController.signal
});
const messages = await response.json();
processMessages(messages);
poll();
} catch(e) {
if(e.name !== 'AbortError') {
setTimeout(poll, 5000);
}
}
}
poll();
// 尝试恢复WebSocket
setInterval(() => {
if(navigator.onLine) {
abortController.abort();
abortController = new AbortController();
connectWithFallback();
}
}, 30000);
}
六、自动响应的动态数组
现有如下代码:
let b = 1
const a = [b ,2, 3]
a.join('.') // 输出 [1, 2, 3]
b = 2
a.join('.') // 依然输出 [1, 2, 3]
在b
的值更改为2
之后,若希望动态输出为[2, 2, 3]
,且a
的相关调用不能更改(如a.join('.')
该调用式不可修改),请输出解决反感
1、思路:
- 动态获取最新数组:通过
getLatestArray
函数实时生成依赖b
的最新数组。 - Proxy 自动转发:代理对象
a
拦截所有属性访问(如a.join
、a[0]
),自动转发到最新数组的对应属性 / 方法。 - 保持方法上下文:通过
bind(latestArr)
确保数组方法(如map
、push
)内部的this
指向最新数组,避免上下文错乱。
2、解决方案:
let b = 1;
// 定义生成最新数组的函数(动态依赖b)
const getLatestArray = () => [b, 2, 3];
// 创建代理对象,自动关联到最新数组
const a = new Proxy({}, {
// 拦截属性访问(如 a[0]、a.length)
get(target, prop) {
const latestArr = getLatestArray();
// 如果访问的是方法(如 join、map、push)
if (typeof latestArr[prop] === 'function') {
// 绑定最新数组作为上下文,确保方法内的this指向正确
return latestArr[prop].bind(latestArr);
}
// 否则直接返回最新数组的属性(如索引、length)
return latestArr[prop];
},
// 拦截属性设置(可选,如需支持修改数组)
set(target, prop, value) {
const latestArr = getLatestArray();
latestArr[prop] = value;
return true;
}
});
// 测试各种数组方法和属性访问
console.log(a.join('.')); // 1.2.3(初始值)
console.log(a.map(x => x * 2)); // [2, 4, 6]
console.log(a.length); // 3
console.log(a[0]); // 1
// 修改b的值
b = 5;
// 自动更新
console.log(a.join('.')); // 5.2.3
console.log(a.map(x => x * 2)); // [10, 4, 6]
console.log(a.length); // 3
console.log(a[0]); // 5
// 支持修改数组(如新增元素)
a.push(4);
console.log(a.join('.')); // 5.2.3.4(修改后的值)