前端面试小结

622 阅读9分钟

一.前端解决跨域问题的方法有哪些?

1.CORS(跨域资源共享)

原理: 服务端设置HTTP响应头(如Access-Control-Allow-Origin)声明允许跨越的源

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配置代理

devServer: {
  proxy: {
    '/api': {
      target: 'http://target-server.com',
      changeOrigin: true,
      pathRewrite: { '^/api': '' }
    }
  }
}

vite配置

server: {
  proxy: {
    '/api': {
      target: 'http://target-server.com',
      changeOrigin: true,
      rewrite: path => path.replace(/^\/api/, '')
    }
  }
}

适用场景: 开发环境

3.JSONP

原理: 利用script标签不受同源策略限制的特性,通过回调函数接收数据 实现:

  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布局
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center;     /* 垂直居中 */
  height: 100vh;           /* 容器高度 */
}

.inner-element {
  /* 内容样式 */
}
2.Grid布局
.container {
  display: grid;
  place-items: center; /* 同时居中 */
  height: 100vh;
}
3.绝对定位+Transform (未知尺寸元素)
.container {
  position: relative;
  height: 100vh;
}

.inner-element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 自适应元素尺寸 */
}
4.Margin:auto(需配合绝对定位)
.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布局
.container {
  display: table;
  width: 100%;
  height: 100vh;
}

.inner-wrapper {
  display: table-cell;
  vertical-align: middle; /* 垂直居中 */
  text-align: center;     /* 水平居中 */
}

.inner-element {
  display: inline-block; /* 使水平居中生效 */
}
6.文本垂直居中
    .text{
        line-height:100px //值等于容器高度
        text-align: center;
    }

三.this指向有哪些场景,怎么改变this指向

在js中,this是一个特殊的关键字,它的取值决定于函数被调用的方式。this的指向在函数定义时无法确定,只有在函数执行的时候才能确定

this指向的7种常见场景

1.全局上下文中的this
console.log(this); // 浏览器中: Window 对象 | Node.js中: global 对象
2.函数调用中的this
function showThis() {
  console.log(this); // 非严格模式: Window | 严格模式: undefined
}
showThis();
3.对象方法中的this
const user = {
  name: '张三',
  greet() {
    console.log(`你好, ${this.name}!`); // this 指向 user 对象
  }
};
user.greet(); // 输出: 你好, 张三!
4.构造函数中的this
function Person(name) {
  this.name = name; // this 指向新创建的实例
  this.sayHello = function() {
    console.log(`我是${this.name}`);
  };
}

const person1 = new Person('李四');
person1.sayHello(); // 输出: 我是李四
5.事件处理函数中的this
document.getElementById('myButton').addEventListener('click', function() {
  console.log(this); // 指向触发事件的元素 (button)
});
6.箭头函数中的this
const obj = {
  value: 42,
  getValue: function() {
    setTimeout(() => {
      console.log(this.value); // 42 (this 继承自外层作用域)
    }, 100);
  }
};
obj.getValue();
7.类中的this
class Counter {
  constructor() {
    this.count = 0;
  }
  
  increment() {
    this.count++;
    console.log(this.count);
  }
}

const counter = new Counter();
counter.increment(); // 输出: 1

计算精度问题的解决方案

1.使用整数计算
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
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进行安全比较
function numbersEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

console.log(numbersEqual(0.1 + 0.2, 0.3)); // true
4.处理大整数时使用BigInt
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在编译阶段检测静态节点(不依赖响应式数据的节点),将它们提升到渲染函数之外,避免在再次渲染时重新创建这些节点

// 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过程可以跳过不需要更新的节点

// 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时只需要遍历动态节点,跳过静态节点

<!-- 模板 -->
<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算法

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 移动操作:

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 的响应式实现原理

核心代码实现
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触发依赖更新