前端八股总结

一、理解修改dom和重排重绘的过程

display:none和visibility:hidden的区别。visibility:hidde还在渲染树中,只要在渲染树中,修改就会消耗性能。

javascript 复制代码
// 消耗性能,不建议
for(let i=0; i<100; i++){
  let div = document.createElement('div');
  container.appendChild(div); // 每次 append → 一次重排
}

// 批量操作dom,建议
let nodes = [];

for(let i=0; i<100; i++){
  let div = document.createElement('div');
  nodes.push(div); // 先放进数组
}

container.append(...nodes); // ✅ 一次性 append

transform/opacity(仅触发合成,不触发重排重绘),浏览器会把他们提升为GPU图层,不占用cpu,不干扰主线程。可使用will-change: transform给高频动效元素开启GPU加速,但不可滥用。

二、懒加载和虚拟列表

1. 懒加载的使用IntersectionObserver

  • 代码监听所有非首屏元素,由浏览器原生高效监控,不卡主线程;
  • 元素进入视口才渲染 DOM,渲染后取消监听;
  • 渲染后滚出视口,完全不处理,DOM 保留,无任何性能损耗;
  • 这是最优懒加载方案:首屏渲染树最小,后续无冗余操作。
javascript 复制代码
// 1. 创建一个「视口监听器」:专门盯着元素是否进入屏幕
const observer = new IntersectionObserver((entries) => {
  // entries = 所有被监听的元素的状态集合
  entries.forEach(entry => {
    // 2. 核心判断:元素【进入视口】了吗?
    if (entry.isIntersecting) {
      const el = entry.target; // 获取当前这个元素
      // 3. ✨ 关键:进入视口 → 才渲染真实 DOM(首屏不渲染,优化首屏)
      el.innerHTML = `<div class="real-dom">${el.dataset.content}</div>`;
      // 4. ✨ 最关键:加载完成 → 立刻取消监听!
      // 从此这个元素和监听器彻底没关系,滚来滚去都不会再触发任何逻辑
      observer.unobserve(el);
    }
    // ⚠️ 没有写 else!所以:元素【滚出视口】→ 啥也不做!DOM 保留!
  });
});

// 5. 给【所有非首屏占位元素】都开启监听
document.querySelectorAll('.lazy-placeholder').forEach(el => {
  observer.observe(el);
});

2. 虚拟列表和懒加载对比

  • 只渲染30-50 条,渲染极少的dom = 虚拟列表
  • 不管数据多少,页面 DOM 永远只有 30-50 个
  • 比普通懒加载更强,性能天花板
  • 普通懒加载:DOM 只增不减 、
  • 虚拟列表:DOM 有增有减,数量永远不变

3. 虚拟列表和懒加载的实际使用

例1:电商商品详情页(最经典)

顶部:商品图、价格、购买按钮(首屏必须加载)

中间:详情介绍(首屏必须加载)

底部:评价列表、推荐商品、售后服务、相关好物(非首屏)

👉 这些底部模块,必须用懒加载! 它们不是长列表 每个模块结构完全不同 虚拟列表用不了 首屏不渲染,减少渲染树节点 滚动到底部再加载 这就是懒加载的主场!

例2:知乎 / 掘金文章页

顶部:文章标题、内容(首屏)

底部:评论区、相关文章、热门推荐(非首屏)

👉 评论区和推荐模块 → 懒加载 不是列表,虚拟列表用不了!

例3:B 站首页

顶部:导航、banner(首屏)

下方:各种分区、直播、推荐(非首屏)

👉 下方所有大模块 → 懒加载

例4:你的个人主页

头像、昵称(首屏)

下方:作品、动态、收藏、设置(非首屏)

👉 全部 → 懒加载
说明一点:也可采用分页。但分页多用于后台管理系统、传统工具页。像电商商品列表、社交平台信息、移动端长列表多采用虚拟列表。

三、promise

all:一个失败 → 立刻 catch,但其他继续执行,结果扔掉;若都成功,则返回[数据1, 数据2, 数据3]

race:一个完成 → 立刻返回,但其他继续执行,结果扔掉;

allSettled:全部等完,一个都不放过,返回[

{ status: 'fulfilled', value: 数据1 },

{ status: 'rejected', value: 数据2 },

{ status: 'fulfilled', value: 数据3 }

],不会进入catch;

javascript 复制代码
// 整体捕获
async function getAll() {
  try {
    let [res1, res2] = await Promise.all([p1(), p2()]);
  } catch (err) {
    // 任何一个失败,都会进 catch;已经开始的异步:会继续执行,不会停止,只是结果被抛弃了
    console.log("接口失败");
  }
}```

// 单独捕获,一个失败不影响其他
async function getAll() {
  try {
    let [res1, res2] = await Promise.all([
      p1().catch(err => null),  // 失败返回 null
      p2().catch(err => null)
    ]);
  } catch (err) {
    // 这里不会触发,因为错误都被内部 catch 了
  }
}

四、箭头函数

1.为什么要有箭头函数

  • 为了固定this:让this不乱跑
  • 简化代码:少些function、return、{},写法更优雅

例如:

javascript 复制代码
const obj = {
  name: "小明",
  getData: function() {
    // 这里的 this 指向 obj 
    console.log(this.name); // 小明

    setTimeout(function() {
      // 这里的 this 指向 window (因为定时器是window调用的)
      console.log(this.name); // undefined!
    }, 1000);
  }
};

// 以前为了保住 this,必须写丑陋的代码:
let _this = this; // 缓存 this
setTimeout(function() {
  console.log(_this.name);
});

// 箭头函数没有自己的 this,它的 this 是继承来的,从父作用域继承,永远不变!
const obj = {
  name: "小明",
  getData: function() {
    setTimeout(() => {
      // 这里的 this 直接继承外层 getData 的 this
      // 稳稳指向 obj ,永远不跑偏!
      console.log(this.name); // 小明
    });
  }
};

五、样式适配

最常用的vw和rem,现在主流使用vw。

安装npm install postcss-px-to-viewport,在项目根目录新建文件:postcss.config.js。这样在代码中写px,运行后自动会转为vw

javascript 复制代码
module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      viewportWidth: 375,    // 设计图宽度(最常用)
      viewportUnit: "vw",    // 转换成 vw
      unitPrecision: 5,      // 保留5位小数
    },
  },
};

安装npm install postcss-pxtorem,在项目根目录新建文件:postcss.config.js。这样在代码中写px,运行后自动会转为rem

javascript 复制代码
document.documentElement.style.fontSize = clientWidth / 10 + 'px';
javascript 复制代码
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 37.5,   // 1rem = 37.5px
      propList: ['*'],   // 全部转换
    }
  }
}

750的图需先/2,2倍图,高倍图。uniapp除外。

ui给的图都是物理像素,代码中计算的都是逻辑像素

六、vue2框架和vue3框架的区别

1、开发语法:Options API vs Composition API(最大区别)

2、响应式原理(底层核心差异)

  • Vue2:基于 Object.defineProperty()
    缺陷:无法监听对象新增 / 删除属性、数组下标修改 /长度修改,必须用set/set/set/delete 兜底。
  • Vue3:基于 ES6 的 Proxy 代理
    优势:直接监听整个对象 /数组,支持所有增删改查操作,无需$set,性能更高。

3、生命周期函数

  • setup 执行时机早于 Vue2 的 beforeCreate/created,这两个钩子直接废弃;
  • 其余生命周期加前缀 on:
    beforeMount → onBeforeMount
    mounted → onMounted
    beforeDestroy →onBeforeUnmount
    destroyed → onUnmounted
    新增调试钩子:onRenderTracked/onRenderTriggered。

4、模板根节点

  • Vue2:模板必须有且仅有一个根节点,所有内容要套一个div
  • Vue3:支持多根节点(Fragment片段),无需多余根标签,代码更简洁。

5、TypeScript 支持

  • Vue2:原生 TS 支持极差,需要依赖第三方库,类型推导弱;
  • Vue3:全程用 TS 重写,原生完美支持TS,自动类型推导,无需额外配置

6、打包体积

  • Vue2:全局引入 API,未使用的功能也会被打包,体积偏大;
  • Vue3:支持 Tree-Shaking(摇树优化),未使用的 API(如transition)会被自动剔除,打包体积更小(最小仅 10KB 左右)。

7、性能优化

  • 编译时优化:静态节点提升、事件缓存、按需更新;
  • 响应式性能:Proxy 比 defineProperty 效率更高;
  • 内存占用更低,大型应用流畅度远超 Vue2。

8、新增重磅特性

  • Teleport(传送门):把组件渲染到任意 DOM 节点(专治弹窗、遮罩层级问题);
  • Suspense:异步组件加载时展示占位符;
  • emits 选项:强制声明自定义事件,代码更规范;
  • 组合式函数:替代 Vue2 的 mixin(解决命名冲突、数据来源不清晰问题)。

9、全局 API 变更

javascript 复制代码
// 全局单例
import Vue from 'vue'
Vue.use(xxx)

// 只引入了createApp,可以创建无数个独立实例
import { createApp } from 'vue'
const app = createApp(App)
app.use(xxx)

七、虚拟dom

1、根据template和setup里的初始数据,两者结合在内存中画出第一份虚拟dom,vue拿着这个虚拟 DOM(JS 对象),命令浏览器创建对应的页面元素(挂载),浏览器收到命令,全新创建真实 DOM。把这份虚拟dom存起来当旧dom。

2、真实的dom是浏览器节点,不是js对象。

3、数据改变后,vue生成新的虚拟dom树,和旧的虚拟树进行对比,Vue只保留一份最新的虚拟 DOM,旧的会被自动清理(垃圾回收),永远只存最新的当对比草稿;

名称 本质是什么 谁生成的 存在哪里 类型
虚拟 DOM 纯JS 对象 Vue 生成 JS 内存 代码 / 对象
真实 DOM 浏览器原生节点 浏览器生成 浏览器渲染引擎 页面元素 / 硬件渲染

beforeCreate 之前:初始化组件空壳

beforeCreate:钩子执行

created 之前:初始化 data、methods 等

created:钩子执行(此时:有数据,无虚拟 DOM)

✨ 关键阶段:created 执行完 → vue内部编译 template+数据 → 生成虚拟 DOM

beforeMount:钩子执行(此时:虚拟 DOM 已生成,无真实 DOM)

beforeMount 之后:虚拟 DOM → 生成真实 DOM 并挂载到页面

mounted:钩子执行(真实 DOM 渲染完成)

八、webpack和vite的区别

Webpack Vite
开发模式 全量打包:把所有代码打包成 bundle,再启动 不打包:用浏览器原生 ESM,按需加载
启动速度 项目越大越慢 无论项目多大,秒启动
热更新 改代码→重新打包→更新,大项目卡顿 改代码→只更新单个文件,毫秒级更新
生产环境 打包构建 用 Rollup 打包(和 Webpack 结果一致)
配置 复杂,需要配 loader、plugin 开箱即用,0 配置跑 Vue 项目
适配 所有前端项目(Vue2/3/React) 主打Vue3、现代框架

九、组件中的name

1、支持递归组件

2、配合keep-alive缓存

十、大屏

1、大屏适配

scale适配就是不对页面内部元素做响应式,而是把整个大屏页面按设计稿比例写死宽高,再通过 CSS 的 transform: scale() 把整个页面等比缩放到当前屏幕,保证在任何分辨率下布局、比例、文字、图表都不变形、不挤压、不错位。

javascript 复制代码
①以设计稿(如 1920×1080)为基准,页面宽高写死。
②计算当前屏幕宽高与设计稿的缩放比例:
	scaleX = 当前宽 / 设计稿宽
	scaleY = 当前高 / 设计稿高
	最终取最小值 scale = min(scaleX, scaleY)
③用 transform: scale(scale值) 对整个页面居中等比缩放。
④监听窗口 resize,实时重新计算缩放。

2、原生js大屏写scale的话有什么缺点?

  • 文字 / 图表在极端屏幕下会模糊

    解决:用 devicePixelRatio 初始化 ECharts,提高渲染清晰度。

  • 缩放后图表事件坐标偏移

    解决:确保 transform-origin: left top,并居中定位。

  • 浏览器缩放会影响布局

    解决:resize 里做防抖,重新计算 scale。

3、性能

问题 解决
图表太多、同时渲染 → 首屏巨卡 一个屏 10~20 个图表,一起初始化,CPU 瞬间拉满 用 延时渲染 / 队列渲染 错开时间;不在可视区的图表先不渲染;用 v-if 控制,不要全部一上来就渲染
数据量太大(几千、上万条)→ 直接卡死 折线图、曲线图点太多,ECharts 扛不住 后端做 数据抽样 / 降采样;前端用 dataZoom 只渲染可视区;关闭 animation 动画
定时器不清理 → 内存越来越大,越跑越卡 大屏常用定时刷新,页面销毁定时器还在跑 onUnmounted 里必须 clearInterval;一个页面只维护一个定时器,不要一堆
图表实例不销毁 → 内存泄漏 切换页面 / 组件,ECharts 实例还在内存里 chart.dispose() ;chart = null
特效、3D、地图太耗性能 发光、阴影、渐变、3D 柱状图、地图下钻 能关就关:shadowBlur、opacity 过度地图禁用海量点标注;3D 尽量少用
频繁 setOption → 不停重绘 一更新数据就 setOption,图表疯狂重渲染 合并更新,不要频繁改;使用setOption(newOption, { notMerge: false, lazyUpdate: true })
全屏 scale + 高分辨率屏幕 → 渲染压力大 4K 屏、8K 屏、拼接屏,像素点巨多 ECharts 初始化加 devicePixelRatio 优化:echarts.init(dom, null, { devicePixelRatio: 1 })
大量图表一起 resize → 掉帧、卡顿 窗口变化,所有图表同时重绘 防抖;只对当前可见图表做 resize
CSS 动画 + JS 动画 + 图表动画 → 性能打架 页面流光、滚动、呼吸灯 + ECharts 动画一起跑 优先用 CSS3 硬件加速(transform /opacity);禁用不必要的 ECharts 动画;大量动效少用 js 去改 DOM
接口请求频繁 → 页面一直抖动 1s 一次刷新,接口一直请求、一直渲染 延长刷新间隔(3s、5s、10s);接口做缓存;数据不变就不更新图表
  • 延时渲染/队列渲染:延时渲染是会创建定时器,比如间隔30~50ms,到点就渲染一个,适合图标少,5个以内的时候。队列渲染是,前一个执行完后一个再执行,严格串行,适合图标多的时候。
  • 数据抽样 / 降采样:把大量数据 "压缩变少",只保留趋势,不影响看图,让 ECharts 不卡顿。采样方式可以用均匀抽样或LTTB算法。
  • echarts中提升性能的属性
javascript 复制代码
chart.setOption(newOption, {
  notMerge: false,    // 合并更新,不清空重画
  lazyUpdate: true   // 延迟更新,合并多次操作
})

4、数据分片

javascript 复制代码
<template>
  <div id="chart" style="width:600px;height:400px"></div>
</template>

<script setup>
import * as echarts from 'echarts'
import { onMounted } from 'vue'

let chart = null

// 假设有 10000 条超大数据
const bigData = Array.from({ length: 10000 }, (_, i) => ({
  name: '数据' + i,
  value: Math.random() * 1000
}))

// 分片配置
const pageSize = 200    // 每片200条
let current = 0         // 当前渲染到第几片

onMounted(() => {
  chart = echarts.init(document.getElementById('chart'))
  startLoadSlice()
})

// 🔥 核心:分片加载函数
function startLoadSlice() {
  // 1. 取出当前这一小片数据
  const sliceData = bigData.slice(current, current + pageSize)

  // 2. 追加渲染到图表
  chart.setOption({
    xAxis: { data: chart.getOption()?.xAxis?.[0]?.data || [] },
    series: [{
      data: [
        ...(chart.getOption()?.series?.[0]?.data || []),
        ...sliceData
      ]
    }]
  })

  // 3. 下标前进
  current += pageSize

  // 4. 还没渲染完,继续渲染下一片
  if (current < bigData.length) {
    // 用 setTimeout 让出主线程,页面不卡
    setTimeout(startLoadSlice, 50)
  }
}
</script>

注意和空闲加载区分requestIdleCallback,空闲加载适用于不是必须立刻渲染完成时

javascript 复制代码
let bigData = [...] // 1万条数据
let pageSize = 100
let current = 0

function loadSlice() {
  // 取一小片
  let slice = bigData.slice(current, current + pageSize)

  // 渲染这一片
  chart.setOption(...)

  current += pageSize

  // ✅ 关键:等浏览器下一次空闲,再渲染下一片
  if (current < bigData.length) {
    requestIdleCallback(loadSlice)
  }
}

// 开始
requestIdleCallback(loadSlice)

十一、webpack分包

javascript 复制代码
// 没分包
import Home from './views/Home.vue'
import About from './views/About.vue'

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
]
javascript 复制代码
// 分包
const routes = [
  { 
    path: '/', 
    component: () => import('./views/Home.vue')  // 🔥 这就分包了
  },
  { 
    path: '/about', 
    component: () => import('./views/About.vue') // 🔥 这也分包了
  }
]

十一、websocket

javascript 复制代码
class Socket {
  constructor(url) {
    this.url = url
    this.ws = null
    this.lock = false // 防止重复连接
    this.heartTimer = null // 心跳定时器
    this.reconnectTimer = null // 重连定时器
    
    this.connect() // 一进来就连接
  }

  // 1. 创建连接
  connect() {
    if (this.lock) return
    this.ws = new WebSocket(this.url)

    // 连接成功
    this.ws.onopen = () => {
      console.log('连接成功')
      this.lock = false
      this.startHeart() // 开启心跳
    }

    // 收到后端推送的消息
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      console.log('收到数据:', data)
      // 在这里更新页面/图表
    }

    // 关闭 → 重连
    this.ws.onclose = () => {
      console.log('断开连接')
      this.lock = false
      this.reconnect() // 自动重连
    }

    // 报错 → 重连
    this.ws.onerror = () => {
      console.log('连接异常')
      this.lock = false
      this.reconnect()
    }
  }

  // 2. 心跳(保活)
  startHeart() {
    this.heartTimer = setInterval(() => {
      if (this.ws.readyState === 1) {
        this.ws.send(JSON.stringify({ type: 'heartbeat' }))
      }
    }, 30000) // 30秒一次心跳
  }

  // 3. 自动重连
  reconnect() {
    if (this.lock) return
    this.lock = true

    clearInterval(this.heartTimer)
    clearTimeout(this.reconnectTimer)

    // 3秒后重试
    this.reconnectTimer = setTimeout(() => {
      console.log('重试连接...')
      this.connect()
    }, 3000)
  }
}

// 使用
const ws = new Socket('ws://xxx.xxx.xxx:8080')
相关推荐
今天减肥吗1 小时前
前端面试题
开发语言·前端·javascript
Rabbit_QL1 小时前
【前端UI行话】前端 UI 术语速查表
前端·ui·状态模式
码码哈哈0.01 小时前
LangChain 快速入门(从0到可用)
开发语言·python·langchain
小码哥_常2 小时前
一文带你吃透Android BLE蓝牙开发全流程
前端
熊文豪2 小时前
Java 入门指南
开发语言·python
小码哥_常2 小时前
从“新老交锋”看Retrofit与Ktor
前端
小菜鸡桃蛋狗2 小时前
C++——类和对象(上)
开发语言·c++
伯恩bourne2 小时前
Google Guava:Java 核心工具库的卓越之选
java·开发语言·guava
小J听不清2 小时前
CSS 外边距(margin)全解析:取值规则 + 实战用法
前端·javascript·css·html·css3