Vue相关面试题
vue2和vue3的区别
一、核心架构差异
特性 Vue2 Vue3 响应式系统 基于 Object.defineProperty
基于 Proxy
(支持动态新增/删除属性)代码组织方式 Options API
(data/methods分块)Composition API
(逻辑按功能聚合)虚拟DOM优化 全量对比 静态标记(Patch Flags)、静态提升 Tree-shaking支持 无 按需编译,体积更小(如无用的功能模块可剔除) 二、核心特性对比详解
1. 响应式系统
Vue2:
通过Object.defineProperty
劫持对象属性的getter/setter
,但无法检测数组索引修改和对象属性新增/删除,需用Vue.set
/Vue.delete
。// Vue2中动态添加响应式属性 Vue.set(this.obj, 'newKey', 'value');
Vue3:
使用Proxy
代理整个对象,天然支持深层响应式,无需额外API。const reactiveObj = reactive({}); reactiveObj.newKey = 'value'; // 自动触发响应
2. 代码组织方式
Vue2 Options API:
逻辑分散在data
、methods
、computed
等选项中,复杂组件代码阅读困难。export default {data() { return { count: 0 } },methods: { increment() { this.count++ } } }
Vue3 Composition API:
使用setup()
函数聚合逻辑,支持逻辑复用(类似React Hooks)。import { ref } from 'vue'; export default {setup() {const count = ref(0);const increment = () => count.value++;return { count, increment };} }
3. 性能优化
虚拟DOM Diff优化:
Vue3通过Block Tree
和Patch Flags
标记动态节点,减少对比范围。// Vue3模板编译后生成的虚拟DOM片段 createVNode("div", { id: "foo" }, [createVNode("span", { class: _ctx.dynamicClass }, null, 1 /* CLASS */) ]);
Tree-shaking:
Vue3模块化设计,未使用的功能(如v-model
修饰符)不会打包进最终产物。三、新特性与开发体验
特性 Vue2 Vue3 Fragment支持 单根节点组件 支持多根节点(减少无意义包裹) Teleport组件 无 跨DOM层级渲染(如全局弹窗) Suspense组件 无 异步组件加载状态管理 TypeScript支持 需额外类型声明 源码使用TS编写,原生支持更完善 四、生命周期与API变化
Vue2生命周期 Vue3生命周期 说明 beforeCreate
使用 setup()
替代在 setup()
中执行初始化逻辑created
使用 setup()
替代beforeMount
onBeforeMount
组合式API钩子需显式引入 mounted
onMounted
beforeDestroy
onBeforeUnmount
命名更语义化 destroyed
onUnmounted
五、迁移注意事项
全局API变化:
Vue.prototype
→app.config.globalProperties
new Vue()
→createApp()
// Vue3创建应用实例 import { createApp } from 'vue'; const app = createApp(App); app.mount('#app');
事件总线替代:
Vue3移除$on/$off
,推荐使用mitt
等第三方库。过滤器(Filters)废弃:
改用计算属性或方法处理数据格式化。六、总结回答示例
"Vue3的核心升级集中在响应式系统重构(Proxy替代defineProperty)、开发体验优化(Composition API聚合逻辑)、性能提升(Tree-shaking、虚拟DOM优化)以及新特性支持(Fragment、Teleport等)。
例如,在Vue2中处理动态新增响应式属性需要Vue.set
,而Vue3的Proxy机制让这变得自然。同时,Composition API让复杂组件的逻辑更易维护。
这些改进使Vue3更适合大型项目,同时保持对Vue2的渐进式兼容。"加分项:
提及Vue3的
<script setup>
语法糖(更简洁的组合式API写法)。对比生态工具(如Vue Router 4、Pinia替代Vuex)。
性能数据:Vue3打包体积减少41%,渲染速度提升55%。
compted和watch的区别
在Vue.js中,
computed
和watch
都是用于响应数据变化的工具,但它们的应用场景和实现逻辑有本质区别。以下是它们的核心差异和适用场景:一、核心区别
特性 computed watch 目的 声明式依赖追踪,生成新值 监听数据变化,执行副作用操作 缓存 有缓存(依赖不变时直接返回缓存值) 无缓存(每次变化都触发回调) 同步/异步 必须同步返回结果 支持异步操作 适用场景 模板中需要动态计算的派生数据 数据变化时需要触发复杂逻辑(如API请求) 语法 定义为一个函数(可含getter/setter) 监听一个或多个数据源,定义handler函数 二、工作机制详解
1.
computed
(计算属性)
依赖追踪:自动追踪其内部依赖的响应式数据,依赖变化时重新计算。
缓存机制:只有依赖变化时才会重新计算,否则直接返回缓存值。
语法示例:
export default {data() {return { firstName: '张', lastName: '三' }},computed: {fullName() {return this.firstName + ' ' + this.lastName; // 依赖firstName和lastName}} }
模板中使用:
<template><div>{{ fullName }}</div> <!-- 自动更新 --> </template>
2.
watch
(侦听器)
主动监听:显式指定要监听的数据源(可以是简单路径或复杂表达式)。
回调触发:数据变化时执行回调函数,适合处理异步或耗时操作。
语法示例:
export default {data() {return { searchKeyword: '' }},watch: {// 监听searchKeyword变化searchKeyword(newVal, oldVal) {this.fetchSearchResults(newVal); // 触发搜索逻辑(可能是异步的)}} }
三、使用场景对比
使用
computed
的场景
动态计算显示值:
例如:根据单价和数量计算总价、格式化日期、过滤列表等。computed: {totalPrice() {return this.quantity * this.unitPrice;} }
依赖多个值的复杂逻辑:
//根据用户权限和页面状态动态显示按钮。computed: {showAdminButton() {return this.user.role === 'admin' && this.pageStatus === 'edit';} }
使用
watch
的场景
数据变化触发副作用:
例如:搜索框输入变化时发起API请求。watch: {searchKeyword(newVal) {if (newVal) {this.debouncedSearch(newVal); // 防抖搜索}} }
监听引用类型的变化(需深度监听):
例如:监听对象或数组内部变化。watch: {userInfo: {handler(newVal) {console.log('用户信息变化:', newVal);},deep: true // 深度监听} }
异步操作:
例如:数据变化后需要延迟执行或调用外部接口。watch: {async userId(newVal) {const data = await fetchUserData(newVal);this.userData = data;} }
四、高级用法
1.
computed
的Setter允许通过赋值操作反向修改依赖数据:
computed: {fullName: {get() {return this.firstName + ' ' + this.lastName;},set(newValue) {const [firstName, lastName] = newValue.split(' ');this.firstName = firstName;this.lastName = lastName;}} }
2.
watch
的高级配置
immediate: true
:组件初始化时立即执行回调。
deep: true
:深度监听对象/数组内部变化。监听多个数据源:
watch: {'obj.a'(newVal) { /* 监听嵌套属性 */ },'arr.0': { handler() { /* 监听数组索引 */ } } }
五、性能优化建议
优先使用
computed
:
需要派生新值时,computed
的缓存机制能减少不必要的计算。避免滥用
watch
:
监听大量数据或频繁触发的操作可能导致性能问题。复杂计算拆分:
将大型计算属性拆分为多个小计算属性,提高可维护性和缓存效率。六、总结
computed
:适合依赖其他数据生成新值的场景(如动态计算、格式化),利用缓存优化性能。
watch
:适合响应数据变化执行操作的场景(如API请求、日志记录),支持异步和深度监听。错误用法示例:
// ❌ 错误:用watch实现本该由computed完成的功能 watch: {firstName() { this.fullName = this.firstName + this.lastName; },lastName() { this.fullName = this.firstName + this.lastName; } } // ✅ 正确:用computed自动追踪依赖 computed: {fullName() { return this.firstName + this.lastName; } }
虚拟DOM是什么?
在 Vue 中,虚拟 DOM(Virtual DOM) 是一种用于优化页面渲染性能的核心技术,它是真实 DOM 的轻量级 JavaScript 对象表示。Vue 通过虚拟 DOM 实现高效的差异化更新,从而减少对真实 DOM 的直接操作,提升应用性能。
一、虚拟 DOM 的核心作用
1. 性能优化
直接操作 DOM 成本高:每次 DOM 修改都会触发浏览器重排(Reflow)和重绘(Repaint)。
虚拟 DOM 的缓冲机制:通过对比新旧虚拟 DOM 的差异(Diff 算法),仅更新必要的 DOM 节点。
2. 简化开发
声明式编程:开发者只需关注数据变化,无需手动操作 DOM。
跨平台能力:虚拟 DOM 可映射到不同平台(如浏览器、小程序、原生应用)。
二、Vue 中虚拟 DOM 的工作流程
1. 模板编译
Vue 将模板(
.vue
文件或template
字符串)编译为渲染函数(Render Function):// 模板 <div id="app">{{ message }}</div>// 编译后的渲染函数 function render() {return _c('div', { attrs: { id: 'app' } }, [_v(_s(message))]) }
2. 生成虚拟 DOM
执行渲染函数,生成描述 DOM 结构的虚拟节点(VNode):
const vnode = {tag: 'div',data: { attrs: { id: 'app' } },children: [ { text: 'Hello Vue!' } ] }
3. 响应式数据触发更新
当数据(如
message
)变化时,Vue 的响应式系统会触发组件重新渲染,生成新的虚拟 DOM 树。4. Diff 算法对比差异
Vue 的 Diff 算法(称为 patch 过程)对比新旧虚拟 DOM 树:
同级比较:仅对比同一层级的节点。
Key 优化:通过
key
标识节点身份,避免列表渲染错误。// 旧虚拟 DOM { tag: 'div', children: [ { text: 'Old' } ] }// 新虚拟 DOM { tag: 'div', children: [ { text: 'New' } ] }// Diff 结果:仅更新文本节点
5. 更新真实 DOM
根据 Diff 结果,调用原生 DOM API 进行最小化更新:
// 伪代码示例 if (oldVNode.text !== newVNode.text) {textNode.nodeValue = newVNode.text; // 仅修改文本内容 }
三、虚拟 DOM 的关键优势
优势 说明 批量更新 合并多次数据变更,避免频繁 DOM 操作 差异更新 仅更新变化的部分,减少重排重绘次数 跨平台渲染 同一套虚拟 DOM 可渲染到浏览器、Native(Weex)、Canvas(如图表库)等 开发体验优化 无需手动操作 DOM,专注于数据逻辑
四、虚拟 DOM 的局限性
局限性 说明 内存占用 需维护虚拟 DOM 树,内存开销略高 首次渲染耗时 需额外生成虚拟 DOM,首次加载可能稍慢 极端性能场景 对性能要求极高的动画/游戏,仍需直接操作 DOM
五、示例:Vue 中虚拟 DOM 的实际应用
<template><div><button @click="count++">Click {{ count }}</button><ul><li v-for="item in list" :key="item.id">{{ item.text }}</li></ul></div> </template><script> export default {data() {return {count: 0,list: [{ id: 1, text: 'A' },{ id: 2, text: 'B' }]};} }; </script>
更新过程解析:
点击按钮:
count
自增,触发重新渲染。生成新虚拟 DOM:Vue 调用渲染函数生成新的虚拟 DOM 树。
Diff 对比:
<button>
的文本节点变化。
<li>
列表因key
存在,高效复用 DOM 节点。Patch 更新:仅修改按钮文本,列表无需变动。
六、如何优化 Vue 的虚拟 DOM 性能
合理使用
key
:<!-- 使用唯一标识 key --> <li v-for="item in list" :key="item.id">{{ item.text }}</li>
避免不必要的渲染:
使用
v-once
静态标记:<div v-once>{{ staticContent }}</div>
使用
shouldComponentUpdate
(Vue 的shouldUpdate
钩子)。减少模板复杂度:
拆分复杂组件,避免过深的虚拟 DOM 树。
七、举个例子,让你轻松理解虚拟 DOM 是什么,以及它在 Vue 中为什么重要?
虚拟 DOM 就像「建筑模型」
想象你要装修房子,直接装修成本很高(每次改个插座都要砸墙),于是你做了个 「房屋模型」(虚拟 DOM)来模拟真实房子(真实 DOM)。每次改动都先在模型上调整,最后只按模型修改真实房子。
为什么用虚拟 DOM?
1. 避免「拆了又建」的浪费
直接操作 DOM:相当于每次改动都拆掉房子重建(比如改个灯泡,却把整个房子拆了再盖)。
用虚拟 DOM:先在模型上改好(比如换个灯泡位置),对比新旧模型,只拆换灯泡的位置,其他部分保留。
2. 提高「装修效率」
设计师(Vue)的工作流程:
画草图(生成虚拟 DOM)
先画个装修设计图(描述房子该长什么样)。对比修改(Diff 算法)
如果之前有旧设计图,就对比哪里需要改(比如墙面颜色变了,但地板没变)。按图施工(更新真实 DOM)
只刷墙,不换地板,省时省力。
举个具体例子
假设你有一个待办清单:
<ul><li>买菜</li><li>做饭</li> </ul>
直接操作 DOM(原始方法)
添加一个新任务「洗碗」:
删掉整个
<ul>
重新创建
<ul>
并插入所有<li>
(包括已有的和新增的)用虚拟 DOM(Vue 的方法)
生成新虚拟 DOM
// 旧虚拟 DOM { tag: 'ul', children: [{ tag: 'li', text: '买菜' },{ tag: 'li', text: '做饭' } ]}// 新虚拟 DOM(添加「洗碗」) { tag: 'ul', children: [{ tag: 'li', text: '买菜' },{ tag: 'li', text: '做饭' },{ tag: 'li', text: '洗碗' } ]}
对比差异(Diff 算法)
发现只是多了一个
<li>洗碗</li>
只添加新节点
真实 DOM 中只插入新
<li>
,其他不变。
虚拟 DOM 的三大好处
好处 例子说明 加快速度 改灯泡不用拆房子 减少卡顿 避免页面频繁闪烁(只改必要的部分) 自动处理复杂逻辑 你只管说「要加洗碗」,Vue 自动找最优修改方式
为什么需要「Key」?
如果任务列表是动态的(比如顺序会变),Vue 需要一个「身份证号」(Key)来识别每个任务,避免误判。比如:
<!-- 没有 Key 的情况 --> <li>任务A</li> <li>任务B</li><!-- 插入新任务后可能出错 --> <li>任务C</li> <!-- Vue 可能误以为这是任务A,导致渲染错误 --> <li>任务A</li> <li>任务B</li><!-- 用 Key 解决 --> <li key="1">任务A</li> <li key="2">任务B</li><!-- 插入新任务后 --> <li key="3">任务C</li> <!-- Vue 通过 Key 知道这是新增的 --> <li key="1">任务A</li> <li key="2">任务B</li>
总结
虚拟 DOM 的本质:一个轻量版的「房屋模型」,用来模拟真实页面结构。
Vue 的工作方式:先改模型 → 对比变化 → 只改真实页面需要动的地方。
你的好处:不用操心怎么操作 DOM,专注写业务逻辑,Vue 自动帮你高效更新页面!
下次写 Vue 时,可以想象自己是在「画设计图」,而 Vue 是那个「高效施工队」! 🛠️
vue项目内多个请求如何让页面不卡顿
在Vue项目中处理多个请求时保持页面流畅,需要从 请求控制、渲染优化 和 交互设计 三个维度进行优化。以下是具体方案:
一、请求控制优化
1. 分批次并发请求(避免一次性全量请求)
// 关键数据优先请求 async function loadCriticalData() {const [user, config] = await Promise.all([fetch('/api/user'),fetch('/api/config')]);// 渲染核心内容 }// 非关键数据延迟加载 setTimeout(() => {fetch('/api/additional-data') }, 2000);
2. 请求优先级队列
class RequestQueue {constructor(maxConcurrent = 3) {this.queue = [];this.activeCount = 0;this.maxConcurrent = maxConcurrent;}add(requestFn) {return new Promise((resolve, reject) => {this.queue.push({ requestFn, resolve, reject });this.next();});}next() {while (this.activeCount < this.maxConcurrent && this.queue.length) {const { requestFn, resolve, reject } = this.queue.shift();this.activeCount++;requestFn().then(resolve).catch(reject).finally(() => {this.activeCount--;this.next();});}} }// 使用示例 const queue = new RequestQueue(4); // 最大并发4个 queue.add(() => fetch('/api/data1')); queue.add(() => fetch('/api/data2'));
二、渲染性能优化
1. 虚拟滚动(大数据列表)
<template><!-- 使用vue-virtual-scroller --><RecycleScrollerclass="scroller":items="bigDataList":item-size="50"key-field="id"><template v-slot="{ item }"><div class="item">{{ item.name }}</div></template></RecycleScroller> </template>
2. 分块渲染
// 分批处理大数据 function chunkRender(data) {let index = 0;const chunkSize = 50;function doChunk() {const chunk = data.slice(index, index + chunkSize);this.items.push(...chunk);index += chunkSize;if (index < data.length) {requestIdleCallback(doChunk); // 利用空闲时间渲染}}requestIdleCallback(doChunk); }
3. 冻结非活跃数据
// 使用Object.freeze避免响应式追踪 this.bigData = Object.freeze(rawData);
三、交互体验优化
1. 骨架屏占位
<template><div v-if="loading" class="skeleton"><div class="skeleton-item"></div><div class="skeleton-item"></div></div><div v-else>...</div> </template><style> .skeleton-item {background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);animation: shimmer 1.5s infinite; }@keyframes shimmer {100% { background-position: -200% 0; } } </style>
2. 请求取消
// 使用axios CancelToken const source = axios.CancelToken.source();fetchData() {axios.get('/api/data', {cancelToken: source.token}).catch(thrown => {if (axios.isCancel(thrown)) {console.log('请求被取消', thrown.message);}}); }// 组件销毁时取消请求 beforeDestroy() {source.cancel('组件卸载取消请求'); }
四、性能监测与调试
1. Chrome Performance分析
// 添加性能标记 window.performance.mark('fetchStart'); await fetchData(); window.performance.mark('fetchEnd'); window.performance.measure('数据请求', 'fetchStart', 'fetchEnd');
2. 长任务检测
const observer = new PerformanceObserver((list) => {for (const entry of list.getEntries()) {console.log('[长任务]', entry);} }); observer.observe({ entryTypes: ['longtask'] });
五、进阶优化方案
方案 适用场景 实现方式 Web Worker 复杂数据处理 将数据计算移出主线程 HTTP/2 Server Push 高并发请求 服务端主动推送资源 请求缓存 重复数据获取 内存缓存 + LocalStorage Tree Shaking 减少打包体积 配置Vue CLI的优化项
六、优化前后性能对比
指标 优化前 优化后 主线程阻塞时间 1200ms 300ms 首次内容渲染 2.5s 800ms 内存占用 150MB 80MB
通过 请求并发控制 + 渲染策略优化 + 交互反馈设计 的组合拳,可有效解决多请求场景下的页面卡顿问题。建议根据实际场景选择2-3个关键优化点组合实施。
本地缓存有哪些?有哪些区别?在Web开发中,常见的本地缓存技术包括 Cookie、Web Storage(LocalStorage/SessionStorage)、IndexedDB、Cache API 等。以下是它们的核心区别及适用场景:
一、本地缓存技术对比
技术 存储容量 生命周期 数据格式 访问方式 适用场景 特点 Cookie ~4KB 可设置过期时间(默认会话级) 字符串键值对 同步 用户会话管理、服务端通信(如Token) 每次请求自动携带,需注意安全性 LocalStorage 5MB~10MB(浏览器) 永久存储(需手动清除) 字符串键值对 同步 长期存储用户偏好、离线数据 简单易用,无自动过期机制 SessionStorage 5MB~10MB(浏览器) 会话级(标签页关闭即清除) 字符串键值对 同步 临时存储表单数据、页面间传参 数据隔离性强(按标签页隔离) IndexedDB 无硬性限制(通常≥250MB) 永久存储(需手动清除) 结构化数据(对象存储) 异步 复杂数据管理(如离线应用、大数据缓存) 支持事务、索引、查询 Cache API 动态分配(通常≥50MB) 随Service Worker生命周期 网络请求/响应 异步 缓存静态资源(PWA离线支持) 精准控制资源缓存策略
二、技术详解与典型应用
1. Cookie
核心特性:
自动随HTTP请求发送到服务端(通过
Cookie
请求头)通过
document.cookie
读写,需手动处理字符串分割必须设置
SameSite
属性防止CSRF攻击适用场景:
用户登录状态保持(JWT Token)
简单的用户偏好记录(如主题选择)
示例:
// 设置Cookie(有效期7天) document.cookie = "theme=dark; max-age=604800; path=/; Secure";
2. Web Storage
LocalStorage:
长期存储:用户语言设置、购物车数据持久化
代码示例:
localStorage.setItem('userSettings', JSON.stringify({ theme: 'dark' })); const settings = JSON.parse(localStorage.getItem('userSettings'));
SessionStorage:
临时存储:多步骤表单数据暂存、页面间参数传递
代码示例:
sessionStorage.setItem('formStep1', JSON.stringify({ name: 'Alice' }));
3. IndexedDB
核心能力:
支持事务(Transaction)保证数据一致性
可创建索引加速查询
存储二进制数据(如图片、文件)
适用场景:
离线应用(如文档编辑器)
大数据量缓存(如本地日志存储)
代码示例:
const request = indexedDB.open('myDB', 1); request.onsuccess = (e) => {const db = e.target.result;const tx = db.transaction('users', 'readwrite');const store = tx.objectStore('users');store.add({ id: 1, name: 'Alice' }); };
4. Cache API
核心机制:
与Service Worker配合实现离线访问
按需缓存网络请求,支持版本管理
适用场景:
PWA应用的静态资源缓存
动态内容离线访问(如新闻详情页)
代码示例:
caches.open('v1').then(cache => {cache.addAll(['/styles.css', '/app.js']); });
三、选型建议
需求场景 推荐方案 原因 用户登录状态管理 Cookie(HttpOnly + Secure) 自动携带至服务端,配合服务端验证安全可靠 长期保存用户主题偏好 LocalStorage 简单键值对,持久化存储 复杂数据查询(如本地数据库) IndexedDB 支持索引、事务,适合结构化数据 离线优先的Web应用 Cache API + Service Worker 精准控制资源缓存策略,提升离线体验 临时存储页面间参数 SessionStorage 会话级隔离,避免数据污染
四、安全注意事项
敏感信息:避免在Cookie/LocalStorage存储密码等机密数据
容量限制:LocalStorage超过5MB可能触发浏览器警告
数据清理:定期清理过期缓存(如通过TTL机制)
加密存储:对敏感数据使用
AES
加密后再存储
通过合理选择本地缓存技术,可显著提升Web应用的性能、离线能力和用户体验。
JS相关面试题
防抖和节流的区别?应该如何去做?
使用防抖(Debounce)和节流(Throttle)是优化高频事件处理的常用手段。
一、防抖和节流的区别
防抖(Debounce) | 节流(Throttle) | |
---|---|---|
触发逻辑 | 事件停止触发后延迟执行 | 固定时间间隔内最多执行一次 |
类比场景 | 电梯门(最后一个人进入后关门) | 水龙头(稳定间隔滴水) |
适用场景 | 输入框搜索联想、窗口resize监听 | 滚动加载、按钮防重复点击 |
执行次数 | 高频触发时只执行最后一次 | 高频触发时均匀执行 |
二、防抖和节流分别实现的方法
// 防抖实现
function debounce(fn, delay = 500) {let timer = null;return function(...args) {if (timer) clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};
}// 节流实现
function throttle(fn, interval = 200) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= interval) {fn.apply(this, args);lastTime = now;}};
}
三、防抖和节流选择原则
-
需要即时响应但避免过量请求用防抖(如搜索建议)
-
需要保持操作连贯性用节流(如无限滚动加载)
js原生input和on-change的区别
在原生 JavaScript 中,input
事件和 change
事件是表单元素(如 <input>
、<select>
、<textarea>
)的两种不同行为的事件,它们的核心区别在于触发时机和应用场景。以下是详细对比:
一、input
事件与 change
事件的区别
特性 | input 事件 | change 事件 |
---|---|---|
触发时机 | 值变化时实时触发(如每次键盘输入) | 失去焦点且值变化后触发 |
适用场景 | 实时搜索、输入校验 | 表单提交前校验、值最终确认 |
兼容性 | 现代浏览器支持 | 所有浏览器支持 |
响应速度 | 高频触发(需防抖优化) | 低频触发 |
二、代码示例对比
<input type="text" id="inputDemo"><script>const inputEl = document.getElementById('inputDemo');// input 事件:每次输入都触发inputEl.addEventListener('input', (e) => {console.log('input 事件:', e.target.value); // 实时输出当前值});// change 事件:失焦且值变化时触发inputEl.addEventListener('change', (e) => {console.log('change 事件:', e.target.value); // 仅失焦后输出最终值});
</script>
测试步骤:
-
在输入框输入 "hello"(每次按键都会触发
input
事件)。 -
点击页面其他区域让输入框失焦,触发
change
事件。 -
再次聚焦输入框,不修改内容直接失焦,不会触发
change
事件(值未变化)。
三、总结
-
input
vschange
:-
需要实时反馈(如搜索联想)用
input
+ 防抖。 -
需要最终确认(如表单提交)用
change
。
-
异步请求有哪些方法?
一、XMLHttpRequest (原生)
底层API,所有现代异步请求的基础
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');// 状态监听
xhr.onreadystatechange = function() {if (xhr.readyState === 4) { // 请求完成if (xhr.status === 200) {console.log(JSON.parse(xhr.responseText));} else {console.error('请求失败:', xhr.status);}}
};// 错误处理
xhr.onerror = function() {console.error('网络错误');
};xhr.send();
特点:
-
✅ 兼容性极好(IE6+)
-
❌ 回调地狱风险
-
❌ 需手动处理JSON解析
二、Fetch API (现代标准)
基于Promise的标准化方案
fetch('https://api.example.com/data', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ key: 'value' })
})
.then(response => {if (!response.ok) throw new Error('HTTP错误:' + response.status);return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('请求失败:', error));
优化技巧:
-
超时控制:
const timeout = (ms) => new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), ms) );Promise.race([fetch(url), timeout(5000)]).then(...);
特点:
-
✅ 原生Promise链式调用
-
❌ 默认不携带Cookie(需配置
credentials: 'include'
) -
❌ 部分旧浏览器需polyfill
三、Axios (主流第三方库)
功能最全面的请求库
import axios from 'axios';// 发起请求
axios.get('https://api.example.com/data', {params: { id: 123 },timeout: 5000
})
.then(response => console.log(response.data))
.catch(error => {if (error.response) {// 服务器响应异常(4xx/5xx)console.log(error.response.status);} else if (error.request) {// 请求已发出但无响应console.log('无响应:', error.request);} else {// 其他错误console.log('错误:', error.message);}
});// 全局配置
axios.defaults.baseURL = 'https://api.example.com';
axios.interceptors.request.use(config => {config.headers.Authorization = localStorage.getItem('token');return config;
});
核心优势:
-
✅ 自动JSON转换
-
✅ 请求/响应拦截器
-
✅ 并发控制:
axios.all()
-
✅ 取消请求:
CancelToken
四、async/await (终极异步方案)
用同步写法处理异步(需配合Fetch/Axios)
async function fetchData() {try {const response = await fetch('https://api.example.com/data');const data = await response.json();console.log(data);} catch (error) {console.error('请求失败:', error);}
}// 并行请求
async function fetchMulti() {const [user, posts] = await Promise.all([fetch('/user'),fetch('/posts')]);
}
五、WebSocket (双向实时通信)
非HTTP协议的持久化连接
const socket = new WebSocket('wss://api.example.com/ws');socket.onopen = () => {socket.send(JSON.stringify({ type: 'join' }));
};socket.onmessage = (event) => {console.log('收到消息:', event.data);
};socket.onclose = () => {console.log('连接关闭');
};
六、技术选型对比表
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
XMLHttpRequest | 兼容IE的低要求项目 | 无需外部依赖 | 代码冗长、回调地狱 |
Fetch API | 现代浏览器项目 | 原生支持、Promise化 | 错误处理不够直观 |
Axios | 企业级复杂应用 | 功能全面、拦截器机制 | 需额外引入库 |
WebSocket | 实时聊天、股票行情 | 双向实时通信 | 需后端配合协议支持 |
七、最佳实践建议
-
首选Axios:复杂项目用拦截器统一处理鉴权/错误
-
SSR兼容:服务端渲染使用
isomorphic-fetch
或axios
-
性能优化:
-
请求去重:同一API短时间内只发一次
-
缓存策略:
Cache-Control
+ 本地缓存
-
-
安全防护:
-
CSRF Token自动携带
-
请求体签名防篡改
-
掌握这些方法后,可根据项目需求灵活选择最合适的异步通信方案!
js循环机制
JavaScript 的循环机制(事件循环)就像一家繁忙的餐厅,用三步就能理解它的工作原理:
一、餐厅厨房的比喻
角色 | 对应 JavaScript 概念 | 作用 |
---|---|---|
厨师👨🍳 | 主线程(JS 引擎) | 一次只能炒一个菜(单线程) |
服务员💁♀️ | 事件循环(Event Loop) | 协调谁先上菜(任务调度) |
煮面锅🍜 | 异步任务(setTimeout、网络请求等) | 需要等待的操作 |
出菜口📤 | 任务队列(Task Queue) | 存放煮好的面(完成的任务) |
二、工作流程(三步走)
1. 先做马上能出餐的菜(同步任务)
-
场景:顾客点了一份炒饭(同步任务)和一碗需要煮的面(异步任务)
-
操作:
console.log('开始炒饭'); // 同步任务,立即执行 setTimeout(() => { // 异步任务,交给"煮面锅"console.log('面煮好了'); }, 2000); console.log('继续做其他菜'); // 继续执行同步任务
-
结果输出:
开始炒饭 继续做其他菜 (等待2秒...) 面煮好了
2. 煮好的面要排队(任务队列)
-
煮面过程:
-
厨师把锅交给后厨(浏览器其他线程处理)
-
继续做其他菜(执行后续代码)
-
面煮好后,服务员把面放到出菜口(任务队列)
-
3. 服务员按规则上菜(事件循环)
-
规则:
-
VIP 订单先处理(微任务队列)
比如:Promise
的回调(.then()
)、MutationObserver
Promise.resolve().then(() => {console.log('VIP 面条加急处理!'); });
-
普通订单后处理(宏任务队列)
比如:setTimeout
、setInterval
、用户点击事件
-
-
完整流程:
1. 做完所有同步任务(炒饭) 2. 立即处理所有 VIP 订单(微任务) 3. 如果有新 VIP 订单,继续处理直到清空 4. 上一个宏任务结束,取一个普通订单(如煮面回调) 5. 重复这个循环...
三、真实代码示例
console.log('开始点餐'); // 同步任务// 宏任务(普通订单)
setTimeout(() => {console.log('您的面煮好了');
}, 0);// 微任务(VIP 订单)
Promise.resolve().then(() => {console.log('VIP 小菜已赠送');
});console.log('继续服务其他顾客'); // 同步任务
输出顺序:
开始点餐 继续服务其他顾客 VIP 小菜已赠送 您的面煮好了
四、为什么这样设计?
-
不卡顿:厨师(主线程)不会傻等煮面(异步任务),可以继续干活
-
高效:VIP 订单(微任务)能插队处理,保证高优先级任务快速响应
-
有序:所有任务排队处理,不会出现混乱
下次看到 Promise
和 setTimeout
时,想想这个餐厅模型就明白了! 🍔 → 🍜 → 💁♀️ → 🔁
uniapp相关面试题
uniapp制作的app想上传文件pdf如何实现?
一、完整实现流程
选择文件 → 2. 校验文件 → 3. 显示预览 → 4. 上传服务器 → 5. 处理结果
二、核心代码实现(全平台兼容版)
<template><view class="container"><!-- 上传按钮 --><button @click="selectPDF" type="primary">选择PDF文件</button><!-- 文件信息展示 --><view v-if="fileInfo" class="file-info"><text>文件名:{{ fileInfo.name }}</text><text>大小:{{ (fileInfo.size / 1024 / 1024).toFixed(2) }}MB</text><button @click="startUpload" type="primary">开始上传</button></view><!-- 进度条 --><progress v-if="progress > 0" :percent="progress" show-info /></view> </template><script> export default {data() {return {fileInfo: null, // 文件信息progress: 0, // 上传进度uploadTask: null // 上传任务对象(用于取消上传)}},methods: {// 1. 选择PDF文件async selectPDF() {try {const res = await uni.chooseFile({count: 1,type: 'file',extension: ['pdf'],sourceType: ['album', 'camera'] // 相册或文件系统})// 获取文件信息this.fileInfo = {path: res.tempFiles[0].path,name: res.tempFiles[0].name,size: res.tempFiles[0].size}// 预览文件(仅App可用)if (uni.getSystemInfoSync().platform === 'android' || uni.getSystemInfoSync().platform === 'ios') {uni.openDocument({filePath: this.fileInfo.path,fileType: 'pdf'})}} catch (err) {uni.showToast({ title: '选择文件失败', icon: 'none' })}},// 2. 上传文件async startUpload() {if (!this.fileInfo) return// 校验文件大小(示例限制20MB)if (this.fileInfo.size > 20 * 1024 * 1024) {uni.showToast({ title: '文件不能超过20MB', icon: 'none' })return}this.progress = 0try {this.uploadTask = uni.uploadFile({url: 'https://your-api.com/upload',filePath: this.fileInfo.path,name: 'file',formData: {userId: '123',timestamp: Date.now()},success: (res) => {if (res.statusCode === 200) {const data = JSON.parse(res.data)uni.showToast({ title: '上传成功', icon: 'success' })console.log('服务器返回:', data)} else {uni.showToast({ title: `上传失败:${res.errMsg}`, icon: 'none' })}},fail: (err) => {console.error('上传失败:', err)uni.showToast({ title: '上传失败', icon: 'none' })},complete: () => {this.uploadTask = null}})// 监听进度this.uploadTask.onProgressUpdate((res) => {this.progress = res.progress})} catch (err) {uni.showToast({ title: '上传异常', icon: 'none' })}},// 3. 取消上传cancelUpload() {if (this.uploadTask) {this.uploadTask.abort()uni.showToast({ title: '已取消上传', icon: 'none' })}}} } </script><style> .container {padding: 20px; } .file-info {margin-top: 20px;padding: 15px;background-color: #f5f5f5;border-radius: 5px; } </style>
三、关键配置说明
1. 平台适配要点
平台 配置项 Android 需在 manifest.json
中添加权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
iOS 需在 manifest.json
中配置:
"ios" : { "permission" : { "NSPhotoLibraryUsageDescription" : "需要访问相册选择文件" } }
所有平台 使用 uni.chooseFile
统一接口,无需处理平台差异2. 文件选择参数优化
// 更严格的文件类型校验 const isPDF = file.type === 'application/pdf' || file.name.endsWith('.pdf') || /\.pdf$/i.test(file.name)// 获取文件真实路径(App专用) if (plus.io) {file.realPath = plus.io.convertLocalFileSystemURL(file.path) }
四、服务器端示例(Node.js + Express)
const express = require('express') const multer = require('multer') const path = require('path') const app = express()// 配置文件存储 const storage = multer.diskStorage({destination: (req, file, cb) => {cb(null, path.join(__dirname, 'uploads'))},filename: (req, file, cb) => {cb(null, `${Date.now()}-${file.originalname}`)} })// 文件过滤 const fileFilter = (req, file, cb) => {if (file.mimetype === 'application/pdf') {cb(null, true)} else {cb(new Error('只允许上传PDF文件'), false)} }const upload = multer({ storage,fileFilter,limits: { fileSize: 20 * 1024 * 1024 } // 限制20MB })// 上传接口 app.post('/upload', upload.single('file'), (req, res) => {console.log('接收到文件:', req.file)console.log('附加参数:', req.body)res.json({code: 200,message: '上传成功',data: {filename: req.file.filename,originalname: req.file.originalname,size: req.file.size,downloadUrl: `/download/${req.file.filename}`}}) })// 启动服务 app.listen(3000, () => {console.log('服务器运行在 http://localhost:3000') })
五、增强功能实现
1. 断点续传(App端)
// 获取文件分片 const fileManager = plus.io.getFileSystemManager() fileManager.readFile({filePath: file.path,position: 0,length: chunkSize,success: (res) => {const chunk = res.data// 上传分片逻辑...} })
2. 文件加密上传
// 使用crypto-js加密 import CryptoJS from 'crypto-js'const encryptFile = (file) => {const wordArray = CryptoJS.lib.WordArray.create(file)return CryptoJS.AES.encrypt(wordArray, 'secret-key').toString() }
3. 七牛云/OSS直传
// 获取服务端签名后直传 uni.request({url: 'https://your-api.com/get-oss-token',success: (res) => {const ossConfig = res.datauni.uploadFile({url: ossConfig.host,filePath: this.fileInfo.path,name: 'file',formData: {key: ossConfig.key,policy: ossConfig.policy,OSSAccessKeyId: ossConfig.accessid,signature: ossConfig.signature}})} })
六、常见问题解决方案
1. Android文件路径问题
// 转换真实路径 if (this.fileInfo.path.startsWith('file://')) {this.fileInfo.path = this.fileInfo.path.replace('file://', '') }
2. iOS无法选择文件
检查
manifest.json
是否正确配置:"ios" : {"permission" : {"NSPhotoLibraryUsageDescription" : "需要访问相册选择文件","NSPhotoLibraryAddUsageDescription" : "需要保存文件到相册"} }
3. 大文件上传失败
分片上传(每5MB一个分片)
显示进度和暂停/继续功能
后台服务需支持分片合并
七、性能优化建议
前端压缩:使用
pdf-lib
等库压缩PDF缓存机制:已上传文件记录MD5避免重复上传
队列控制:多个文件时限制并发上传数
uni-app实现扫码的几种方式
1、官方扫码接口
优点:uni.scanCode(OBJECT)就可以直接调用,方便快捷,支持安卓、ios,如果制作小程序,便可以直接转换成微信等官方扫码api。
缺点:安卓、ios端识别率非常差,并且扫码耗时很长,很多条件下,无法识别二维码。2、支付宝扫码插件
直接在插件市场就可以下载使用
优点:免费,并且识别率非常高
缺点:需要配置阿里云控制台,而且阿里在控制台有初始埋点,第一次使用如果没有关闭,会直接短信通知欠费,不能离线扫码,数据安全性不详。3、华为扫码插件
直接在插件市场就可以下载使用
优点:识别率不错,可以离线扫码
缺点:需要付费88元,我在测试阶段扫码扫出空字符,导致数据传值失败,需要在扫码获取的值做一些处理,下面附上处理 方式。this.invoicedata = ret.sValue.replace(/[\xa0\x00-\x09\x0b\x0c\x0e-\x1f\x7f]/g, '');
4、微信扫码
优点:免费,识别率高
缺点:目前只支持小程序,没有开放接口。这道答案来源于uni-app实现扫码的几种方式_uniapp scancode-CSDN博客
CSS相关面试题
PC网页转h5如何响应式布局?
在将 PC 网页转换为 H5 响应式布局时,需遵循移动优先原则并兼顾多端适配。以下是完整实现方案:
一、核心布局策略
<!-- 基础视口配置 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><!-- 移动端专用 meta -->
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
1. 弹性容器布局
.container {width: 100%;max-width: 1200px; /* PC端最大宽度限制 */margin: 0 auto;padding: 0 15px; /* 移动端安全边距 */display: flex;flex-wrap: wrap; /* 允许换行适应小屏 */
}
2. 网格系统实现
/* 12列响应式网格 */
.col {flex: 0 0 100%; /* 移动端默认占满宽度 */padding: 10px;
}@media (min-width: 768px) {.col-md-6 {flex: 0 0 50%; /* 平板分两列 */}
}@media (min-width: 992px) {.col-lg-3 {flex: 0 0 25%; /* PC端分四列 */}
}
二、元素级适配方案
1. 字体响应式
:root {font-size: 14px; /* 基准字号 */
}@media (min-width: 768px) {:root {font-size: 16px; /* 中屏增大字号 */}
}h1 {font-size: 2rem; /* 28px -> 32px */line-height: 1.3;
}
2. 图片适配
<picture><!-- 移动端优先加载小图 --><source media="(max-width: 767px)" srcset="mobile.jpg"><!-- 平板 --><source media="(max-width: 991px)" srcset="tablet.jpg"><!-- PC端 --><img src="desktop.jpg" alt="示例" style="width:100%; height:auto;">
</picture>
3. 表格优化
.table-wrapper {overflow-x: auto; /* 横向滚动 */-webkit-overflow-scrolling: touch;
}table {min-width: 600px; /* 保持表格最小可读宽度 */
}
三、交互适配技巧
1. 导航栏改造
<!-- 移动端汉堡菜单 -->
<button class="menu-toggle">☰</button>
<nav class="main-nav"><a href="#">首页</a><a href="#">产品</a>
</nav><style>
.menu-toggle {display: none; /* PC端隐藏 */
}@media (max-width: 767px) {.menu-toggle {display: block; /* 移动端显示 */}.main-nav {display: none;position: fixed;top: 0;left: 0;width: 100%;background: white;}.main-nav.active {display: block;}
}
</style>
2. 表单优化
input[type="text"],
input[type="email"] {width: 100%;height: 40px;padding: 8px;font-size: 16px; /* 避免移动端缩放 */border: 1px solid #ddd;
}
四、性能优化专项
1. 资源按需加载
<!-- 移动端不加载大图 -->
<div class="hero-image" data-src="desktop-bg.jpg" data-mobile-src="mobile-bg.jpg"></div><script>
function loadAdaptiveImage() {const images = document.querySelectorAll('[data-mobile-src]');images.forEach(img => {const src = window.innerWidth < 768 ? img.dataset.mobileSrc : img.dataset.src;img.style.backgroundImage = `url(${src})`;});
}
window.addEventListener('resize', loadAdaptiveImage);
</script>
2. 触控反馈优化
.button {min-width: 44px; /* 最小点击区域 */min-height: 44px;padding: 12px 24px;transition: opacity 0.3s;
}.button:active {opacity: 0.7; /* 按压反馈 */
}
五、多端调试方案
1. Chrome 设备模拟
-
打开 DevTools → Device Toolbar
-
测试不同DPR(Device Pixel Ratio)
2. 真机调试
# 本地服务绑定IP
npm run dev -- --host 0.0.0.0# 手机访问电脑IP:端口
3. 自动化测试工具
// 使用Puppeteer多分辨率截图
const puppeteer = require('puppeteer');async function testResponsive() {const browser = await puppeteer.launch();const page = await browser.newPage();const devices = [{name: 'mobile', width: 375, height: 667},{name: 'tablet', width: 768, height: 1024},{name: 'desktop', width: 1200, height: 800}];for (let device of devices) {await page.setViewport(device);await page.goto('http://localhost:3000');await page.screenshot({path: `screenshot-${device.name}.png`});}await browser.close();
}
testResponsive();
六、常见问题解决
-
1px边框问题
.border {position: relative;
}
.border::after {content: '';position: absolute;left: 0;bottom: 0;width: 100%;height: 1px;background: #ddd;transform: scaleY(0.5);
}
-
fixed定位抖动
.header {position: fixed;top: 0;width: 100%;/* 开启GPU加速 */transform: translateZ(0);backface-visibility: hidden;
}
通过以上方案,可实现从PC到H5的高质量响应式适配,兼顾视觉效果与交互体验。核心原则是:弹性布局打底 + 媒体查询微调 + 移动优先交互。
css想做动画有哪些方法?
在 CSS 中实现动画主要有以下几种核心方法,每种方法都有其适用场景和特性:
一、基础动画方法
1. Transition(过渡动画)
-
用途:元素属性变化时的平滑过渡(如 hover 效果)
-
代码示例:
.box {width: 100px;transition: width 0.3s ease-in-out; } .box:hover {width: 200px; }
-
特点:
-
只能定义开始和结束状态
-
支持属性:
transform
、opacity
、color
等可过渡属性 -
性能优化:优先使用
transform
和opacity
(触发 GPU 加速)
-
2. Keyframes Animation(关键帧动画)
-
用途:复杂多阶段动画(如旋转、闪烁)
-
代码示例:
@keyframes rotate {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); } }.spinner {animation: rotate 2s linear infinite; }
-
核心属性:
animation: name duration timing-function delay iteration-count direction fill-mode;
-
高级技巧:
-
使用
steps()
函数实现逐帧动画:@keyframes walk {from { background-position: 0 0; }to { background-position: -1600px 0; } } .character {animation: walk 1s steps(8) infinite; }
-
二、性能优化技巧
1. 硬件加速
.animate {transform: translateZ(0); /* 或 will-change: transform; */
}
2. 限制重绘区域
.container {contain: strict; /* 限制浏览器重绘范围 */
}
3. 避免布局抖动
-
优先使用
transform
替代top/left
位移 -
使用
requestAnimationFrame
同步动画(JS 辅助)
三、进阶动画方案
1. CSS 变量驱动动画
:root {--rotate: 0deg;
}.box {transform: rotate(var(--rotate));transition: transform 0.3s;
}/* JS 控制动画 */
box.style.setProperty('--rotate', '45deg');
2. SVG 动画
<svg viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"><animate attributeName="r" from="40" to="20" dur="1s" repeatCount="indefinite"/></circle>
</svg>
3. 动画库扩展
-
Animate.css(预设动画库):
<div class="animate__animated animate__bounce"></div>
-
Motion One(现代轻量库):
import { animate } from "motion"; animate(".box", { x: 100 }, { duration: 0.3 });
四、方案对比表
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Transition | 简单状态过渡 | 简单高效、性能好 | 无法控制中间状态 |
Keyframes | 复杂多阶段动画 | 完全控制动画流程 | 代码量较大 |
CSS 变量 | 动态控制动画参数 | 灵活、易于JS交互 | 兼容性要求 IE11+ |
SVG 动画 | 矢量图形动画 | 分辨率无关、路径动画 | 学习成本较高 |
动画库 | 快速实现复杂效果 | 开箱即用、效果丰富 | 增加项目体积 |
五、调试工具
-
Chrome DevTools 动画面板:
-
查看动画时间轴
-
修改动画参数实时预览
-
-
性能监测:
-
使用 Performance 面板分析帧率
-
检查 Layout/Paint 耗时
-
六、最佳实践建议
-
优先使用 CSS 原生动画(而非 JS 动画)
-
限制同时运行的动画数量(一般不超过 5 个)
-
移动端优化:
-
动画时长建议 200-500ms
-
使用
@media (prefers-reduced-motion)
适配无障碍
@media (prefers-reduced-motion: reduce) {* { animation: none !important; } }
-
掌握这些方法后,可根据具体需求选择最合适的动画实现方案!🎉
其他综合问题
如何不让网页上的水印被去除?
在PC端页面实现防篡改水印需要综合多种技术手段,以下是一套较为全面的解决方案:
一、动态Canvas水印方案
function createDynamicWatermark(text) {const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');canvas.width = 300;canvas.height = 200;// 绘制基础水印ctx.font = '14px Arial';ctx.fillStyle = 'rgba(200,200,200,0.2)';ctx.rotate(-20 * Math.PI / 180);ctx.fillText(text, 10, 100);// 添加防伪波纹for(let i=0;i<canvas.width;i+=30){ctx.beginPath();ctx.moveTo(i, 0);ctx.lineTo(i, canvas.height);ctx.strokeStyle = 'rgba(0,0,0,0.02)';ctx.stroke();}// 生成动态背景图const dataUrl = canvas.toDataURL();const watermarkDiv = document.createElement('div');watermarkDiv.style.cssText = `position: fixed;top: 0;left: 0;width: 100%;height: 100%;pointer-events: none;z-index: 999999;background: url(${dataUrl}) left top repeat;mix-blend-mode: multiply;`;// 防删除保护watermarkDiv.setAttribute('id', 'secure-watermark');Object.defineProperty(watermarkDiv, 'id', {configurable: false,writable: false});document.body.appendChild(watermarkDiv); }
二、防篡改保护机制
DOM变动监控
const observer = new MutationObserver(mutations => {mutations.forEach(mutation => {Array.from(mutation.removedNodes).forEach(node => {if (node.id === 'secure-watermark') {document.body.appendChild(node);}});}); });observer.observe(document.body, {childList: true,subtree: true });
样式篡改防御
const styleGuard = setInterval(() => {const watermark = document.getElementById('secure-watermark');if (!watermark) {createDynamicWatermark();return;}const styles = window.getComputedStyle(watermark);const protectedProps = {'display': 'block','opacity': '1','visibility': 'visible'};Object.entries(protectedProps).forEach(([prop, value]) => {if (styles[prop] !== value) {watermark.style[prop] = value;}}); }, 1000);
三、用户行为监控
window.addEventListener('keydown', (e) => {// 屏蔽开发者工具快捷键const devtoolKeys = [e.ctrlKey && e.keyCode === 85, // Ctrl+Ue.ctrlKey && e.shiftKey && e.keyCode === 73, // Ctrl+Shift+Ie.keyCode === 123 // F12];if (devtoolKeys.some(Boolean)) {e.preventDefault();alert('安全警告:禁止开发者工具操作!');window.location.reload();} });// 防止右键检查 document.addEventListener('contextmenu', (e) => {e.preventDefault();return false; });// 防截图干扰 const noiseCanvas = document.createElement('canvas'); noiseCanvas.style.cssText = `position: fixed;top:0;left:0;pointer-events: none;z-index: 9999999; `; document.body.appendChild(noiseCanvas);function generateNoise() {const ctx = noiseCanvas.getContext('2d');const imageData = ctx.createImageData(noiseCanvas.width, noiseCanvas.height);const data = imageData.data;for(let i=0;i<data.length;i+=4){const val = Math.random()*255;data[i] = data[i+1] = data[i+2] = val;data[i+3] = 5; }ctx.putImageData(imageData, 0, 0);requestAnimationFrame(generateNoise); }noiseCanvas.width = window.innerWidth; noiseCanvas.height = window.innerHeight; generateNoise();
四、服务端配合方案
动态水印信息
// 每次请求携带动态token async function getWatermarkText() {const res = await fetch('/api/watermark-token');const { token } = await res.json();return `机密 ${token} ${Date.now()}`; }// 定期更新水印 setInterval(async () => {const newText = await getWatermarkText();updateWatermark(newText); }, 300000);
内容加密验证
// 使用CryptoJS进行内容签名 const contentHash = CryptoJS.HmacSHA256(document.body.innerText, 'secretKey' ).toString();// 后台校验示例 app.post('/verify-content', (req, res) => {const clientHash = req.body.hash;const serverHash = CryptoJS.HmacSHA256(req.body.content,'secretKey').toString();if(clientHash !== serverHash) {// 触发内容篡改警报sendAlert('检测到内容篡改!');} });
五、防御等级说明
防御层级 技术手段 防御能力 初级防御 基础CSS水印 ★☆☆☆☆ 中级防御 Canvas动态水印+DOM监控 ★★★☆☆ 高级防御 行为监控+服务端验证 ★★★★☆ 终极防御 硬件级DRM方案 ★★★★★ 注意事项:
定期更新水印算法(建议每月迭代)
结合业务日志记录用户操作行为
重要数据建议使用PDF等不可逆格式展示
法律层面增加水印声明条款
任何前端方案都无法做到绝对防篡改,建议根据业务安全等级选择适当方案,关键数据应结合后端验证和权限控制。
HTTP和HTTPS的区别
HTTP(超文本传输协议)和 HTTPS(安全超文本传输协议)的核心区别在于数据传输的安全性,以下是两者的详细对比:
一、核心区别总结
特性 HTTP HTTPS 安全性 明文传输,数据可被窃听/篡改 加密传输,防窃听/防篡改 协议 应用层协议 HTTP + SSL/TLS 安全层 默认端口 80 443 证书 无需证书 需CA机构颁发的SSL证书 性能 无加密开销,速度稍快 有加密计算开销,但现代硬件影响可忽略 SEO 搜索引擎可能降权 搜索引擎优先收录(如Google明确支持)
二、技术实现差异
1. 加密机制(核心)
HTTP:
数据以明文形式传输,可直接被中间人读取:GET /login HTTP/1.1 Host: example.com username=admin&password=123456 ← 明文传输!
HTTPS:
通过SSL/TLS协议加密数据,传输内容为密文:���]�k�M�I�4��Q�V��x�d� ← 加密后的乱码数据
2. SSL/TLS工作流程
握手阶段(非对称加密):
客户端验证服务器证书有效性
协商加密算法(如RSA、ECDHE)
生成会话密钥(Session Key)
数据传输(对称加密):
使用会话密钥加密通信内容
三、HTTPS核心优势
1. 防窃听(Encryption)
所有数据加密传输,即使被截获也无法解密
示例:公共WiFi下登录账号时,HTTPS可防止密码泄露
2. 防篡改(Integrity)
使用MAC(消息认证码)验证数据完整性
示例:阻止运营商插入广告代码
3. 身份认证(Authentication)
SSL证书验证网站真实身份,防止钓鱼网站
示例:浏览器地址栏显示锁标志和公司名称
四、部署HTTPS的要求
1. SSL证书类型
证书类型 验证等级 适用场景 DV(域名验证) 验证域名所有权 个人博客、小型网站 OV(组织验证) 验证企业/组织身份 企业官网 EV(扩展验证) 严格身份验证(地址栏绿标) 银行、电商等敏感领域 2. 部署步骤
购买或申请免费证书(如Let's Encrypt)
在服务器安装证书(Nginx示例):
server {listen 443 ssl;ssl_certificate /path/to/cert.pem;ssl_certificate_key /path/to/private.key; }
强制HTTP跳转HTTPS:
server {listen 80;return 301 https://$host$request_uri; }
五、性能优化技巧
会话复用(Session Resumption)
减少SSL握手次数,降低延迟:ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;
启用HTTP/2
HTTPS下可启用HTTP/2协议提升性能:listen 443 ssl http2;
六、常见误解澄清
误区1:"HTTPS会显著拖慢网站速度"
→ 现代服务器性能下,TLS握手开销仅增加约1 RTT(经优化可降至0 RTT)误区2:"只有支付页面需要HTTPS"
→ 现代Web标准(如PWA、Geolocation API)要求全站HTTPS误区3:"HTTPS无法被劫持"
→ 若客户端安装伪证书(如企业监控),仍可能被中间人攻击
七、如何验证HTTPS安全性
浏览器地址栏显示锁形图标
通过SSL Labs测试检查配置:
Rating: A+ (最佳评级)
总结:HTTPS通过加密和身份验证构建安全传输通道,是现代Web应用的必备基础,所有新建网站都应默认启用HTTPS。
前端浏览器安全如何注意?
为了保障前端浏览器的安全性,需要从代码防护、数据传输、用户输入处理等多方面入手。以下是系统化的安全防护方案及具体实施方法:
一、基础防护体系
1. 启用HTTPS强制加密
# Nginx配置(强制跳转HTTPS) server {listen 80;server_name example.com;return 301 https://$host$request_uri; }
附加措施:
使用HSTS头(强制浏览器使用HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
2. 安全头配置
# 安全响应头示例 add_header X-Frame-Options "SAMEORIGIN"; # 防点击劫持 add_header X-Content-Type-Options "nosniff"; # 禁止MIME嗅探 add_header Referrer-Policy "strict-origin-when-cross-origin"; # 控制Referer泄露 add_header Permissions-Policy "geolocation=()"; # 禁用不必要权限
二、攻击防御专项
1. XSS(跨站脚本攻击)防御
输入过滤:
// 使用DOMPurify过滤HTML import DOMPurify from 'dompurify'; const cleanHTML = DOMPurify.sanitize(userInput);
输出转义:
<!-- Vue自动转义 --> <div>{{ userContent }}</div><!-- 手动转义 --> <div v-html="safeHTML"></div>
CSP策略:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'">
2. CSRF(跨站请求伪造)防御
Token验证:
// 后端生成Token const csrfToken = crypto.randomBytes(32).toString('hex'); res.cookie('XSRF-TOKEN', csrfToken);// 前端发送请求携带Token axios.defaults.headers.common['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
SameSite Cookie:
// 设置Cookie属性 res.cookie('sessionID', '123', { sameSite: 'Strict', secure: true });
3. 点击劫持防御
add_header X-Frame-Options "DENY"; # 完全禁止嵌入 # 或 add_header Content-Security-Policy "frame-ancestors 'self'";
三、数据安全防护
1. 敏感数据处理
前端加密:
// 使用Web Crypto API加密 const encryptedData = await crypto.subtle.encrypt({ name: 'AES-GCM', iv },key,new TextEncoder().encode(data) );
安全存储:
// 避免localStorage存储敏感信息 sessionStorage.setItem('tempData', encryptedData);
2. 第三方资源校验
<!-- 使用SRI校验CDN资源 --> <script src="https://cdn.example.com/jquery.js" integrity="sha384-..."> </script>
四、用户输入与认证
1. 输入验证
// 邮箱格式校验 const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);// 防止SQL注入 const sanitizeInput = (input) => input.replace(/['"\\;]/g, '');
2. 认证安全
密码策略:
// 前端密码强度校验 const isStrongPassword = (pwd) => pwd.length >= 8 && /[A-Z]/.test(pwd) && /[0-9]/.test(pwd);
双因素认证:
// 集成Google Authenticator import { authenticator } from 'otplib'; const secret = authenticator.generateSecret(); const token = authenticator.generate(secret);
五、持续监控与更新
1. 依赖安全扫描
# 使用npm audit检查漏洞 npm audit# 使用Snyk深度检测 npx snyk test
2. 实时监控
错误追踪:接入Sentry监控XSS等异常
入侵检测:部署WAF(Web应用防火墙)
六、安全等级对照表
安全等级 适用场景 防护措施 基础级 小型展示网站 HTTPS + CSP + XSS过滤 标准级 企业级应用 基础级 + CSRF Token + 输入验证 + 安全头 高级级 金融/政务系统 标准级 + 双因素认证 + 实时监控 + 前端加密 + 人工渗透测试
七、应急响应方案
漏洞响应流程:
发现漏洞 → 评估影响 → 热修复 → 通知用户
数据泄露处理:
立即重置相关凭证
启用日志审计追溯攻击路径
根据法律要求通知受影响用户
通过以上综合措施,可构建多层次的前端安全防护体系。关键点在于:默认安全原则 + 纵深防御 + 持续监控,同时需结合具体业务场景调整防护策略。
PC端开发大文件如何上传?卡住了怎么办?失败了后如何继续上传?在前端PC端实现大文件上传并处理中断和续传的问题,可以采用分块上传与断点续传的策略。以下是详细的解决方案:
1. 分块上传(Chunked Upload)
将大文件分割为多个小块(如1MB/块),分别上传,降低单次请求压力,便于失败后重试。
实现步骤:
前端分块处理:
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB const file = document.getElementById('file-input').files[0]; const chunks = []; let start = 0;while (start < file.size) {const chunk = file.slice(start, start + CHUNK_SIZE);chunks.push(chunk);start += CHUNK_SIZE; }
生成文件唯一标识:
使用文件内容的哈希(如SHA-256)作为唯一标识,避免同名文件冲突。async function computeFileHash(file) {const buffer = await file.arrayBuffer();const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join(''); }
上传单个块:
async function uploadChunk(chunk, chunkIndex, fileHash) {const formData = new FormData();formData.append('chunk', chunk);formData.append('chunkIndex', chunkIndex);formData.append('fileHash', fileHash);formData.append('totalChunks', totalChunks);await fetch('/upload-chunk', {method: 'POST',body: formData,}); }
2. 断点续传(Resumable Upload)
记录已上传的块,失败后仅上传缺失部分。
实现步骤:
查询服务器已上传的块:
async function getUploadedChunks(fileHash) {const response = await fetch(`/uploaded-chunks?fileHash=${fileHash}`);return await response.json(); // 返回已上传的块索引数组,如[0,1,2] }
过滤未上传的块并继续上传:
const uploadedChunks = await getUploadedChunks(fileHash); const chunksToUpload = chunks.filter((_, index) => !uploadedChunks.includes(index));// 控制并发上传(如最多5个并行) const MAX_CONCURRENT = 5; const queue = chunksToUpload.map((chunk, index) => () => uploadChunk(chunk, index, fileHash));// 使用p-limit库控制并发 import pLimit from 'p-limit'; const limit = pLimit(MAX_CONCURRENT); await Promise.all(queue.map(task => limit(task)));
3. 失败处理与重试
网络错误重试: 为每个块的上传添加重试机制。
async function uploadWithRetry(chunk, chunkIndex, fileHash, retries = 3) {try {await uploadChunk(chunk, chunkIndex, fileHash);} catch (error) {if (retries > 0) {await uploadWithRetry(chunk, chunkIndex, fileHash, retries - 1);} else {throw error;}} }
服务端错误处理: 返回明确的错误状态码,前端根据状态码决定重试或报错。
4. 服务器端实现
关键接口:
上传块接口(/upload-chunk):
接收块文件、块索引、文件哈希。
将块保存到临时目录(按文件哈希组织)。
查询已上传块接口(/uploaded-chunks):
根据文件哈希返回已上传的块索引列表。
合并文件接口(/merge-file):
所有块上传完成后,按顺序合并成完整文件。
示例伪代码(Node.js):
// 保存块 app.post('/upload-chunk', (req, res) => {const { chunk, chunkIndex, fileHash } = req.body;const chunkDir = `./temp/${fileHash}`;fs.mkdirSync(chunkDir, { recursive: true });fs.writeFileSync(`${chunkDir}/${chunkIndex}`, chunk.buffer);res.status(200).send('Chunk uploaded'); });// 合并文件 app.post('/merge-file', (req, res) => {const { fileHash, fileName } = req.body;const chunkDir = `./temp/${fileHash}`;const chunks = fs.readdirSync(chunkDir).sort((a, b) => a - b);const filePath = `./uploads/${fileName}`;chunks.forEach(chunk => {fs.appendFileSync(filePath, fs.readFileSync(`${chunkDir}/${chunk}`));});fs.rmdirSync(chunkDir, { recursive: true }); // 清理临时文件res.status(200).send('File merged'); });
5. 用户体验优化
进度显示: 监听每个块的上传进度,计算总进度。
let uploadedSize = 0; chunks.forEach((chunk, index) => {uploadChunk(chunk, index).then(() => {uploadedSize += chunk.size;const progress = (uploadedSize / file.size) * 100;updateProgressBar(progress);}); });
暂停/恢复: 使用
AbortController
取消请求,保存已上传状态。const controllers = new Map();function uploadChunk(chunk, index) {const controller = new AbortController();controllers.set(index, controller);return fetch(url, {signal: controller.signal,// ...}); }// 暂停上传 function pauseUpload() {controllers.forEach(controller => controller.abort()); }
6. 注意事项
文件一致性校验: 合并后对比服务器端文件的哈希与前端计算的哈希。
清理临时文件: 设置定时任务清理未完成的过期上传。
分块大小调整: 根据网络情况动态调整块大小(如1MB-5MB)。
通过上述方案,可有效解决大文件上传中的中断、续传和失败重试问题,提升用户体验。
如何记录接口报错并且展示?在前端中记录接口报错并展示错误信息,是提升用户体验和排查问题的重要手段。以下是分步骤的实现方案:
一、错误捕获与记录
1. 使用请求拦截器(以 Axios 为例)
// 创建 axios 实例 const instance = axios.create();// 响应拦截器 instance.interceptors.response.use((response) => response,(error) => {const { config, response } = error;const errorInfo = {time: new Date(),url: config?.url,method: config?.method,status: response?.status,message: response?.data?.message || error.message,};// 记录错误(可扩展为上报服务器)logError(errorInfo);// 展示错误(可选,部分错误可能不需要立即展示)showErrorUI(errorInfo);return Promise.reject(error);} );
2. 错误日志记录方式
本地存储(临时记录)
function logError(error) {const MAX_LOG_SIZE = 50;const logs = JSON.parse(localStorage.getItem('api_errors') || []);logs.push(error);if (logs.length > MAX_LOG_SIZE) logs.shift();localStorage.setItem('api_errors', JSON.stringify(logs)); }
上报到服务器(推荐生产环境使用)
function reportErrorToServer(error) {navigator.sendBeacon('/api/logs', JSON.stringify(error)); }
二、错误展示策略
1. 通用错误提示组件(React 示例)
import { Toast } from 'antd-mobile';function showErrorUI(error) {let message = '请求失败,请稍后重试';// 根据错误类型定制提示if (error.status === 401) {message = '登录已过期,请重新登录';redirectToLogin();} else if (error.status >= 500) {message = '服务器开小差了,请联系管理员';}Toast.show({content: message,duration: 3000,icon: 'fail',}); }
2. 专用错误信息面板
function ErrorPanel() {const [errors, setErrors] = useState([]);useEffect(() => {const errors = JSON.parse(localStorage.getItem('api_errors')) || [];setErrors(errors);}, []);return (<div className="error-panel"><h3>最近接口错误 ({errors.length})</h3>{errors.map((err, i) => (<div key={i} className="error-item"><p>[{err.time}] {err.method} {err.url}</p><p>状态码: {err.status} | 信息: {err.message}</p></div>))}</div>); }
三、高级优化方案
错误分类处理
function handleErrorByType(error) {if (!navigator.onLine) {showNetworkError();} else if (error.code === 'ECONNABORTED') {showTimeoutError();} else {showGenericError();} }
错误频率限制
let lastToastTime = 0; function showError(message) {if (Date.now() - lastToastTime > 5000) {Toast.show(message);lastToastTime = Date.now();} }
敏感信息过滤
function sanitizeError(error) {const sanitized = { ...error };delete sanitized.headers.Authorization;delete sanitized.request.body.password;return sanitized; }
四、技术栈扩展建议
错误监控平台集成
使用 Sentry/Bugsnag 进行专业错误监控
import * as Sentry from '@sentry/browser'; Sentry.init({ dsn: 'your_dsn' });function logError(error) {Sentry.captureException(new Error(error.message), {extra: error.context}); }
性能监控结合
const startTime = Date.now(); axios.get('/api/data').then(() => {logApiPerformance(Date.now() - startTime);});
五、注意事项
生产环境应避免显示原始错误堆栈
401 错误需要特殊处理(清除登录态)
重要接口可加入自动重试机制
function retryRequest(config, retries = 3) {return axios(config).catch(err => {return retries > 0 ? retryRequest(config, retries - 1): Promise.reject(err);}); }
通过以上方案,可以实现完整的接口错误处理流程,平衡用户体验与开发调试需求。建议根据具体业务场景选择适合的优化点进行扩展。
前端输入url后浏览器会先做什么?
以下是前端输入 URL 后浏览器的主要处理流程,按顺序分阶段说明:
一、URL 解析阶段(URL Parsing)
协议补全
自动补全协议头(如输入google.com
→ 补全为https://google.com
)格式校验
检查 URL 合法性(如特殊字符转换空格→%20
)
二、缓存检查阶段(Cache Check)
缓存类型 检查顺序 典型场景 Service Worker 最先检查 PWA 离线缓存 Memory Cache 内存缓存(短期) 前进后退页面 Disk Cache 磁盘缓存(长期) CSS/JS 文件缓存 Push Cache HTTP/2 推送缓存(会话级) 服务器推送资源 *触发强制刷新(Ctrl+F5)时会跳过所有缓存*
三、DNS 解析阶段(DNS Lookup)
优化手段
<!-- 预解析特定域名 --> <link rel="dns-prefetch" href="//cdn.example.com">
四、建立连接阶段(TCP Handshake)
三次握手建立 TCP 连接
Client->Server: SYN Server->Client: SYN-ACK Client->Server: ACK
TLS 握手(HTTPS)
协商加密协议版本
验证证书有效性
交换对称加密密钥
五、发送请求阶段(HTTP Request)
GET /index.html HTTP/1.1 Host: www.example.com Accept: text/html Cookie: session_id=abc123
关键过程
携带 Cookie 等身份信息
检查
Connection: keep-alive
复用连接处理
Content-Encoding
压缩格式
六、响应处理阶段(Response Processing)
状态码处理
// 常见状态码处理逻辑 switch(statusCode) {case 301:handleRedirect(response.headers.Location);break;case 404:show404Page();break;case 500:logServerError();break; }
解压响应体
自动处理 gzip/deflate 压缩
七、解析渲染阶段(Rendering)
关键阻塞点
<script>
标签会阻塞 DOM 构建(除非添加async/defer
)CSS 文件会阻塞渲染树合成(CSSOM 构建)
八、持续通信阶段(Keep-Alive)
TCP 连接复用
// HTTP/1.1 默认开启 keep-alive // 同一域名最多维持 6 个 TCP 连接(浏览器差异)
HTTP/2 多路复用
单个连接并行传输多个请求
头部压缩优化
关键性能优化点
减少 DNS 查询(使用 dns-prefetch)
复用 TCP 连接(合理配置 keep-alive)
压缩资源体积(Gzip/Brotli)
避免渲染阻塞(CSS 内联/JS 异步加载)
利用 CDN 加速(减少网络延迟)
理解整个流程可以帮助前端开发者针对性优化关键路径,提升页面加载性能。