一.前端解决跨域问题的方法有哪些?
1.CORS(跨域资源共享)
原理: 服务端设置HTTP响应头(如Access-Control-Allow-Origin)声明允许跨越的源
http
Access-Control-Allow-Origin: * // 允许所有域名(不安全)
Access-Control-Allow-Origin: https://your-domain.com // 允许指定域名
Access-Control-Allow-Methods: GET, POST, PUT // 允许的请求方法
Access-Control-Allow-Headers: Content-Type, Authorization // 允许的自定义头
适用场景: 最主流、安全的跨域方案,支持所有的HTTP方法
2代理服务器(Proxy)
原理: 前端请求同源服务器,由改服务器转发请求到目标服务器(避开浏览器同源策略)
实现方式:
webpack DevServer配置代理
js
devServer: {
proxy: {
'/api': {
target: 'http://target-server.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
vite配置
js
server: {
proxy: {
'/api': {
target: 'http://target-server.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
适用场景: 开发环境
3.JSONP
原理: 利用script标签不受同源策略限制的特性,通过回调函数接收数据 实现:
js
> function handleResponse(data) {
console.log("收到数据:", data);
}
</script>
<script src="https://api.example.com/data?callback=handleResponse"></script>
缺点:
- 仅支持GET请求
- 存在XSS安全风险
- 需要服务端返回callback(dWebSocket协议不受同源策略限制ata)
4.WebSocket
原理: WebSocket协议不受同源策略限制
5.postMessage API
原理: 通过window.postMessage()实现不同窗口间的跨域通信
二.元素水平垂直居中
1.Flex布局
css
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 容器高度 */
}
.inner-element {
/* 内容样式 */
}
2.Grid布局
css
.container {
display: grid;
place-items: center; /* 同时居中 */
height: 100vh;
}
3.绝对定位+Transform (未知尺寸元素)
css
.container {
position: relative;
height: 100vh;
}
.inner-element {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 自适应元素尺寸 */
}
4.Margin:auto(需配合绝对定位)
css
.container {
position: relative;
height: 100vh;
}
.inner-element {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
width: 200px;
height: 100px;
}
5.table-cell布局
css
.container {
display: table;
width: 100%;
height: 100vh;
}
.inner-wrapper {
display: table-cell;
vertical-align: middle; /* 垂直居中 */
text-align: center; /* 水平居中 */
}
.inner-element {
display: inline-block; /* 使水平居中生效 */
}
6.文本垂直居中
css
.text{
line-height:100px //值等于容器高度
text-align: center;
}
三.this指向有哪些场景,怎么改变this指向
在js中,this是一个特殊的关键字,它的取值决定于函数被调用的方式。this的指向在函数定义时无法确定,只有在函数执行的时候才能确定
this指向的7种常见场景
1.全局上下文中的this
js
console.log(this); // 浏览器中: Window 对象 | Node.js中: global 对象
2.函数调用中的this
js
function showThis() {
console.log(this); // 非严格模式: Window | 严格模式: undefined
}
showThis();
3.对象方法中的this
js
const user = {
name: '张三',
greet() {
console.log(`你好, ${this.name}!`); // this 指向 user 对象
}
};
user.greet(); // 输出: 你好, 张三!
4.构造函数中的this
js
function Person(name) {
this.name = name; // this 指向新创建的实例
this.sayHello = function() {
console.log(`我是${this.name}`);
};
}
const person1 = new Person('李四');
person1.sayHello(); // 输出: 我是李四
5.事件处理函数中的this
js
document.getElementById('myButton').addEventListener('click', function() {
console.log(this); // 指向触发事件的元素 (button)
});
6.箭头函数中的this
js
const obj = {
value: 42,
getValue: function() {
setTimeout(() => {
console.log(this.value); // 42 (this 继承自外层作用域)
}, 100);
}
};
obj.getValue();
7.类中的this
js
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
console.log(this.count);
}
}
const counter = new Counter();
counter.increment(); // 输出: 1
计算精度问题的解决方案
1.使用整数计算
js
function preciseAdd(a, b) {
const factor = Math.pow(10, Math.max(
String(a).split('.')[1]?.length || 0,
String(b).split('.')[1]?.length || 0
));
return (a * factor + b * factor) / factor;
}
console.log(preciseAdd(0.1, 0.2)); // 0.3
console.log(preciseAdd(1.001, 2.002)); // 3.003
2.toFixed + parseFloat
js
function toFixedFloat(num, precision = 10) {
return parseFloat(num.toFixed(precision));
}
console.log(toFixedFloat(0.1 + 0.2)); // 0.3
console.log(toFixedFloat(1.005 * 100)); // 100.5
3.计算对比两个数时使用Number.EPSILON进行安全比较
js
function numbersEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
console.log(numbersEqual(0.1 + 0.2, 0.3)); // true
4.处理大整数时使用BigInt
js
const bigIntResult = (12345678901234567890n * 2n).toString();
console.log(bigIntResult); // "24691357802469135780"
5.使用第三方数学库(如#### decimal.js,#### big.js)
vue3对比vue2的优点
Composition API
革命性的API设计,允许将相关逻辑组织在一起,而不是分散在各个选项中。解决了Vue2中大型组件难以维护的问题
性能飞跃
- 重写虚拟DOM,优化diff算法
- 编译时优化:静态节点提升,补丁标志
- 基于Proxy的响应式系统,初始化速度提升100%
- 内存占用减少50%
- 打包体积减少41%,运行时大小仅10kb
TypeScript支持
- 更好的IDE支持和类型检查
- Composition API 天生支持TS
- 新的defineComponent函数提供类型推断
- 完整的TSX支持
新内置组件
Teleport 将子组件渲染到DOM树的其他位置 Suspense 优雅的处理异步组件加载状态
响应式系统升级
基于Proxy的全新响应式系统:
- 支持MAP,Set,WeakMap,WeakSet
- 检测数组索引和长度变化
- 检测对象属性的添加/删除
- 独立的响应式API (可在vue组件外使用)
模块化架构
Vue3被设计为更加模块化
- 核心功能可单独使用(如响应式系统)
- 更好的Tree Shaking支持
- 自定义渲染器API
- 编译器与运行时分离
vue3 虚拟DOM重新
1.静态节点提升
vue3在编译阶段检测静态节点(不依赖响应式数据的节点),将它们提升到渲染函数之外,避免在再次渲染时重新创建这些节点
js
// Vue2 处理静态节点
function render() {
return createVNode('div', null, [
createVNode('h1', null, 'Static Title'), // 每次重新创建
createVNode('p', null, state.dynamicContent)
])
}
// Vue3 处理静态节点
const hoisted = createVNode('h1', null, 'Static Title') // 提升到外部
function render() {
return createVNode('div', null, [
hoisted, // 直接复用静态节点
createVNode('p', null, state.dynamicContent)
])
}
补丁标志
vue3为每个虚拟DOM节点添加"补丁标志",标志节点类型需要更新的类型(文本,props,class等),使得diff过程可以跳过不需要更新的节点
objectivec
// Patch Flags 示例
const PatchFlags = {
TEXT: 1, // 动态文本内容
CLASS: 2, // 动态 class
STYLE: 4, // 动态 style
PROPS: 8, // 动态 props(非 class/style)
FULL_PROPS: 16, // 动态 key 的 props
// ...其他标志
}
// 创建带补丁标志的 VNode
createVNode(
'div',
{ class: dynamicClass },
dynamicText,
PatchFlags.CLASS | PatchFlags.TEXT // 标志位组合
)
3.树结构打平
vue3将动态子节点提取到单独的数组中,Diff时只需要遍历动态节点,跳过静态节点
html
<!-- 模板 -->
<div>
<div>静态头部</div> <!-- 静态 -->
<div v-for="item in items" :key="item.id">{{ item.text }}</div> <!-- 动态 -->
<div>静态底部</div> <!-- 静态 -->
</div>
<!-- 编译后 -->
const _hoisted_1 = /* 静态头部 */
const _hoisted_2 = /* 静态底部 */
function render() {
return createVNode('div', null, [
_hoisted_1,
...state.items.map(item => createVNode('div', { key: item.id }, item.text)),
_hoisted_2
])
}
Diff算法优化
1.基于序列的Diff算法
Vue3使用更高效的Diff算法
js
function patchChildren(n1, n2, container) {
// 1. 预处理:相同前缀和后缀
let i = 0
let e1 = n1.length - 1
let e2 = n2.length - 1
// 跳过相同前缀
while (i <= e1 && i <= e2 && isSameVNode(n1[i], n2[i])) i++
// 跳过相同后缀
while (i <= e1 && i <= e2 && isSameVNode(n1[e1], n2[e2])) {
e1--
e2--
}
// 2. 简单情况处理
if (i > e1) {
// 只有新节点,挂载新增部分
} else if (i > e2) {
// 只有旧节点,卸载多余部分
} else {
// 3. 复杂情况处理
const s1 = i
const s2 = i
const keyToNewIndexMap = new Map()
// 4. 构建新节点 key 到索引的映射
for (let j = s2; j <= e2; j++) {
keyToNewIndexMap.set(n2[j].key, j)
}
// 5. 遍历旧节点,找出可复用的节点
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let j = s1; j <= e1; j++) {
const oldVNode = n1[j]
const newIndex = keyToNewIndexMap.get(oldVNode.key)
if (newIndex === undefined) {
// 没有对应新节点,卸载
unmount(oldVNode)
} else {
// 记录新旧索引关系
newIndexToOldIndexMap[newIndex - s2] = j + 1
// 递归 patch 子节点
patch(oldVNode, n2[newIndex], container)
}
}
// 6. 移动和挂载新节点
// 使用最长递增子序列算法最小化移动操作
const increasingNewIndexSequence = getSequence(newIndexToOldIndexMap)
let lastIndex = increasingNewIndexSequence.length - 1
for (let j = toBePatched - 1; j >= 0; j--) {
const newIndex = s2 + j
const newVNode = n2[newIndex]
if (newIndexToOldIndexMap[j] === 0) {
// 挂载新节点
patch(null, newVNode, container)
} else if (j !== increasingNewIndexSequence[lastIndex]) {
// 移动节点
move(newVNode, container, anchor)
} else {
// 不需要移动
lastIndex--
}
}
}
}
2最长递增子序列(LIS)优化
Vue3 使用最长递增子序列算法来最小化 DOM 移动操作:
js
function getSequence(arr) {
const p = arr.slice()
const result = [0]
let i, j, u, v, c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1]
if (arr[j] < arrI) {
p[i] = j
result.push(i)
continue
}
u = 0
v = result.length - 1
while (u < v) {
c = (u + v) >> 1
if (arr[result[c]] < arrI) {
u = c + 1
} else {
v = c
}
}
** **
if (arrI < arr[result[u]]) {
if (u > 0) p[i] = result[u - 1]
result[u] = i
}
}
}
u = result.length
v = result[u - 1]
while (u-- > 0) {
result[u] = v
v = p[v]
}
return result
}
浏览器的攻击事件有哪些,怎么解决?
XSS (跨站脚本攻击)
攻击原理: 攻击者向网页注入恶意脚本,当用户浏览该网页时,脚本在用户浏览器中执行
防御措施:
- 输入转义:对用户输入的特殊字符进行转义处理
- Content Security Policy(CSP):限制可执行脚本来源
- HttpOnly Cookie:防止JavaScript访问敏感Cookie
- 输入过滤:对用户输入内容进行严格过滤
CSRF(跨站请求伪造)
攻击原理: 攻击者诱导用户访问恶意网站,该网站自动向用户已登录的合法网站发送请求。
防御措施:
- CSRF Token:在表单中添加随机Token验证
- SmeSite Cookie:设置Cookie的SameSite属性为Lax或Strict
- 验证Referer/Origin头:检查请求来源是否合法
- 关键操作二次验证:对敏感操作进行二次身份验证
点击劫持
攻击原理: 攻击者使用透明层覆盖在合法网页上,诱使用户在不知情的情况下点击恶意链接
防御措施:
- X-Frame-Options:设置HTTP头位DENY或SAMEORIGIN
- CSP frame-ancestors:使用CSP限制页面被嵌入
- JavaScript防御:检测页面是否被嵌入到iframe中
注入攻击
攻击原理: 攻击者通过输入恶意数据,使后端执行非法SQL或者系统命令
防御措施:
- 参数化查询:避免拼接SQL语句
- 输入验证:对用户输入进行严格验证
- 最小权原则:限制数据库账号权限
- 使用ORM框架:减少直接编写sql的机会
Vue3 中 ref 的响应式实现原理
核心代码实现
js
function ref(value) {
return createRef(value, false);
}
function createRef(rawValue, shallow) {
// 如果已经是ref,直接返回
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl {
constructor(value, _shallow = false) {
this._shallow = _shallow;
this.__v_isRef = true; // 标识这是一个ref对象
this._rawValue = _shallow ? value : toRaw(value);
this._value = _shallow ? value : toReactive(value);
// 依赖收集的dep集合
this.dep = undefined;
}
get value() {
// 收集依赖
trackRefValue(this);
return this._value;
}
set value(newVal) {
// 比较新值和旧值
newVal = this._shallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : toReactive(newVal);
// 触发更新
triggerRefValue(this, newVal);
}
}
}
// 简化版的track和trigger实现
function trackRefValue(ref) {
if (shouldTrack && activeEffect) {
ref.dep ??= new Set();
ref.dep.add(activeEffect);
}
}
function triggerRefValue(ref, newValue) {
if (ref.dep) {
const effects = [...ref.dep];
for (const effect of effects) {
effect.run();
}
}
}
响应式流程解析
1.创建阶段
- 当调用ref(value)时,会创建一个RefImpl实例
- 原始值被存在_rawVlaue中
- 如果值是对象,会调用toReactive将其转换为响应式对象
2.访问阶段
- 当通过.value访问时,触发get value()方法
- 调用trackRefValue收集当前活跃的effect作为依赖
3.修改阶段
- 当修改.value时,触发set value()方法
- 比较新旧值是否发生变化
- 如果变化,更新值并调用triggerRefValue触发依赖更新