72、大文件上传
1. 分片上传
将大文件分割成多个小块(chunks),分别上传到服务器,最后在服务器端合并。
实现步骤:
- 前端分片:
- 使用
File 对象的 slice 方法将文件分割成多个小块。
- 每个分片通过 **
FormData**上传到服务器。
- 记录分片的顺序,方便服务器合并。
- 后端合并:
- 服务器接收到分片后,按顺序存储。
- 所有分片上传完成后,服务器将分片合并成完整文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| const CHUNK_SIZE = 1024 * 1024;
async function uploadFile(file) { const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const fileId = Date.now();
for (let i = 0; i < totalChunks; i++) { const start = i * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end);
const formData = new FormData(); formData.append('file', chunk); formData.append('fileId', fileId); formData.append('chunkIndex', i); formData.append('totalChunks', totalChunks);
await uploadChunk(formData); console.log(`Uploaded chunk ${i + 1}/${totalChunks}`); }
console.log('All chunks uploaded'); }
async function uploadChunk(formData) { try { const response = await fetch('/upload', { method: 'POST', body: formData, }); if (!response.ok) { throw new Error('Upload failed'); } } catch (error) { console.error('Error uploading chunk:', error); } }
const fileInput = document.querySelector('input[type="file"]'); fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { uploadFile(file); } });
|
2. 断点续传
在上传过程中,如果网络中断或用户暂停上传,可以从上次中断的地方继续上传,而不是重新开始。
实现步骤:
- 记录上传进度:
- 前端记录已上传的分片索引。
- 后端保存已接收的分片。
- 续传逻辑:
- 上传前,前端向后端查询已上传的分片。
- 只上传未完成的分片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| async function uploadFile(file) { const CHUNK_SIZE = 1024 * 1024; const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const fileId = Date.now();
const uploadedChunks = await getUploadedChunks(fileId);
for (let i = 0; i < totalChunks; i++) { if (uploadedChunks.includes(i)) { console.log(`Chunk ${i} already uploaded`); continue; }
const start = i * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const chunk = file.slice(start, end);
const formData = new FormData(); formData.append('file', chunk); formData.append('fileId', fileId); formData.append('chunkIndex', i); formData.append('totalChunks', totalChunks);
await uploadChunk(formData); console.log(`Uploaded chunk ${i + 1}/${totalChunks}`); }
console.log('All chunks uploaded'); }
async function getUploadedChunks(fileId) { try { const response = await fetch(`/uploaded-chunks?fileId=${fileId}`); const data = await response.json(); return data.uploadedChunks || []; } catch (error) { console.error('Error fetching uploaded chunks:', error); return []; } }
|
3. 进度显示
通过 XMLHttpRequest 或 fetch 的 ProgressEvent 监听上传进度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| async function uploadChunk(formData) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload', true);
xhr.upload.onprogress = (event) => { if (event.lengthComputable) { const percent = (event.loaded / event.total) * 100; console.log(`Upload progress: ${percent.toFixed(2)}%`); } };
xhr.onload = () => { if (xhr.status === 200) { resolve(); } else { reject(new Error('Upload failed')); } };
xhr.onerror = () => { reject(new Error('Upload failed')); };
xhr.send(formData); }); }
|
5. 优化建议
- 压缩文件:在上传前对文件进行压缩,减少传输体积。
- 并发上传:使用多个并发请求上传分片,提高上传速度。
- 文件校验:在上传完成后,通过 MD5 或 SHA 校验文件完整性。
73、前端高并发场景解决方案
1. 请求合并与批量处理
将多个小请求合并为一个批量请求,减少请求次数,降低服务器压力。
示例场景:
- 上传多个文件时,将文件打包成一个请求发送。
- 提交表单时,将多个字段合并为一个请求。
2. 请求队列与限流
通过队列控制请求的发送速率,避免短时间内发送过多请求。
实现方式:
- 使用队列(如
Promise 队列)控制请求顺序。
- 设置并发数限制,避免同时发送过多请求。
3. 缓存与本地存储
利用浏览器缓存或本地存储(如 localStorage、IndexedDB)减少重复请求。
示例场景:
- 缓存 API 响应数据,避免重复请求。
- 将用户输入的数据暂存到本地,减少实时请求。
4. Web Workers
将计算密集型任务放到 Web Workers 中执行,避免阻塞主线程。
示例场景:
5. 懒加载与分页
对于大量数据的展示,采用懒加载或分页的方式,减少一次性加载的数据量。
示例场景:
6. 服务端渲染(SSR)与静态化
对于高并发页面,可以采用服务端渲染(SSR)或静态化技术,减少前端渲染压力。
示例场景:
7. CDN 加速
将静态资源(如图片、CSS、JS 文件)托管到 CDN,加速资源加载。
示例场景:
8. 降级与兜底策略
在高并发场景下,确保系统在极端情况下仍能提供基本服务。
示例场景:
- 当服务器压力过大时,返回简化页面或静态内容。
- 使用本地缓存数据作为兜底。
9. 优化网络请求
- HTTP/2:使用 HTTP/2 协议,支持多路复用,减少连接开销。
- 压缩数据:使用 Gzip 或 Brotli 压缩请求和响应数据。
- 减少 Cookie 大小:避免在请求中携带不必要的 Cookie。
10. 监控与报警
通过监控工具(如 Sentry、Prometheus)实时监控前端性能,及时发现并解决问题。
示例场景:
- 监控页面加载时间、API 响应时间。
- 设置报警规则,当性能指标异常时通知开发人员。
73、vue动态路由
1. 定义路由表
首先,定义所有可能的路由,包括需要权限的路由和公共路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import Vue from 'vue'; import Router from 'vue-router'; import PublicPage from './components/PublicPage.vue'; import Login from './components/Login.vue';
Vue.use(Router);
const publicRoutes = [ { path: '/', component: PublicPage, }, { path: '/login', component: Login, }, ];
const adminRoutes = [ { path: '/admin', component: () => import('./components/Admin.vue'), meta: { requiresAuth: true, role: 'admin' }, }, ];
const userRoutes = [ { path: '/dashboard', component: () => import('./components/Dashboard.vue'), meta: { requiresAuth: true, role: 'user' }, }, ];
const router = new Router({ mode: 'history', routes: publicRoutes, });
export default router;
|
2. 动态添加路由
根据用户权限,动态添加需要权限的路由。
1 2 3 4 5 6 7 8 9 10
| function addRoutesBasedOnRole(role) { const routes = role === 'admin' ? adminRoutes : userRoutes; router.addRoutes(routes); }
store.dispatch('login', { username: 'admin', role: 'admin' }).then(() => { addRoutesBasedOnRole(store.getters.userRole); });
|
3. 路由守卫
通过全局路由守卫检查用户权限,决定是否允许访问路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| router.beforeEach((to, from, next) => { const isAuthenticated = store.getters.isAuthenticated; const userRole = store.getters.userRole;
if (to.meta.requiresAuth && !isAuthenticated) { next('/login'); } else if (to.meta.role && to.meta.role !== userRole) { next('/'); } else { next(); } });
|
4. Vuex 管理用户状态
使用 Vuex 管理用户登录状态和角色信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import Vue from 'vue'; import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({ state: { user: null, }, mutations: { setUser(state, user) { state.user = user; }, }, actions: { login({ commit }, user) { return new Promise((resolve) => { setTimeout(() => { commit('setUser', user); resolve(); }, 1000); }); }, logout({ commit }) { commit('setUser', null); }, }, getters: { isAuthenticated: (state) => !!state.user, userRole: (state) => state.user?.role, }, });
|
5. 登录与退出
在登录和退出时更新用户状态,并动态调整路由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <template> <div> <input v-model="username" placeholder="Username" /> <input v-model="password" type="password" placeholder="Password" /> <button @click="login">Login</button> </div> </template>
<script> export default { data() { return { username: '', password: '', }; }, methods: { login() { const role = this.username === 'admin' ? 'admin' : 'user'; this.$store.dispatch('login', { username: this.username, role }).then(() => { this.$router.push('/'); }); }, }, }; </script>
<template> <button @click="logout">Logout</button> </template>
<script> export default { methods: { logout() { this.$store.dispatch('logout'); this.$router.push('/login'); }, }, }; </script>
|
74、 window 对象上挂载了大量的事件监听函数
1. 事件委托(Event Delegation)
将事件监听器绑定到父元素上,利用事件冒泡机制处理子元素的事件。这样可以减少事件监听器的数量。
1 2 3 4 5 6 7 8 9 10 11
| document.querySelectorAll('button').forEach((button) => { button.addEventListener('click', handleClick); });
document.body.addEventListener('click', (event) => { if (event.target.tagName === 'BUTTON') { handleClick(event); } });
|
2. 按需绑定事件
只在需要的时候绑定事件,在不需要的时候移除事件监听器。
1 2 3 4 5 6 7 8 9
| function handleScroll() { console.log('Scrolling...'); }
window.addEventListener('scroll', handleScroll);
window.removeEventListener('scroll', handleScroll);
|
3. 防抖(Debounce)与节流(Throttle)
对于频繁触发的事件(如 resize、scroll),使用防抖或节流减少事件处理函数的调用频率。
4. 使用 Passive Event Listeners
对于不会调用 preventDefault() 的事件(如 touchstart、touchmove),可以将事件监听器标记为 passive,以提升滚动性能。
1
| window.addEventListener('touchmove', handleTouchMove, { passive: true });
|
5. 避免内存泄漏
确保在组件销毁或页面卸载时移除事件监听器。
6. 合并事件处理逻辑
将多个事件的处理逻辑合并到一个函数中,减少事件监听器的数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function handleEvent(event) { switch (event.type) { case 'click': console.log('Clicked'); break; case 'scroll': console.log('Scrolled'); break; default: break; } }
window.addEventListener('click', handleEvent); window.addEventListener('scroll', handleEvent);
|
7. 使用 WeakMap 管理事件监听器
使用 WeakMap 存储事件监听器,避免手动管理监听器的添加和移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const eventListeners = new WeakMap();
function addEventListener(target, event, handler) { const wrappedHandler = (e) => handler(e); eventListeners.set(handler, wrappedHandler); target.addEventListener(event, wrappedHandler); }
function removeEventListener(target, event, handler) { const wrappedHandler = eventListeners.get(handler); if (wrappedHandler) { target.removeEventListener(event, wrappedHandler); eventListeners.delete(handler); } }
const handler = () => console.log('Clicked'); addEventListener(window, 'click', handler); removeEventListener(window, 'click', handler);
|
75、webpack和vite的区别
Webpack 和 Vite 都是现代前端开发中常用的构建工具,但它们在设计理念、性能和使用场景上有显著区别。
1. 核心设计理念
Webpack
- 基于打包:Webpack 是一个静态模块打包工具,它将所有资源(如 JavaScript、CSS、图片等)打包成一个或多个文件。
- 支持多种模块化规范:CommonJS、AMD、ES Module 等。
- 插件和加载器:通过丰富的插件(Plugins)和加载器(Loaders)支持各种功能扩展。
Vite
- 基于原生 ES Module:Vite 利用现代浏览器对 ES Module 的原生支持,直接在浏览器中运行模块化的代码。
- 按需加载:开发环境下不打包,而是按需加载模块,提升开发体验。
- 构建工具:生产环境下使用 Rollup 进行打包。
2. 开发环境性能
Webpack
- 打包时间长:在开发环境下,Webpack 需要将所有模块打包成一个或多个文件,项目越大,打包时间越长。
- 热更新(HMR):支持热更新,但更新速度受项目规模影响。
Vite
- 快速启动:开发环境下不打包,直接启动开发服务器,启动速度极快。
- 按需加载:浏览器按需请求模块,减少初始加载时间。
- 热更新(HMR):基于 ES Module 的热更新,更新速度更快。
3. 生产环境构建
Webpack
- 成熟的打包机制:Webpack 提供了强大的打包功能,支持代码分割、Tree Shaking、懒加载等优化。
- 插件生态:丰富的插件生态(如
html-webpack-plugin、mini-css-extract-plugin 等)可以满足各种需求。
Vite
- 基于 Rollup:生产环境下使用 Rollup 进行打包,生成更小、更高效的代码。
- 开箱即用的优化:支持 Tree Shaking、代码分割等优化,但插件生态相对较少。
4. 配置复杂度
Webpack
- 配置复杂:Webpack 的配置文件(
webpack.config.js)通常比较复杂,尤其是需要自定义加载器和插件时。
- 学习曲线陡峭:初学者可能需要花费较多时间学习 Webpack 的配置和原理。
Vite
- 配置简单:Vite 的配置文件(
vite.config.js)相对简单,大部分功能开箱即用。
- 易于上手:对初学者更友好,学习曲线较低。
5. 生态系统
Webpack
- 成熟的生态系统:Webpack 拥有庞大的插件和加载器生态系统,支持各种前端工具和框架。
- 社区支持:由于历史悠久,社区支持和文档非常丰富。
Vite
- 新兴生态系统:Vite 的插件生态相对较少,但正在快速发展。
- 框架支持:Vite 对 Vue、React、Svelte 等现代框架有很好的支持。
6. 适用场景
Webpack
- 大型项目:适合复杂的大型项目,尤其是需要高度自定义构建流程的场景。
- 传统项目:适合需要兼容旧版浏览器的项目。
Vite
- 现代项目:适合基于现代浏览器开发的项目,尤其是需要快速启动和高效开发体验的场景。
- 小型到中型项目:适合中小型项目,或者对开发体验要求较高的项目。
7. 优缺点对比
Webpack
优点:
- 功能强大,支持各种自定义配置。
- 插件生态丰富,社区支持强大。
- 适合大型复杂项目。
缺点:
- 配置复杂,学习曲线陡峭。
- 开发环境下打包速度较慢。
Vite
优点:
- 开发环境下启动速度快,按需加载。
- 配置简单,易于上手。
- 适合现代前端项目。
缺点:
总结
| 特性 |
Webpack |
Vite |
| 核心设计 |
基于打包 |
基于原生 ES Module |
| 开发性能 |
打包时间长,热更新较慢 |
快速启动,按需加载,热更新快 |
| 生产构建 |
成熟的打包机制 |
基于 Rollup,开箱即用的优化 |
| 配置复杂度 |
配置复杂 |
配置简单 |
| 生态系统 |
插件生态丰富 |
插件生态较少,但发展迅速 |
| 适用场景 |
大型复杂项目,传统项目 |
现代项目,中小型项目 |
- 如果你需要高度自定义的构建流程,或者项目需要兼容旧版浏览器,Webpack 是更好的选择。
- 如果你追求极致的开发体验,或者项目基于现代浏览器开发,Vite 是更优的选择。
76、从树结构数据中拿到想要的数据
1. 递归遍历
递归是处理树结构数据的常用方法。假设你有一个树结构的数据,每个节点可能有子节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const tree = { id: 1, name: 'Root', children: [ { id: 2, name: 'Child 1', children: [ { id: 3, name: 'Grandchild 1', children: [] } ] }, { id: 4, name: 'Child 2', children: [] } ] };
function findNodeById(node, id) { if (node.id === id) { return node; } for (let child of node.children) { const result = findNodeById(child, id); if (result) { return result; } } return null; }
const node = findNodeById(tree, 3); console.log(node);
|
2. 迭代遍历
如果你不想使用递归,可以使用栈或队列来实现迭代遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function findNodeByIdIterative(root, id) { const stack = [root]; while (stack.length > 0) { const node = stack.pop(); if (node.id === id) { return node; } for (let child of node.children) { stack.push(child); } } return null; }
const node = findNodeByIdIterative(tree, 4); console.log(node);
|
3. 使用广度优先搜索(BFS)
广度优先搜索通常使用队列来实现,适合在树中查找离根节点最近的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function findNodeByIdBFS(root, id) { const queue = [root]; while (queue.length > 0) { const node = queue.shift(); if (node.id === id) { return node; } for (let child of node.children) { queue.push(child); } } return null; }
const node = findNodeByIdBFS(tree, 2); console.log(node);
|
4. 使用深度优先搜索(DFS)
深度优先搜索可以使用递归或栈来实现,适合在树中查找特定节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function findNodeByIdDFS(root, id) { const stack = [root]; while (stack.length > 0) { const node = stack.pop(); if (node.id === id) { return node; } for (let i = node.children.length - 1; i >= 0; i--) { stack.push(node.children[i]); } } return null; }
const node = findNodeByIdDFS(tree, 3); console.log(node);
|
5. 使用 reduce 或 map 进行数据处理
如果你需要对树中的每个节点进行处理,可以使用 reduce 或 map 来遍历树并生成新的数据结构。
1 2 3 4 5 6 7 8 9
| function mapTree(node, fn) { return { ...fn(node), children: node.children.map(child => mapTree(child, fn)) }; }
const newTree = mapTree(tree, node => ({ ...node, name: node.name.toUpperCase() })); console.log(newTree);
|
总结
- 递归:简单直观,适合深度优先遍历。
- 迭代:避免递归栈溢出,适合深度优先或广度优先遍历。
- 广度优先搜索(BFS):适合查找离根节点最近的节点。
- 深度优先搜索(DFS):适合查找特定节点或遍历整个树。
77、async、await的设计和实现
- **
async**:标记函数为异步,自动返回 Promise。
- **
await**:暂停执行,等待 Promise 完成。
- 底层实现:依赖生成器函数和状态机,将异步代码转换为同步风格。
- 生成器函数:
async/await 通过生成器函数和 Promise 实现。生成器函数可以暂停和恢复执行,await 利用这一特性等待 Promise 完成。
- 状态机:
async 函数被编译为状态机,每个 await 对应一个状态,Promise 完成后恢复执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
78、深拷贝要注意哪些问题
1. 循环引用问题
如果对象内部存在循环引用(如 obj.a = obj),普通的递归深拷贝会导致栈溢出(Maximum call stack size exceeded)。
解决方案
使用 WeakMap 或 Map 存储已拷贝的对象,遇到重复引用时直接返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function deepClone(obj, map = new WeakMap()) { if (map.has(obj)) return map.get(obj); if (typeof obj !== 'object' || obj === null) return obj;
const clone = Array.isArray(obj) ? [] : {}; map.set(obj, clone);
for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], map); } } return clone; }
|
2. 特殊对象类型
普通 JSON.parse(JSON.stringify(obj)) 或递归遍历无法正确处理以下情况:
Date 对象 → 会被转成字符串
RegExp 对象 → 会被转成空对象 {}
Map/Set → 会被丢弃或转成普通对象
Function → 会被丢弃
Symbol 属性 → 会被忽略
Blob/File/Buffer → 可能无法正确复制
解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| function deepClone(obj, map = new WeakMap()) { if (map.has(obj)) return map.get(obj); if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) { const clone = new Map(); map.set(obj, clone); obj.forEach((value, key) => { clone.set(key, deepClone(value, map)); }); return clone; }
if (obj instanceof Set) { const clone = new Set(); map.set(obj, clone); obj.forEach(value => { clone.add(deepClone(value, map)); }); return clone; }
const clone = Array.isArray(obj) ? [] : {}; map.set(obj, clone);
const symKeys = Object.getOwnPropertySymbols(obj); for (const key of symKeys) { clone[key] = deepClone(obj[key], map); }
for (const key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = deepClone(obj[key], map); } }
return clone; }
|
3. 性能问题
- 递归深拷贝大对象可能导致 栈溢出 或 性能瓶颈。
- 某些库(如
lodash.cloneDeep)优化了性能,但仍有开销。
4. 原型链问题
- 普通深拷贝方法不会复制对象的原型链(
__proto__ 或 Object.getPrototypeOf)。
- 如果对象有自定义原型方法,拷贝后可能丢失。
解决方案
1 2 3 4 5 6 7
| function deepClone(obj, map = new WeakMap()) { const clone = Object.create(Object.getPrototypeOf(obj)); map.set(obj, clone); return clone; }
|
5. JSON.parse(JSON.stringify(obj)) 的局限性
- 无法处理
undefined、function、Symbol → 会被忽略或报错。
- 无法处理
Date → 会变成字符串。
- 无法处理
RegExp → 会变成空对象 {}。
- 无法处理循环引用 → 会抛出错误。
总结
| 问题 |
解决方案 |
| 循环引用 |
使用 WeakMap 缓存已拷贝对象 |
特殊对象(Date/RegExp/Map/Set) |
手动处理 |
| 性能问题 |
使用 structuredClone 或 lodash.cloneDeep |
| 原型链丢失 |
手动设置 Object.create(proto) |
JSON 方法的局限性 |
仅适用于纯数据 |
79、判断数组的方法
1. Array.isArray() (推荐)
1 2
| Array.isArray([]); Array.isArray({});
|
这是 ES5 引入的最可靠的方法,也是目前推荐的方式。
2. instanceof 操作符
1 2
| [] instanceof Array; {} instanceof Array;
|
注意:这种方法在跨 frame 或跨 window 时会失效,因为不同全局环境有不同的 Array 构造函数。
3. constructor 属性
1 2
| [].constructor === Array; {}.constructor === Array;
|
同样存在跨 frame 问题,且 constructor 属性可以被修改。
4. Object.prototype.toString.call()
1 2
| Object.prototype.toString.call([]) === '[object Array]'; Object.prototype.toString.call({}) === '[object Array]';
|
手写instanceof方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function myInstanceof(obj, constructor) { if (obj === null || typeof obj !== 'object') { return false; } let proto = Object.getPrototypeOf(obj); while (true) { if (proto === null) { return false; } if (proto === constructor.prototype) { return true; } proto = Object.getPrototypeOf(proto); } }
|
80、让中间页携带cookie的方法
1. 同源中间页(最简单情况)
如果中间页与主站同源,Cookie 会自动携带:
1 2 3 4
| <a href="/middle-page">跳转到中间页</a>
window.location.href = "/middle-page";
|
运行 HTML
2. 跨域中间页携带 Cookie
前端设置(需要后端配合)
1 2 3 4
| fetch('https://other-domain.com/api', { credentials: 'include' });
|
后端必须设置:
1 2
| Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: 具体域名(不能是 *)
|
3. 通过 iframe 携带 Cookie
1 2 3
| <iframe src="https://other-domain.com/middle-page" sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals" allow="cross-origin-isolated"></iframe>
|
4. 通过表单 POST 跳转携带 Cookie
1 2 3
| <form method="post" action="https://other-domain.com/middle-page"> <input type="hidden" name="data" value="some-data"> </form>
|
5. 服务端代理方案
如果无法直接跨域,可以通过自己的服务端做代理:
81、Webpack工作流程
- 初始化:读取配置 → 创建Compiler → 加载插件
- 编译构建:
- 从入口开始递归解析依赖
- 用Loader转换各模块生成 AST
- 构建完整的模块依赖图
- 输出准备:
- 代码分割生成Chunk
- 应用优化(如Tree Shaking)
- 生成最终打包资源
- 输出结果:写入文件系统 → 完成构建
核心特点:
- 基于事件驱动的插件系统
- 模块化处理(一切皆模块)
- 增量构建(利用缓存优化)
一句话总结:Webpack通过解析依赖关系,将各种资源模块打包成静态文件,整个过程可被插件系统精细控制。
82、ES5实现继承的方式
原型链继承(prototype)
构造函数继承(借助 call)
组合继承
原型式继承(Object.create)
寄生式继承
寄生组合式继承
83、require 和 import 的区别
1. 来源不同
require 是 CommonJS 的模块导入语法(Node.js 默认使用)
import 是 ES6 (ES2015) 的模块导入语法
2. 加载时机
require 是 运行时加载(动态加载)
import 是 编译时加载(静态加载)
3. 主要区别
| 特性 |
require |
import |
| 加载方式 |
同步/动态加载 |
异步/静态加载 |
| 模块解析 |
运行时解析 |
编译时解析 |
| 模块对象 |
可修改 |
只读引用 |
| 条件导入 |
支持 |
不支持(静态语法限制) |
| 文件扩展名 |
通常需要显式指定 |
可省略(由打包工具处理) |
| 默认导出 |
module.exports = ... |
export default ... |
| 命名导出 |
exports.name = ... |
export const name = ... |
4. 其他注意事项
- 现代前端项目通常使用打包工具(如 Webpack、Rollup)将
import 转换为浏览器可识别的代码
import 支持 动态导入 (import()) 语法,这是异步的
5. 性能考虑
由于 import 是静态分析,打包工具可以进行更好的 tree-shaking(移除未使用的代码),而 require 的动态特性使得优化更困难。
84、js执行100万个任务,如何保证浏览器不卡顿
使用Web Workers
Web Workers允许在后台线程中运行脚本,不阻塞主线程:
分片执行(使用setTimeout或requestIdleCallback)
将大任务分解为小任务块:
使用requestAnimationFrame进行批处理
使用Generator函数和setTimeout
85、判断DOM元素是否在可视区域
方法一:使用 Element.getBoundingClientRect()
1 2 3 4 5 6 7 8 9
| function isInViewport(element) { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }
|
如果需要判断元素是否部分可见:
1 2 3 4 5 6 7 8 9 10 11 12
| function isPartiallyInViewport(element) { const rect = element.getBoundingClientRect(); const windowHeight = window.innerHeight || document.documentElement.clientHeight; const windowWidth = window.innerWidth || document.documentElement.clientWidth; const vertInView = (rect.top <= windowHeight) && (rect.top + rect.height >= 0); const horInView = (rect.left <= windowWidth) && (rect.left + rect.width >= 0); return (vertInView && horInView); }
|
方法二:使用 Intersection Observer API(更高效,适合监听)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function observeElementVisibility(element, callback) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { callback(entry.isIntersecting); }); }); observer.observe(element); return observer; }
const target = document.querySelector('#myElement'); const observer = observeElementVisibility(target, (isVisible) => { console.log('元素是否可见:', isVisible); });
|
getBoundingClientRect() 会触发回流(reflow),频繁使用可能影响性能
- Intersection Observer API 是现代浏览器推荐的方式,性能更好
86、图片懒加载的实现方式
1. 使用 loading="lazy" 属性(原生HTML5方案)
1
| <img src="placeholder.jpg" data-src="actual-image.jpg" loading="lazy" alt="示例图片">
|
2. Intersection Observer API(推荐方案)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| document.addEventListener("DOMContentLoaded", function() { const lazyImages = [].slice.call(document.querySelectorAll("img.lazy")); if ("IntersectionObserver" in window) { const lazyImageObserver = new IntersectionObserver(function(entries, observer) { entries.forEach(function(entry) { if (entry.isIntersecting) { const lazyImage = entry.target; lazyImage.src = lazyImage.dataset.src; lazyImage.classList.remove("lazy"); lazyImageObserver.unobserve(lazyImage); } }); }); lazyImages.forEach(function(lazyImage) { lazyImageObserver.observe(lazyImage); }); } else { lazyImages.forEach(function(lazyImage) { lazyImage.src = lazyImage.dataset.src; }); } });
|
3. 传统滚动事件监听方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function lazyLoad() { const images = document.querySelectorAll('img[data-src]'); const windowHeight = window.innerHeight; images.forEach(img => { const imgTop = img.getBoundingClientRect().top; if (imgTop < windowHeight + 200) { img.src = img.dataset.src; img.removeAttribute('data-src'); } }); }
const debounceLazyLoad = debounce(lazyLoad, 200);
document.addEventListener('DOMContentLoaded', lazyLoad);
window.addEventListener('scroll', debounceLazyLoad);
window.addEventListener('resize', debounceLazyLoad);
function debounce(func, wait) { let timeout; return function() { const context = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(() => { func.apply(context, args); }, wait); }; }
|
4. 第三方库实现
使用 lazysizes 库
1 2 3 4 5 6 7 8 9 10 11 12
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js"></script>
<img data-src="image.jpg" class="lazyload" alt="示例图片">
<img data-sizes="auto" data-src="image.jpg" data-srcset="image-400.jpg 400w, image-800.jpg 800w" class="lazyload" alt="示例图片">
|
使用 lozad.js
1 2 3 4 5 6 7 8
| <script src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js"></script>
<img data-src="image.jpg" class="lozad" alt="示例图片">
<script> const observer = lozad('.lozad'); observer.observe(); </script>
|