JavaScript 基础
1. 解释 JavaScript 中的原型和原型链
答案:
JavaScript 中的每个对象都有一个原型(prototype),原型也是一个对象,对象可以从原型继承属性和方法。
- 原型:每个函数都有一个
prototype
属性,指向一个对象,这个对象就是该函数的实例的原型
- 原型链:当访问一个对象的属性时,如果对象本身没有这个属性,JavaScript 会沿着原型链向上查找,直到找到该属性或到达原型链的末端(
null
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Person(name) { this.name = name; }
Person.prototype.sayHello = function() { console.log(`Hello, my name is ${this.name}`); };
const alice = new Person('Alice'); alice.sayHello();
console.log(alice.__proto__ === Person.prototype); console.log(Person.prototype.__proto__ === Object.prototype); console.log(Object.prototype.__proto__ === null);
|
2. var、let 和 const 的区别是什么?
答案:
var:
- 函数作用域或全局作用域
- 存在变量提升(可以在声明前使用,值为 undefined)
- 可以重复声明同名变量
- 可以重新赋值
let:
- 块级作用域
- 存在暂时性死区(不能在声明前使用)
- 不可以重复声明同名变量
- 可以重新赋值
const:
- 块级作用域
- 存在暂时性死区(不能在声明前使用)
- 不可以重复声明同名变量
- 不可以重新赋值(但对于引用类型,可以修改其属性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var x = 1; if (true) { var x = 2; } console.log(x);
let y = 1; if (true) { let y = 2; } console.log(y);
const z = { value: 1 }; z.value = 2; console.log(z.value);
|
3. 解释事件循环(Event Loop)机制
答案:
JavaScript 是单线程的,事件循环是 JavaScript 实现异步的核心机制,它由以下部分组成:
- 调用栈(Call Stack):执行同步代码
- 任务队列(Task Queue):
- 宏任务(Macrotask):setTimeout、setInterval、I/O、UI 渲染等
- 微任务(Microtask):Promise 回调、MutationObserver、queueMicrotask() 等
事件循环的执行顺序:
- 执行同步代码(调用栈中的任务)
- 执行所有微任务
- 执行一个宏任务
- 重复步骤 2-3
1 2 3 4 5 6 7 8 9 10 11 12 13
| console.log('1');
setTimeout(() => { console.log('2'); }, 0);
Promise.resolve().then(() => { console.log('3'); });
console.log('4');
|
4. 闭包是什么?有什么用途和潜在问题?
答案:
闭包是指一个函数可以记住并访问其词法作用域,即使该函数在其词法作用域之外执行。
用途:
- 数据私有化/封装
- 创建函数工厂
- 实现模块模式
- 维持状态
潜在问题:
- 内存泄漏:闭包会保持对外部变量的引用,可能导致这些变量无法被垃圾回收
- 性能问题:过度使用闭包可能影响性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function createCounter() { let count = 0; return { increment: function() { count++; return count; }, decrement: function() { count--; return count; }, getCount: function() { return count; } }; }
const counter = createCounter(); console.log(counter.increment()); console.log(counter.increment()); console.log(counter.decrement());
|
5. Promise、async/await 的作用和区别
答案:
Promise 和 async/await 都是用于处理 JavaScript 中的异步操作。
Promise:
- 表示一个异步操作的最终完成(或失败)及其结果值
- 有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)
- 使用
.then()
和 .catch()
方法处理结果和错误
async/await:
- 是基于 Promise 的语法糖
async
函数返回一个 Promise
await
关键字只能在 async
函数内使用,用于等待 Promise 解决
- 使代码看起来更像同步代码,更易读
区别:
- 语法:async/await 语法更简洁,更接近同步代码
- 错误处理:Promise 使用
.catch()
,async/await 使用 try/catch
- 调试:async/await 更容易调试
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 fetchDataPromise() { return fetch('https://api.example.com/data') .then(response => response.json()) .then(data => { console.log(data); return data; }) .catch(error => { console.error('Error:', error); }); }
async function fetchDataAsync() { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log(data); return data; } catch (error) { console.error('Error:', error); } }
|
HTML & CSS
6. 语义化 HTML 的意义是什么?
答案:
语义化 HTML 是指使用恰当的 HTML 标签来表示内容的结构和含义,而不仅仅是为了展示效果。
意义:
- 可访问性:屏幕阅读器等辅助技术可以更好地解释页面内容
- SEO 优化:搜索引擎更容易理解页面内容和结构
- 可维护性:代码更清晰,易于理解和维护
- 设备兼容性:在不同设备上有更好的展示效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div class="header"> <div class="logo">网站名称</div> <div class="nav"> <div>首页</div> <div>关于</div> </div> </div>
<header> <h1>网站名称</h1> <nav> <ul> <li><a href="/">首页</a></li> <li><a href="/about">关于</a></li> </ul> </nav> </header>
|
7. 解释 CSS 盒模型及其不同类型
答案:
CSS 盒模型描述了元素内容(content)、内边距(padding)、边框(border)和外边距(margin)如何一起决定元素的总尺寸。
两种盒模型:
标准盒模型(content-box):
width
和 height
只包括内容区域
- 总宽度 = width + padding + border + margin
替代盒模型(border-box):
width
和 height
包括内容区域、内边距和边框
- 总宽度 = width + margin(width 已经包含了 padding 和 border)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| .box-content { box-sizing: content-box; width: 300px; padding: 20px; border: 10px solid black; margin: 15px; }
.box-border { box-sizing: border-box; width: 300px; padding: 20px; border: 10px solid black; margin: 15px; }
|
8. CSS 选择器的优先级是如何计算的?
答案:
CSS 选择器的优先级按照以下规则计算:
- 内联样式:1000 分
- ID 选择器:100 分
- 类选择器、属性选择器、伪类:10 分
- 元素选择器、伪元素:1 分
- 通配符(*):0 分
- 继承的样式:无优先级
当优先级相同时,后声明的样式会覆盖先声明的样式。
使用 !important
可以覆盖所有其他样式(除非对方也使用了 !important
,此时仍按优先级计算)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| p { color: black; }
.text { color: blue; }
p.text { color: green; }
#content p { color: red; }
.highlight { color: yellow !important; }
|
9. 响应式设计的核心原则和实现方法有哪些?
答案:
响应式设计是一种让网站能够适应不同设备和屏幕尺寸的设计方法。
核心原则:
- 流式布局:使用相对单位(%、em、rem)而非固定单位(px)
- 媒体查询:根据设备特性应用不同的样式
- 灵活的图片:确保图片能够缩放而不溢出容器
- 移动优先:先设计移动端界面,再逐步增强到大屏幕
实现方法:
- 使用媒体查询(@media)
- 使用 viewport 元标签
- 使用 CSS Grid 和 Flexbox 布局
- 使用相对单位(%、em、rem、vw、vh)
- 使用响应式图片技术(srcset、sizes 属性)
1 2
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
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
| .container { width: 90%; max-width: 1200px; margin: 0 auto; }
img { max-width: 100%; height: auto; }
@media (max-width: 767px) { .sidebar { display: none; } .main-content { width: 100%; } }
@media (min-width: 768px) and (max-width: 1023px) { .sidebar { width: 30%; } .main-content { width: 70%; } }
@media (min-width: 1024px) { .sidebar { width: 25%; } .main-content { width: 75%; } }
|
10. 解释 CSS Grid 和 Flexbox 的区别及适用场景
答案:
CSS Grid 和 Flexbox 都是现代 CSS 布局技术,但它们有不同的设计目标和适用场景。
Flexbox(弹性盒子):
- 一维布局系统:主要沿一个轴(主轴或交叉轴)进行布局
- 内容驱动:元素大小由其内容决定
- 适用场景:导航菜单、卡片布局、居中元素、简单的一维布局
CSS Grid(网格布局):
- 二维布局系统:同时控制行和列
- 布局驱动:先定义布局结构,再放入元素
- 适用场景:整体页面布局、复杂的二维布局、不规则布局
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
| .nav { display: flex; justify-content: space-between; align-items: center; }
.nav-item { flex: 0 1 auto; margin: 0 10px; }
.page { display: grid; grid-template-columns: 1fr 3fr 1fr; grid-template-rows: auto 1fr auto; grid-template-areas: "header header header" "sidebar main aside" "footer footer footer"; min-height: 100vh; }
.header { grid-area: header; } .sidebar { grid-area: sidebar; } .main { grid-area: main; } .aside { grid-area: aside; } .footer { grid-area: footer; }
|
前端框架
11. React 中的虚拟 DOM 是什么?它有什么优势?
答案:
虚拟 DOM (Virtual DOM) 是 React 中的一个概念,它是真实 DOM 的一种轻量级的 JavaScript 对象表示。
工作原理:
- 当组件状态改变时,React 创建一个新的虚拟 DOM 树
- 将新的虚拟 DOM 树与之前的虚拟 DOM 树进行比较(Diffing 算法)
- 计算出需要更新的部分
- 只更新真实 DOM 中需要变化的部分
优势:
- 性能优化:减少直接操作 DOM 的次数,批量处理 DOM 更新
- 跨平台:虚拟 DOM 是平台无关的,可以渲染到不同环境(Web、Native、服务器等)
- 声明式编程:开发者只需关注状态和 UI 的映射关系,不需要手动操作 DOM
- 调试方便:可以追踪 DOM 变化的历史记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Counter() { const [count, setCount] = React.useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }
|
12. Vue 的响应式原理是什么?
答案:
Vue 的响应式系统是其核心特性之一,它能够自动追踪依赖关系并在数据变化时更新视图。
Vue 2 响应式原理:
- 使用
Object.defineProperty()
劫持对象的属性
- 在 getter 中收集依赖(Watcher)
- 在 setter 中通知依赖更新
- 对于数组,通过重写数组方法(push、pop 等)实现响应式
Vue 3 响应式原理:
- 使用 ES6 的 Proxy 代替 Object.defineProperty
- 可以监听整个对象,包括属性的添加和删除
- 可以监听数组的索引和长度变化
- 性能更好,没有 Vue 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
| function defineReactive(obj, key, val) { const dep = new Dep(); Object.defineProperty(obj, key, { get() { if (Dep.target) { dep.depend(); } return val; }, set(newVal) { if (newVal === val) return; val = newVal; dep.notify(); } }); }
function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); trigger(target, key); return result; } }); }
|
13. React Hooks 的作用和常用 Hook 有哪些?
答案:
React Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和其他 React 特性,而不需要编写类组件。
作用:
- 在函数组件中使用状态和生命周期特性
- 复用状态逻辑,而不需要改变组件层次结构
- 将相关逻辑组合在一起,而不是按生命周期方法分散
常用的 Hook:
- useState:管理组件状态
- useEffect:处理副作用(类似 componentDidMount、componentDidUpdate、componentWillUnmount)
- useContext:访问 React Context
- useReducer:使用 reducer 管理复杂状态逻辑
- useCallback:记忆函数,避免不必要的重新渲染
- useMemo:记忆计算结果,避免重复计算
- useRef:保存可变值,不触发重新渲染
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
| function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }
function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchData() { try { const response = await fetch('https://api.example.com/data'); const result = await response.json(); setData(result); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } } fetchData(); return () => { }; }, []); if (loading) return <p>Loading...</p>; return <div>{/* 渲染数据 */}</div>; }
|
14. 前端路由的实现原理是什么?
答案:
前端路由是指在单页应用(SPA)中,通过 JavaScript 控制页面内容的切换,而不刷新整个页面。主要有两种实现方式:
Hash 模式:
- 基于 URL 的哈希部分(
#
后面的部分)
- 通过监听
hashchange
事件检测路由变化
- 兼容性好,但 URL 不够美观
History 模式:
- 基于 HTML5 History API(
pushState
、replaceState
)
- 通过监听
popstate
事件检测路由变化
- URL 更美观,但需要服务器配置支持
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
| class HashRouter { constructor() { this.routes = {}; window.addEventListener('hashchange', this.handleHashChange.bind(this)); this.handleHashChange(); } route(path, callback) { this.routes[path] = callback; } handleHashChange() { const hash = window.location.hash.slice(1) || '/'; const handler = this.routes[hash]; if (handler) { handler(); } } }
class HistoryRouter { constructor() { this.routes = {}; window.addEventListener('popstate', this.handlePopState.bind(this)); this.handlePopState(); } route(path, callback) { this.routes[path] = callback; } navigate(path) { history.pushState(null, null, path); this.handlePopState(); } handlePopState() { const path = window.location.pathname; const handler = this.routes[path]; if (handler) { handler(); } } }
|
15. 状态管理工具(Redux/Vuex/Pinia)的核心概念和工作原理
答案:
状态管理工具用于集中管理应用的状态,使状态变化可预测和可追踪。
Redux(React)核心概念:
- Store:存储应用的状态
- Action:描述状态变化的普通对象
- Reducer:纯函数,根据当前状态和 action 计算新状态
- Dispatch:发送 action 的方法
- Middleware:扩展 Redux 功能的中间件(如处理异步操作)
Vuex(Vue 2)核心概念:
- State:应用的状态
- Getters:从 state 派生的状态
- Mutations:同步修改状态的方法
- Actions:可包含异步操作,提交 mutation
- Modules:将 store 分割成模块
Pinia(Vue 3)核心概念:
- Store:定义状态和操作的容器
- State:存储的响应式状态
- Getters:类似计算属性,从 state 派生状态
- Actions:修改状态的方法,可以是异步的
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
function counterReducer(state = { count: 0 }, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } }
const store = Redux.createStore(counterReducer);
store.subscribe(() => { console.log(store.getState()); });
store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'INCREMENT' }); store.dispatch({ type: 'DECREMENT' });
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }, actions: { incrementAsync({ commit }) { setTimeout(() => { commit('increment'); }, 1000); } }, getters: { doubleCount: state => state.count * 2 } });
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2 }, actions: { increment() { this.count++; }, async incrementAsync() { await new Promise(resolve => setTimeout(resolve, 1000)); this.increment(); } } });
|
前端工程化与性能优化
16. 前端模块化的发展历程和主要规范
答案:
前端模块化是为了解决全局变量污染、代码复用、依赖管理等问题而发展起来的。
发展历程:
- 全局函数:最初的代码组织方式,容易造成命名冲突
- 命名空间:将相关功能封装在对象中,减少全局变量
- IIFE(立即执行函数表达式):创建私有作用域,避免变量泄露
- CommonJS:Node.js 采用的模块规范,同步加载
- AMD(Asynchronous Module Definition):异步加载模块,适用于浏览器
- UMD(Universal Module Definition):兼容 CommonJS 和 AMD
- ES Modules:ECMAScript 官方模块系统,现代浏览器原生支持
主要规范:
CommonJS:
- 使用
require()
导入,module.exports
或 exports
导出
- 同步加载,适合服务器环境
- Node.js 默认使用
1 2 3 4 5 6 7 8 9
| module.exports = { add: (a, b) => a + b, subtract: (a, b) => a - b };
const math = require('./math'); console.log(math.add(2, 3));
|
AMD:
- 使用
define()
定义模块,require()
加载模块
- 异步加载,适合浏览器环境
- RequireJS 是其实现
1 2 3 4 5 6 7 8 9 10 11 12
| define('math', [], function() { return { add: function(a, b) { return a + b; }, subtract: function(a, b) { return a - b; } }; });
require(['math'], function(math) { console.log(math.add(2, 3)); });
|
ES Modules:
- 使用
import
导入,export
导出
- 静态分析,支持 tree-shaking
- 现代浏览器原生支持
1 2 3 4 5 6 7 8 9 10 11 12
| export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
import { add, subtract } from './math.js'; console.log(add(2, 3));
|
17. Webpack 的核心概念和工作原理
答案:
Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它将项目中的各种资源(JS、CSS、图片等)视为模块,根据模块的依赖关系进行打包。
核心概念:
- Entry:入口,指定 webpack 开始构建的起点
- Output:输出,指定打包后的资源输出到哪里
- Loaders:加载器,处理非 JavaScript 文件(如 CSS、图片)
- Plugins:插件,执行范围更广的任务(如打包优化、资源管理)
- Mode:模式,指定开发环境或生产环境
- Chunks:代码块,打包过程中的代码单元
工作原理:
- 初始化参数:从配置文件和命令行参数中读取配置
- 开始编译:初始化 Compiler 对象,加载所有配置的插件
- 确定入口:根据配置中的 entry 找出所有入口文件
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行转换,再递归处理依赖的模块
- 完成模块编译:得到每个模块被转换后的最终内容和它们之间的依赖关系
- 输出资源:根据依赖关系,组装成一个个包含多个模块的 Chunk
- 输出完成:根据配置确定输出路径和文件名,将文件内容写入文件系统
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 51 52 53 54 55 56 57 58
| const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.[contenthash].js' }, mode: 'production', module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource' } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ], optimization: { splitChunks: { chunks: 'all' } } };
|
18. 前端性能优化的主要方法和指标
答案:
前端性能优化是提升用户体验的关键,包括多个方面的优化策略。
主要优化方法:
网络优化:
- 减少 HTTP 请求数(合并文件、CSS Sprites、Base64 编码)
- 使用 HTTP/2
- 使用 CDN 加速
- 启用 Gzip 压缩
- 使用浏览器缓存(Cache-Control、ETag)
- 懒加载和预加载资源
资源优化:
- 压缩代码(JS、CSS、HTML)
- 压缩和优化图片(WebP、SVG、响应式图片)
- Tree-shaking 移除未使用的代码
- 代码分割(Code Splitting)
- 使用现代格式的图片
渲染优化:
- 避免重排(reflow)和重绘(repaint)
- 使用 CSS 动画代替 JavaScript 动画
- 使用
requestAnimationFrame
处理动画
- 使用 Web Workers 处理复杂计算
- 虚拟滚动处理长列表
应用优化:
- 服务端渲染(SSR)或静态站点生成(SSG)
- 应用 PWA 技术
- 使用 Web 缓存 API
- 实现骨架屏(Skeleton Screen)
- 优化首次内容绘制(FCP)
主要性能指标:
- FCP (First Contentful Paint):首次内容绘制,页面上首次绘制任何文本、图像、非空白 canvas 或 SVG 的时间
- LCP (Largest Contentful Paint):最大内容绘制,视口中最大的内容元素绘制完成的时间
- FID (First Input Delay):首次输入延迟,用户首次与页面交互到浏览器响应的时间
- CLS (Cumulative Layout Shift):累积布局偏移,页面加载过程中元素意外移动的程度
- TTI (Time to Interactive):可交互时间,页面完全可交互所需的时间
- TBT (Total Blocking Time):总阻塞时间,FCP 和 TTI 之间主线程被阻塞的总时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(`${entry.name}: ${entry.startTime}ms`); } });
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint', 'layout-shift'] });
performance.mark('app-init-start');
performance.mark('app-init-end'); performance.measure('app-initialization', 'app-init-start', 'app-init-end');
|
19. 前端安全问题及防范措施
答案:
前端安全是 Web 应用安全的重要组成部分,主要包括以下安全问题和防范措施:
XSS(跨站脚本攻击):
- 问题:攻击者将恶意脚本注入到网页中,当用户浏览页面时执行
- 防范措施:
- 对输入输出进行转义和验证
- 使用 Content-Security-Policy (CSP) 头
- 使用 HttpOnly 和 Secure 标记保护 Cookie
- 使用现代框架的内置 XSS 保护
1 2 3 4 5 6 7 8 9 10 11 12 13
| function escapeHTML(str) { return str .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }
|
CSRF(跨站请求伪造):
- 问题:攻击者诱导用户在已认证的网站上执行非预期操作
- 防范措施:
- 使用 CSRF Token
- 验证 Origin 和 Referer 头
- 使用 SameSite Cookie 属性
- 对敏感操作要求重新认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
const csrfToken = generateRandomToken(); session.csrfToken = csrfToken;
<form action="/api/update" method="POST"> <input type="hidden" name="csrf_token" value="{{csrfToken}}"> </form>
// 服务端验证 if (request.body.csrf_token !== session.csrfToken) { return response.status(403).send('CSRF token 验证失败'); }
|
点击劫持:
- 问题:攻击者将透明的目标网站覆盖在另一个网站上,诱导用户点击
- 防范措施:
- 使用 X-Frame-Options 头
- 使用 CSP 的 frame-ancestors 指令
- JavaScript 框架防御
其他安全问题:
- 中间人攻击:使用 HTTPS 防范
- 不安全的依赖:定期更新依赖,使用安全扫描工具
- 敏感信息泄露:避免在前端存储敏感信息,使用环境变量
- 服务器端请求伪造 (SSRF):验证和限制 URL,使用白名单
- 本地存储安全:不在 localStorage/sessionStorage 中存储敏感数据
20. 微前端架构的原理和实现方式
答案:
微前端是一种将前端应用分解为更小、更易于管理的部分的架构风格,每个部分可以独立开发、测试和部署。
核心原则:
- 技术栈无关:每个微前端可以使用不同的技术栈
- 独立开发部署:团队可以独立工作,不影响其他团队
- 运行时集成:微前端在浏览器中组合,而不是构建时
- 隔离:微前端之间不应相互影响
实现方式:
1. 基于路由的分发:
- 不同路由对应不同的微应用
- 简单易实现,但页面间集成度低
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const routes = { '/app1': loadApp1, '/app2': loadApp2, '/app3': loadApp3 };
function router() { const path = window.location.pathname; const route = Object.keys(routes).find(route => path.startsWith(route)); if (route) { routes[route](); } else { loadDefaultApp(); } }
window.addEventListener('popstate', router); router();
|
2. 使用 iframe:
- 完全隔离各个应用
- 但存在通信困难、样式不一致等问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div id="container"> <iframe id="micro-frontend" src="https://app1.example.com"></iframe> </div>
<script> function loadApp(appUrl) { document.getElementById('micro-frontend').src = appUrl; } window.addEventListener('message', (event) => { if (event.origin === 'https://app1.example.com') { console.log('Received message:', event.data); } }); </script>
|
3. Web Components:
- 使用自定义元素封装微前端
- 提供良好的封装性和互操作性
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
| class MicroApp extends HTMLElement { connectedCallback() { const appName = this.getAttribute('name'); const appUrl = this.getAttribute('url'); fetch(`${appUrl}/asset-manifest.json`) .then(res => res.json()) .then(manifest => { this.loadResources(manifest, appUrl); }); } loadResources(manifest, baseUrl) { if (manifest.css) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = `${baseUrl}/${manifest.css}`; this.appendChild(link); } if (manifest.js) { const script = document.createElement('script'); script.src = `${baseUrl}/${manifest.js}`; script.onload = () => { window[`mount${this.getAttribute('name')}`](this); }; this.appendChild(script); } } disconnectedCallback() { const appName = this.getAttribute('name'); if (window[`unmount${appName}`]) { window[`unmount${appName}`](); } } }
customElements.define('micro-app', MicroApp);
|
4. JavaScript 模块加载:
- 动态加载 JavaScript 模块
- 灵活性高,但需要处理好依赖和冲突
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function loadApp(name, url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.onload = () => { resolve(window[name]); }; script.onerror = reject; document.head.appendChild(script); }); }
loadApp('app1', 'https://app1.example.com/bundle.js') .then(app => { app.mount(document.getElementById('app1-container')); });
|
5. 使用微前端框架:
- single-spa:JavaScript 微前端框架
- qiankun:基于 single-spa 的增强框架
- Module Federation:Webpack 5 提供的模块联邦功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { registerApplication, start } from 'single-spa';
registerApplication({ name: 'app1', app: () => import('app1'), activeWhen: '/app1' });
registerApplication({ name: 'app2', app: () => import('app2'), activeWhen: '/app2' });
start();
|
挑战与解决方案:
- 样式隔离:使用 CSS Modules、CSS-in-JS 或 Shadow DOM
- 共享依赖:使用 Webpack 的 Module Federation 或 import maps
- 通信机制:使用自定义事件、发布订阅模式或全局状态管理
- 认证授权:使用 SSO 或 token 共享
Prev: 前端面试题集
Next: react重点