1. pdf预览
javascript 代码解读复制代码// 1.href
'a.pdf' target="_blank">
// 2.window
window.open('a.pdf','_blank')
3. 使用 PDF.js
PDF.js是一个由 Mozilla 开发的开源库,它使用 HTML5 Canvas 来渲染 PDF 文件。PDF.js 提供了广泛的 API 来实现 PDF 的加载、渲染、缩放、打印等功能。
xml 代码解读复制代码
<script src="/path/to/pdf.js">script>
<script src="/path/to/pdf.worker.js">script>
<div id="pdf-container">div>
<script>
// 初始化PDF.js
pdfjsLib.getDocument("/path/to/your/document.pdf").promise.then(function (pdfDoc) {
// 获取第一页
pdfDoc.getPage(1).then(function (page) {
// 设置视口和比例
var scale = 1.5;
var viewport = page.getViewport({ scale: scale });
// 准备用于渲染的Canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
// 将Canvas添加到DOM中
document.getElementById("pdf-container").appendChild(canvas);
// 通过Canvas渲染PDF页面
var renderContext = {
canvasContext: ctx,
viewport: viewport,
};
page.render(renderContext);
});
});
script>
使用第三方服务
也可以使用第三方服务如 Google Docs Viewer 来预览 PDF。这种方法的优点是容易实现,但依赖于外部服务。
ini代码解读复制代码
其中,将http://path.to/your/document.pdf
替换为你的 PDF 文件的真实 URL。
2. 日志监控问题:可有办法将请求的调用源码地址包括代码行数也上报上去?
2.1.1. 源码映射(Source Maps)
SourceMap 主要用于调试目的,让开发者能够在压缩或转译后的代码中追踪到原始代码。
webpack中 配置 devtool: 'source-map'
后,
在编译过程中,会生成一个 .map
文件,一般用于代码调试和错误监控。
- 包含了源代码、编译后的代码、以及它们之间的映射关系。
- 编译后的文件通常会在文件末尾添加一个注释,指向 SourceMap文件的位置。
-
// # sourceMappingURL=example.js.map
- 当在浏览器开发者工具调试时,浏览器会读取这行注释并加载对应的 SourceMap 文件
报错时,点击跳转。即使运行的是编译后的代码,也能够追溯到原始源代码的具体位置,而不是处理经过转换或压缩后的代码,从而提高了调试效率。
2.1.2. 自定义错误日志逻辑
使用try .. catch 自定义报错逻辑,便于错误的追踪。
3. 用户线上问题的解决
首先看是否是突然性大量用户受到影响,如果是可能是上线新功能影响,则应该立即回退,降低影响范围
然后再处理问题。复现问题-判断是前端还是后端问题。
- 浏览器缓存
- 插件影响
- 网络问题
- 浏览器版本问题
- 问题解决后需要进行复盘。
4. 大文件上传
- 前端上传大文件时使用 file.slice 将文件切片,并发上传多个切片(有标号, Blob 对象),最后发送一个合并的请求通知
- 使用formData 上传文件
-
- 将分块后的 Blob 对象封装到
FormData
中,以便通过 HTTP 请求发送。FormData
对象提供了一种简单的方式来构造一个包含表单数据的对象,并且可以直接作为fetch
或axios
请求的body
参数。
- 将分块后的 Blob 对象封装到
- 服务端合并切片
-
- 切片上传可以将上传成功的切片通过localstorage保存,再 继续上传失败的内容
- 服务端接收切片并存储,收到合并请求后使用流将切片合并到最终文件
- 原生 XMLHttpRequest 的 upload.onprogress 对切片上传进度的监听
- 使用 Vue 计算属性根据每个切片的进度算出整个文件的上传进度
xml 代码解读复制代码<template>
<div>
<input type="file" @change="handleFileChange" />
<el-button @click="handleUpload">uploadel-button>
div>
template>
<script>
// 切片大小
// the chunk size
const SIZE = 10 * 1024 * 1024;
export default {
data: () => ({
container: {
file: null
},
data: []
}),
methods: {
handleFileChange(e) {
const [file] = e.target.files;
if (!file) return;
Object.assign(this.$data, this.$options.data());
this.container.file = file;
},
// 生成文件切片
+ createFileChunk(file, size = SIZE) {
+ const fileChunkList = [];
+ let cur = 0;
+ while (cur < file.size) {
+ fileChunkList.push({ file: file.slice(cur, cur + size) });
+ cur += size;
+ }
+ return fileChunkList;
+ },
+ // 上传切片
+ async uploadChunks() {
+ const requestList = this.data
+ .map(({ chunk,hash }) => {
+ const formData = new FormData();
+ formData.append("chunk", chunk);
+ formData.append("hash", hash);
+ formData.append("filename", this.container.file.name);
+ return { formData };
+ })
+ .map(({ formData }) =>
+ this.request({
+ url: "http://localhost:3000",
+ data: formData
+ })
+ );
+ // 并发请求
+ await Promise.all(requestList);
+ },
+ async handleUpload() {
+ if (!this.container.file) return;
+ const fileChunkList = this.createFileChunk(this.container.file);
+ this.data = fileChunkList.map(({ file },index) => ({
+ chunk: file,
+ // 文件名 + 数组下标
+ hash: this.container.file.name + "-" + index
+ }));
+ await this.uploadChunks();
+ }
// 合并切片
+ await this.mergeRequest();
},
+ async mergeRequest() {
+ await this.request({
+ url: "http://localhost:3000/merge",
+ headers: {
+ "content-type": "application/json"
+ },
+ data: JSON.stringify({
+ filename: this.container.file.name
+ })
+ });
+ },
}
};
script>
5. 文本点开收起展开
xml 代码解读复制代码html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="textContainer" class="text-overflow">
这是一段可能很长的文本,我们希望在一开始时只显示部分,点击“展开”按钮后显示全部内容,再次点击则“收起”文本。
div>
<button id="toggleButton">展开button>
<script>
const button = document.getElementById('toggleButton');
button.addEventListener('click', () => {
const container = document.getElementById('textContainer');
if (button.textContent === '展开') {
button.textContent = '收起';
container.style.whiteSpace = 'normal'
} else {
button.textContent = '展开';
container.style.whiteSpace = 'nowrap'
}
})
script>
<style>
.text-overflow {
width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
style>
body>
html>
6. 富文本划线获取
javascript 代码解读复制代码document.addEventListener('mouseup',(e) => {
const selection = window.getSelection()
if(selection) {
const res = selection.toString();
console.log(res)
}
})
7. 鼠标拖拽
实现鼠标拖拽功能通常涉及到监听和处理鼠标事件,比如:mousedown
、mousemove
和mouseup
事件。
ini 代码解读复制代码="toggleButton">展开
const button = document.getElementById("toggleButton")
button.style.cursor = 'pointer'
button.style.position = 'absolute'
let dist = {
x: 0,
y: 0
}
let isdraggable = false;
button.addEventListener('mousedown', (e) => {
isdraggable = true;
dist.x = e.pageX - button.offsetLeft;
dist.y = e.pageY - button.offsetTop;
})
button.addEventListener('mousemove', (e) => {
if (isdraggable) {
button.style.left = e.pageX - dist.x + 'px'
button.style.top = e.pageY - dist.y + 'px'
}
})
button.addEventListener('mouseup', (e) => {
if (isdraggable) {
isdraggable = false;
dist.x = 0
dist.y = 0
}
})
8. 要统计全站每一个静态资源(如图片、JS 脚本、CSS 样式表等)的加载耗时
- 使用
PerformanceObserver
: 创建一个PerformanceObserver
实例来监听资源加载事件,能够实时收集性能数据,而且对性能影响较小。 - 过滤静态资源类型: 通过检查
initiatorType
属性,筛选出静态资源(例如img
、script
、css
等)的加载事件。 - 计算和展示耗时: 对每个静态资源的加载耗时进行计算并展示。资源的耗时可以通过
duration
属性直接获取。
javascript 代码解读复制代码// 创建性能观察者实例来监听资源加载事件
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
for (const entry of entries) {
// 过滤静态资源类型
if (["img", "script", "css", "link"].includes(entry.initiatorType)) {
console.log(`资源 ${entry.name} 类型 ${entry.initiatorType} 耗时:${entry.duration.toFixed(2)} 毫秒`);
}
}
});
// 开始观察 Resource Timing 类型的性能条目
observer.observe({ entryTypes: ["resource"] });
9. 如何防止前端接口重复发送
1、提交按钮点击后增加loading, 防止重复点击
2、节流或防抖
3、使用缓存
对于一些数据不经常变化的请求,例如用户信息、配置数据等,可以将请求的结果缓存起来。下一次请求相同的资源时,先从缓存中读取数据,如果缓存有效,则无需再发起新的网络请求。
10. 一次性渲染十万条数据
10.1. 全部渲染-卡死
这种方法虽然实现起来简单直接,但由于它在一个循环中创建并添加了所有列表项至DOM树
,因此在执行过程中,浏览器需要等待JavaScript
完全执行完毕才能开始渲染页面。当数据量非常大(例如本例中的100,000个列表项)时,这种大量的DOM操作
会导致浏览器的渲染队列积压大量工作,从而引发页面的回流与重绘,浏览器无法进行任何渲染操作,导致了所谓的“阻塞”渲染。
10.2. setTimeout分批渲染 或 requestAnimationFrame
为了避免一次性操作引起浏览器卡顿,我们可以使用setTimeout
将创建和添加操作分散到多个时间点,每次只渲染一部分数据。
ini 代码解读复制代码let ul=document.getElementById('container');
const total=100000
let once= 20
let page=total/once
let index=0
function loop(curTotal,curIndex){
let pageCount=Math.min(once,curTotal)
setTimeout(()=>{
for(let i=0;i
let li=document.createElement('li');
li.innerText=curIndex+i+':'(Math.random()*total)
ul.appendChild(li)
}
loop(curTotal-pageCount,curIndex+pageCount)
})
}
loop(total,index)
这里就是把浏览器渲染时的压力分摊给了js引擎
,js引擎
是单线程工作的,先执行同步,异步,然后浏览器渲染,再宏任务,这里就很好的利用了这一点,把渲染的任务分批执行,减轻了浏览器一次要渲染大量数据造成的渲染“阻塞”,也很好的解决了数据过多
时可能造成页面卡顿或白屏的问题,
使用 requestAnimationFrame
替代 setTimeout
,将数据拆分为每帧处理 20-50 条,避免主线程阻塞。相比 setTimeout
,帧率更稳定且与浏览器渲染周期同步
10.3. 分页实现渲染
10.4. 虚拟列表
虚拟列表
其实是按需显示的一种实现,即只对可见区域
进行渲染,对非可见区域
中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
xml 代码解读复制代码<template>
<div ref="listWrapper" class="list-wrapper" @scroll="handleScroll">
<div class="visible-items" :style="{ transform: `translateY(${startIndex * itemHeight}px)` }">
<div v-for="item in visibleItems" :key="item.id" class="list-item">
{{ item.text }}
div>
div>
div>
template>
<script>
export default {
data() {
return {
items: [/* 假设这里是你的大数据列表 */],
itemHeight: 50, // 假设每个列表项的高度是固定的
startIndex: 0, // 当前可视区域的起始索引
visibleCount: 10 // 可视区域内同时显示的列表项数量
};
},
computed: {
endIndex() {
return Math.min(this.startIndex + this.visibleCount, this.items.length);
},
visibleItems() {
return this.items.slice(this.startIndex, this.endIndex);
}
},
methods: {
handleScroll() {
const scrollTop = this.$refs.listWrapper.scrollTop;
this.startIndex = Math.floor(scrollTop / this.itemHeight);
}
}
};
script>
<style scoped>
.list-wrapper {
height: 300px; /* 设定滚动容器的高度 */
overflow-y: auto; /* 允许垂直滚动 */
position: relative;
}
.list-item {
height: 50px; /* 与data中的itemHeight保持一致 */
/* 其他样式 */
}
style>
11. 如何判断用户设备
-
用户代理字符串包含了浏览器类型、版本、操作系统等信息,可以通过分析这些信息来大致判断用户的设备类型。
navigator.userAgent
属性用于获取用户代理字符串。 -
使用window.innerWidth 检测视口宽度
12. IntersectionObserver + scrollIntoView 实现电梯导航
电梯导航也被称为锚点导航
- 当点击锚点元素时,页面内相应标记的元素滚动到视口。 scrollIntoView
- 页面内元素滚动时相应锚点也会高亮。IntersectionObserver
javascript 代码解读复制代码// 点击锚点跳转
let rightBox = document.querySelector('.rightBox')
rightBox.addEventListener('click', function (e) {
let target = e.target || e.srcElement;
if (target && !target.classList.contains('rightBox')) {
document.querySelector('.' + target.className.replace('Li', '')).scrollIntoView({
behavior: 'smooth',
block: 'center'
})
}
}, false)
// 页面滚动时,对应锚点样式改变
// 也可以使用计算el.offsetTop < window.innerHeight + document.documentElement.scrollTop判断
// 是否进入视口
let observer = new IntersectionObserver(function (entries) {
entries.forEach(entry => {
let target = document.querySelector('.' + entry.target.className + 'Li')
if (entry.isIntersecting) { // 出现在检测区域内
document.querySelectorAll('li').forEach(el => {
if(el.classList.contains('active')){
el.classList.remove('active')
}
})
if (!target.classList.contains('active')) {
target.classList.add('active')
}
}
})
}, {
threshold: 1
})
document.querySelectorAll('div').forEach(el => {
observer.observe(el)
})
13. 退出浏览器之间, 发送积压的埋点数据请求
- fecth的 keepalive属性
navigator.sendBeacon()
navigator.sendBeacon()
方法允许你在浏览器会话结束时异步地向服务器发送小量数据。这个方法的设计初衷就是为了解决上述问题。sendBeacon()
在大多数现代浏览器中得到支持,并且其异步特性意味着它不会阻塞页面卸载或影响用户体验。
javascript 代码解读复制代码window.addEventListener("beforeunload", function (event) {
var data = {
/* 收集的埋点数据 */
};
var beaconUrl = "https://yourserver.com/path"; // 你的服务器接收端点
navigator.sendBeacon(beaconUrl, JSON.stringify(data));
});
fetch()
API 的 keepalive
选项是另一个选择。这个选项允许你发送一个保持存活状态的请求,即使用户已经离开页面。但是,需要注意的是,使用 keepalive
选项发送的请求有大小限制(大约为 64KB)。
less 代码解读复制代码window.addEventListener("beforeunload", function (event) {
var data = {
/* 收集的埋点数据 */
};
var beaconUrl = "https://yourserver.com/path"; // 你的服务器接收端点
fetch(beaconUrl, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
},
keepalive: true, // 保持请求存活
});
});
14. 代码打印
ini 代码解读复制代码const { a = 1, b = 2, c = 3 } = { a: '', b: undefined, c: null };
// 只有设置为undefined的时候或者没有这个属性的时候才使用默认值
console.log(a, b, c); // 2, null
// 考察运符号优先级和 加法
// const result = undefined || (1 + undefined) || 2;
// undefined转换成数字是NaN, 1+NaN = NaN
// const result = undefined || NaN || 2;
// 最终输出2
const result = undefined || 1 + undefined || 2;
console.log(result);
15. 多核处理任务
多核环境下的性能优化需求和JavaScript特性,可采用Web Workers结合时间分片技术实现非阻塞定时任务处理。以下是基于JavaScript类的实现方案
ini 代码解读复制代码 // worker.js
self.onMessage = (data) => {
const start = Date.now()
while (Date.now() - start < 100) {
continue
}
self.postMessage(data)
}
// main
class MultTask {
constructor(concurrency = 4) {
this.workersPool = [];
this.taskQueue = [];
for (let i = 0; i < concurrency; i++) {
const worker = new Worker('worker.js');
worker.onmessage = (data) => this.#handleResult(data, worker)
worker.task = null;
this.workersPool.push(worker)
}
}
addTask(input) {
return new Promise((resolve, reject) => {
const task = {
id: Date.now(),
input,
resolve,
}
this.taskQueue.push(task)
this.#run()
})
}
#run() {
if (!this.taskQueue.length) return;
const availWorker = this.workersPool.find(item => !item.busy)
if (!availWorker) return;
availWorker.busy = true;
const task = this.taskQueue.shift();
availWorker.task = task;
availWorker.postMessage({
id: task.id,
input: task.input
})
}
#handleResult(data, worker) {
worker.busy = false;
worker.task.resolve(data);
worker.task = null;
this.#run()
}
}
// 使用实例
const processor = new MultTask(3)
setInterval(() => {
processor.addTask(Math.random()).then(res => console.log(res))
}, 40)
16. 浏览器环境下幂级计算的优化方案
快速幂运算 + webworker 结合
快速幂算法
通过二进制分解指数,将计算复杂度从 O(n) 优化至 O(log n),减少乘法次数。例如计算 a15 时,分解为 a8×a4×a2×a1,仅需 4 次乘法而非 14 次
scss 代码解读复制代码
// worker.js
// // 分解为子问题计算,递归
function fast(a, n) {
if (n === 0) return 1;
if(n < 0) return 1 / fast(a, -n);
const half = fast(a, Math.floor(n / 2));
return n % 2 === 0 ? half * half : half * half * a
}
self.onmessage = (a,n) => {
const res = fastPower(a,n)
self.postmessage(res)
}
// main
const worker = new Worker(./worker.js)
worker.postmessage(2,15)
worker.onmessage = (res) => {
console.log(res)
}
评论记录:
回复评论: