前端复健笔记1
1.vite里source-map配置有什么作用
Vite 中,source-map(源码映射)配置的核心作用是将构建或编译后的代码映射回原始源代码,从而在浏览器开发者工具中直接查看和调试原始的 TypeScript、JSX、SCSS 等源码,而非压缩或转译后的产物代码,方便设置断点、查看变量,大幅提升问题排查效率。配置文件:vite.config.js,值:true(生成 .map 文件,并在 bundle 中添加 //# sourceMappingURL= 注释) | false | hidden(生成 .map 文件,但不添加注释) | inline(Source Map 内联到 JS 文件中(作为 data URI))。
import { defineConfig } from 'vite';
export default defineConfig({
build: {
sourcemap: process.env.NODE_ENV === 'production' ? 'hidden' : true
}
});
Vite 在 vite dev 模式下默认使用 inline-source-map,无需额外配置即可调试 TS/SCSS 等源码。
开发环境:保持 sourcemap: true(默认),确保快速精准调试。
生产环境:若无错误监控系统 → sourcemap: false;若使用 Sentry 等工具 → sourcemap: 'hidden',并上传 .map 文件至监控平台。
大型项目:结合 rollupOptions.output.sourcemapExcludeSources 减少 .map 文件体积。
2.vue-router history模式上线时需要注意什么事项
①后端服务器配置重定向规则。History模式依赖 HTML5 History API,URL无#,刷新或直接访问路由时会向服务器发送请求。若服务器未配置,将因找不到对应静态资源返回 404。需将所有前端路由请求回退至 index.html,由 Vue Router 接管。
②正确设置 base 路径(如部署在子目录)。若应用部署在非根域名下(如 https://example.com/my-app/),需在创建 Router 实例时指定 base:
const router = createRouter({
history: createWebHistory('/my-app/'), // Vue 3
// 或
mode: 'history',
base: '/my-app/',
routes
});
③检查资源路径引用方式,确保 index.html 中引入的 CSS、JS、图片等资源使用绝对路径(以 / 开头),避免因路由层级导致 404。
④验证打包配置(如使用 Vue CLI 或 Vite)。Vue CLI:确保 publicPath 为 '/'(默认)或与部署路径一致;Vite:检查 base 配置。
// vue.config.js
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/'
};
// vite.config.js
export default defineConfig({
base: '/my-app/'
});
⑤上线前务必在浏览器中直接访问非首页路由(如 /about)并刷新,确认是否正常加载。
3.scss中除了变量还有哪些常用语法
①嵌套规则(Nesting)。允许将子选择器嵌套在父选择器内部,使代码结构更清晰、贴近 HTML 层级。
②混合宏(Mixins)。可定义可重用的样式块,支持参数和默认值,类似函数。
③继承(@extend)。一个选择器可以继承另一个选择器的所有样式,减少重复代码。
④函数(Functions)。支持内置函数(如 lighten()、darken()、rgba() 等),也支持自定义函数。
⑤控制指令(Control Directives)。包括条件语句(@if / @else)和循环(@for、@each、@while),用于动态生成样式。
⑥占位符选择器(Placeholder Selectors)。以 % 开头,定义不会直接输出的可继承样式,常用于抽象公共样式。
⑦导入(@import)。可将多个 SCSS 文件合并编译,支持模块化开发。
⑧插值(Interpolation)。使用 #{} 在选择器、属性名或值中插入变量。
⑨默认变量(!default)。设置变量的默认值,允许后续覆盖。
⑩注释(Comments)。支持单行(//,不输出到 CSS)和多行(/* */,会保留)注释。
⑩①属性嵌套(Nested Properties)。对于具有相同前缀的属性(如 border、font),可进行嵌套写法。
示例:
.nav {
ul { margin: 0; padding: 0; }
li { display: inline-block; }
a { color: blue; }
}
@mixin border-radius($radius: 5px) {
border-radius: $radius;
}
.box { @include border-radius(10px); }
%btn { padding: 10px; border: none; }
.btn-primary { @extend %btn; background-color: blue; }
@function px-to-rem($px, $base: 16px) {
@return ($px / $base) * 1rem;
}
.text { font-size: px-to-rem(18px); }
@for $i from 1 through 3 {
.col-#{$i} { width: 100% / $i; }
}
%center { display: flex; justify-content: center; }
.container { @extend %center; }
@import 'variables';
@import 'buttons';
$property: margin;
.box { #{$property}: 10px; }
$color: blue !default;
$color: red; // 覆盖默认值
.box {
font: {
family: Arial;
size: 16px;
}
}
4、【JS】对闭包的理解
闭包就是在一个内层函数中访问到其外层函数的作用域。在 JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来,作为函数内部与外部连接起来的一座桥梁。
闭包的优缺点:优点:1:变量长期驻扎在内存中,可以重复使用变量;2:闭包可以实现局部变量,避免全局变量的污染;3:可用于声明私有函数和变量。缺点:1.滥用闭包会造成内存泄露;2.性能损失大。
闭包的工作原理:1.函数的作用域:在JavaScript中,每个函数都有自己的作用域。当一个函数在其内部创建另一个函数时,内部函数会捕获其外部函数的变量。2.变量的生命周期:通常,当一个函数执行完毕后,其局部变量会被销毁。但当内部函数被返回或以其他方式被外部访问时,这些变量不会被销毁(无法被垃圾回收机制回收),因为它们被内部函数“捕获”了。
闭包的作用:私有化变量、模块化、缓存、防抖节流、循环绑定事件。
防抖示例:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
5.事件循环(Event Loop)
事件循环是由一个或以上的任务队列组成的。由于 JavaScript 是单线程语言,所以在 JS 中所有的任务都需要排队执行,这些任务共同组成了任务队列,依次排队执行的过程,形成一个执行栈。在任务队列中,最先执行是同步任务。
同步任务就是当上一个任务执行完成后,接下来可以立即执行的任务。它们在主线程上依次排队执行,直到清空。
异步任务就是需要等待被通知才以执行的任务,它们不会直接进入主线程执行,而是进入到微任务队列或下一次事件循环中的任务队列进行等待。
任务队列中遇到的宏任务将进入到下一次事件循环的任务队列,而微任务则会被放入到本次事件循环的微任务队列中。
常见宏任务:script、setTimeout、setInterval、document.appendChild()
常见微任务:Promise.then/catch/finally、MutationObserver、queueMicrotask
执行顺序:同步 → 微任务 → 渲染 → 下一个宏任务
6.promise封装接口返回的时候,如何串联后续流程(如另一个接口)
常见方法:
①使用.then()链式调用:每个.then()接收上一个Promise的成功结果,并返回一个新的Promise,从而可以无限串联。例如,先通过fetch获取数据,然后解析JSON,最后处理业务数据。
②使用async/await语法:在支持async/await的环境中,使用await关键字等待每个Promise解决,实现串行执行,代码更简洁。
③封装接口调用:可以将重复的Promise代码封装成函数,减少冗余代码。例如,针对同一个接口的多次嵌套调用,可以封装成函数进行调用。
④使用Promise.all():当需要同时处理多个异步请求并控制返回结果的顺序时,可以使用Promise.all()。它接收一个Promise数组,并返回一个新的Promise,当所有输入的Promise都解决后,新的Promise也解决,并返回一个包含所有结果的数组。
⑤使用Promise.race():如果只需要返回最快执行完成的Promise的结果,可使用Promise.race()。它接收一个Promise数组,返回一个新的Promise,当第一个输入的Promise解决或拒绝时,新的Promise也解决或拒绝,并返回该结果。
7.深拷贝与浅拷贝相关
JavaScript中,对象和数组是引用类型,这意味着将它们赋值给一个变量或者作为函数参数传递时,实际上是在传递一个指向内存中对象的引用,而不是对象本身的副本。
①浅拷贝:只复制对象的第一层属性。也就是说,如果原始对象的属性是基本类型(如数字、字符串、布尔值),那么浅拷贝会复制这些值,且完全独立互不影响;但如果属性是引用类型(如数组、对象、函数),那么拷贝的只是引用,而不复制引用指向的内存地址中的数据,引用类型字段指向同一对象,修改会互相影响。
浅拷贝的方法:Object.assign();展开运算符(...);slice()方法【返回一个新数组,不改变原数组,但若数组包含对象,对象引用仍共享】……
②深拷贝:深拷贝会创建一个全新的对象,并且递归地复制所有属性,使得原始对象和拷贝对象完全独立,互不影响。深拷贝后的对象中所有的属性都是新的引用,即使属性是引用类型,也会被完全复制。
深拷贝的方法:JSON.parse(JSON.stringify(obj))【不能处理函数、undefined、循环引用、特殊对象(如Date、RegExp)等】;lodash的cloneDeep()……
8.computed和watch
相同点:
- 本质上都是一个watcher实例,都通过响应式系统与数据,页面建立通信,只是行为不同
- 计算属性和监听属性对于新值和旧值一样的赋值操作,都不会做任何变化,不过这一点的实现是在响应式系统完成的。
- 都是以Vue的依赖追踪机制为基础的
不同点:
机制不同:computed是计算属性,基于响应式依赖自动缓存派生值,依赖不变时直接返回缓存结果。watch是侦听器,用于监听特定数据变化并执行副作用操作(如异步请求、复杂逻辑),无缓存机制。- 使用场景不同:
computed用于需要计算时;watch用于需要在数据变化时执行异步操作时(比如发送网络请求)、需要执行一些开销较大的操作,但又不需要实时更新UI时(比如监听浏览器的URL变化)。 computed支持缓存、不支持异步、必须同步返回值;watch:无缓存、支持异步、可深度监听对象/数组内部变化。
computed有什么场景是值发生了变化,但是结果没有被更新:
- 直接通过索引修改数组元素
- 修改数组长度(如:
arr.length = newLength) 在对象上动态添加新属性(若对象不是响应式的,或属性在初始化时不存在,直接赋值(如obj.newProp = value)- 使用非响应式对象替换响应式对象(如:
state.data = { ... },若新对象不是通过reactive()或ref()创建,则会丢失响应式连接 ) - 在
computed中读取了未被正确追踪的属性(如解构响应式对象:const { name } = this.user,此时name是普通变量,脱离了 Vue 的响应式系统 - 依赖的值是
props中未在data/setup中声明的字段 - 使用了
Object.freeze()冻结的对象(冻结后的对象属性变化不会触发响应式更新 computed返回的是引用类型,但内部结构变化未通过响应式 API 触发(如仅修改了reactive对象中的普通属性,但该对象本身未被正确初始化为响应式
9.data为什么是一个函数,并且要返回一个对象
①为什么是一个函数
为了确保每个组件实例拥有独立的状态,避免数据共享带来的副作用。
Vue 组件通常会被多次复用(如多个按钮、卡片等),如果 data 是一个普通对象,所有组件实例会共享同一个引用,导致一个实例修改数据时,其他实例也会受到影响。当 data 是函数时,Vue 在创建每个组件实例时都会调用该函数,从而获得一个全新的数据对象,彼此隔离,互不干扰
②为什么必须返回一个对象
Vue 的响应式机制(基于 Object.defineProperty 或 Proxy)只能对对象的属性进行数据劫持。返回对象后,Vue 会将其所有属性转换为 getter/setter,实现视图自动更新 对象可以包含多个属性,方便组织和访问组件状态

浙公网安备 33010602011771号