面试题汇总:
1、pinia的使用?
优点,同vuex的区别?
为什么vuex 中 会有同步异步的方法 mutations actions 而 pinia 取消了mutations方法?
2、如何画一个田的页面?
3、箭头函数的优点?
4、双向绑定的原理?
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
双向绑定的实现依赖于数据劫持
(data hijacking)和发布者-订阅者模式
(publisher-subscriber pattern)。
数据劫持和响应式系统
Vue.js通过Object.defineProperty()方法劫持对象的属性的getter和setter,从而能够监听数据的变化。当数据发生变化时,Vue实例会被通知,并触发相应的getter和setter回调函数,以更新视图。
et data = { name: 'Vue' };
Object.defineProperty(data, 'name', {
get: function() {
console.log('获取数据');
return name;
},
set: function(newValue) {
console.log('更新数据');
name = newValue;
}
});
发布者-订阅者模式
在Vue中,每个组件实例都对应一个watcher实例,它会在组件渲染过程中把属性记录为依赖,因此当依赖项的setter被调用时,会通知watcher,从而使得组件重新渲染。
// Watcher 伪代码
class Watcher {
constructor() {
// 当数据变化时,触发依赖的更新
Dep.target = this;
}
update() {
// 更新视图的逻辑
}
}
官方图例:
5、闭包的使用?
日常使用最多的场景就是使用多个重复的变量,为了不相互影响,直接放到必包中。下面案例中的变量a 相互不会影响。
(function(){
var a = 111;
})()
(function(){
var a = 222;
})()
6、 继续使用vue2有什么风险?
官方通知:
7、 vue-devtools的使用?
能够提高开发效率?
8、 rem如何配置?
具体的配置,应该如何根据设计稿配置具体的比例?
主要是通过把px 转换成 rem来实现。rem是通过根节点的fontSize 大小来进行转换的。所以我们只要动态控制根节点的fontSize大小就可以。
具体如何设置fontSize的大小呢?
移动端
:主要通过:flexible.js
, lib-flexible 库
来实现的。 这个工具主要是用来帮助我们实现动态计算的,我们只需要在页面中写px单位即可,工具会自动帮助我们转换成rem。
pc端
:主要通过 (postcss-pxtorem,amfe-flexible)来实现的。amfe-flexible 是 lib-flexible的升级版。
需要注意的是:行内样式不会转成被转成rem
9、 Echarts 如何在一个图标(折线图)中,前半段展示实线,后半段展示虚线?
import * as echarts from 'echarts';
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
legend: {
data: ['实虚线', '实虚实线']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
tooltip: {
trigger: 'axis',
formatter: function (params) {
const hasMap = {};
let html = `${params[0].name}<br>`;
params.forEach((param) => {
// 过滤无效值和重叠值
if (param.value === '-' || hasMap[param.seriesName] === param.value) {
} else {
hasMap[param.seriesName] = param.value;
html += `<span style="display: flex;flex-direction: column;">${param.seriesName}:${param.value}</span>`;
}
});
return `<div style="text-align:left">${html}</div>`;
}
},
series: [
{
name: '实虚线',
type: 'line',
smooth: false,
data: [123, 132, 101, 456, '-', '-', '-']
},
{
name: '实虚线',
type: 'line',
smooth: false,
data: ['-', '-', '-', 456, 526, 230, 152],
lineStyle: {
width: 2,
type: 'dotted'
}
},
{
name: '实虚实线',
type: 'line',
smooth: false,
data: [220, 182, '-', '-', '-', '-', '-']
},
{
name: '实虚实线',
type: 'line',
smooth: false,
data: ['-', 182, 166, 188, 290, '-', '-'],
lineStyle: {
width: 2,
type: 'dotted'
}
},
{
name: '实虚实线',
type: 'line',
smooth: false,
data: ['-', '-', '-', '-', 290, 366, 758]
}
]
};
option && myChart.setOption(option);
参考文章地址:点击跳转文章
10、vuex中mutaions 和 actions的区别?
11、vuex中 mapState 的使用?
主要作用就是为了更好的获取store里面的属性值,减少代码,更方便维护
12、大屏的开发?
13、vue-router 里面几种模式?
14、正则表达式的基本使用?
15、vue2和vue3的主要区别?
1、响应式系统:
vue2:
vue2使用Object.defineProperty,
只能监听对象的属性,无法监听数组的索引和对象属性的添加。
需要使用 Vue.set 或 Vue.delete 来更新响应式数据。
vue3:
使用 Proxy 实现响应式。
支持监听数组索引和对象属性的动态添加。
无需额外 API,直接操作数据即可触发响应式更新。
2、composition API:
Vue2:
使用 Options API(如 data、methods、computed 等)组织代码。
在复杂组件中,逻辑可能分散在不同的选项中,导致代码难以维护。
Vue3:
引入 Composition API(如 ref、reactive、watch、computed 等)。
允许将相关逻辑组织在一起,提升代码的可读性和可维护性。
更适合 TypeScript 支持。
3、性能优化:
Vue2:
虚拟 DOM 的 diff 算法是全量对比,性能开销较大。
在复杂场景下可能存在性能瓶颈。
Vue3:
虚拟 DOM 的 diff 算法优化为静态标记(Patch Flag),只对比动态部分。
支持 Tree Shaking,移除未使用的代码,减少打包体积。
整体性能提升,尤其是在大型应用中。
4、TypeScript 支持
Vue2:
对 TypeScript 的支持较弱,类型推断和类型检查不够完善。
需要使用 Vue Class Component 或 Vue Property Decorator 来增强 TypeScript 支持。
Vue3:
完全使用 TypeScript 重写,原生支持 TypeScript。
提供更好的类型推断和类型检查,开发体验更佳。
5、生命周期钩子:
这个就不列举了
6、新特性
Vue3:
Fragment:
支持多根节点模板,无需包裹一个根元素。
Teleport:
允许将组件渲染到 DOM 中的任意位置。
Suspense:
支持异步组件的加载状态处理。
自定义渲染器:
提供自定义渲染器 API,支持非 DOM 环境的渲染(如小程序、Canvas)。
16、react 和 vue的主要区别?
17、首屏优化?
- 首屏渲染指标:fcp (first content paint --->也叫页面中第一个元素的渲染时长),lcp (largest content paint 页中最大的一个元素渲染的时长) ;
- 懒加载:需要我们代码去实现。第一种方式就是分页的去加载数据。
- http优化: 协商缓存,强缓存。
- 强缓存:服务端给响应头追加的一些字段 (expires --> 截止时效时间) 在expires没有时效之前,无论你怎麽刷新页面,浏览器不会请求页面,而是从缓存里面取。
- 协商缓存:是否使用缓存要跟后端商量一下,当服务端跟我们打上协商缓存的标记以后,客户端在下次刷新页面需要重新请求资源时会发送一个协商请求给服务端,服务端如果说需要变化,则会响应具体的内容,如果服务端觉得没有变化则会响应304,从浏览器缓存中获取。
18、函数式组件?
19、请说一下最近一个项目主要的技术难点是啥?
20、说一下var 和 let 的区别?
1、作用域:
-
var:
函数作用域(Function Scope):变量在声明它的整个函数内有效。
如果在函数外声明,则为全局作用域。function test() { if (true) { var x = 10; } console.log(x); // 输出 10,x 在函数内可见 }
-
let:
块级作用域(Block Scope):变量仅在声明它的块(如 {})内有效。function test() { if (true) { let y = 20; } console.log(y); // 报错:y is not defined }
let 解决的问题:
var 的函数作用域容易导致变量泄露到全局或函数外层,而 let 的块级作用域更符合直觉,减少了变量污染的风险。
2、变量提升:
- var
变量会被提升到函数或全局作用域的顶部,但赋值不会提升。
console.log(a); // 输出 undefined
var a = 5;
实际执行顺序:
var a;
console.log(a); // undefined
a = 5;
- let
变量也会被提升,但在声明之前访问会触发“暂时性死区”(Temporal Dead Zone, TDZ),导致报错。
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 10;
3、重复声明:
- var
允许在同一作用域内重复声明变量,不会报错。
var c = 1;
var c = 2;
console.log(c); // 输出 2
- let
不允许在同一作用域内重复声明变量,会报错。
let d = 1;
let d = 2; // 报错:Identifier 'd' has already been declared
let 解决的问题:
var 的重复声明可能导致变量被意外覆盖,而 let 的严格限制避免了这种问题。
4、全局作用域下的行为:
- var
在全局作用域下声明的变量会成为全局对象(如 window)的属性。
var e = 5;
console.log(window.e); // 输出 5
- let
在全局作用域下声明的变量不会成为全局对象的属性。
let f = 10;
console.log(window.f); // 输出 undefined
let 解决的问题:
var 的全局变量会污染全局对象,而 let 避免了这一问题,使代码更安全。
5、循环中的行为:
- var
在循环中使用 var 声明的变量会共享同一个作用域,可能导致意外行为。
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i); // 输出 3, 3, 3
}, 100);
}
- let
在循环中使用 let 声明的变量会为每次迭代创建一个新的块级作用域,解决共享作用域问题。
for (let j = 0; j < 3; j++) {
setTimeout(() => {
console.log(j); // 输出 0, 1, 2
}, 100);
}
let 解决的问题:
var 在循环中会导致变量共享,而 let 为每次迭代创建独立作用域,避免了闭包中的常见问题。
21、watch 和 computed的区别?
使用 computed 的场景:
- 需要根据响应式数据动态计算一个值。
export default {
data() {
return {
price: 10,
quantity: 2
};
},
computed: {
totalPrice() {
return this.price * this.quantity;
}
}
};
特点:totalPrice 会根据 price 和 quantity 的变化自动更新,且具有缓存。
- 需要缓存计算结果以优化性能。
computed: {
filteredList() {
return this.list.filter(item => item.isActive);
}
}
list是不变的数据,filteredList从list里面去过滤
- 模板中有复杂逻辑需要抽离。
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
使用 watch 的场景:
- 需要在数据变化时执行副作用(如异步请求、DOM 操作)。
export default {
data() {
return {
searchQuery: '',
results: []
};
},
watch: {
searchQuery(newVal) {
this.fetchResults(newVal);
}
},
methods: {
async fetchResults(query) {
this.results = await api.search(query);
}
}
};
- 需要监听对象或数组的深层变化。
watch: {
user: {
handler(newVal, oldVal) {
console.log('用户信息变化:', newVal);
},
deep: true // 深度监听
}
}
当需要监听对象或数组内部的变化时,使用 watch 的 deep 选项。
- 需要执行非响应式操作。当需要在数据变化时执行非响应式操作(如操作 DOM、调用第三方库)时
watch: {
count(newVal) {
document.title = `当前计数: ${newVal}`;
}
}
注意事项⚠️
1、避免滥用watch
如果可以用computed实现,优先使用computed,因为watch 没有缓存,性能开销大。
2、computed不支持异步:
如果计算逻辑需要异步,只能使用watch。
3、watch的深度监听性能开销:
深度监听(deep:true) 会遍历对象或者数组的所有属性,可能导致性能问题,需谨慎使用。
4、immediate选项:
在watch中,如果需要立即执行回调,可以设置immediate:true.
watch: {
searchQuery: {
handler(newVal) {
this.fetchResults(newVal);
},
immediate: true // 立即执行
}
}
22、如果清除定时器?
let timer = setTimeout(()=>{
console.log(111);//自己的处理逻辑
});
//清楚定时器
timer = null; //触发垃圾回收
//或者使用clearTimeout
clearTimeout(timer);
23、定时器的使用场景?
-
延迟执行代码
-
模拟异步行为
-
防抖与节流
-
轮询与超时控制
-
动画与过渡效果
-
延迟初始化
24、异步任务:
异步操作的底层机制:事件循环(Event Loop)
JavaScript 通过事件循环机制处理异步操作,其核心包括:
调用栈(Call Stack):执行同步任务。
任务队列(Task Queue):存放异步任务的回调函数。
事件循环:不断检查调用栈是否为空,如果为空,则将任务队列中的回调函数推入调用栈执行。
宏任务(Macro Task):setTimeout、setInterval、I/O 操作等。
微任务(Micro Task):Promise.then、MutationObserver 等。
事件循环会优先执行所有微任务,然后再执行一个宏任务。
25、promise的三种状态,如何触发:
Promise 是 JavaScript 中用于处理异步操作的对象,它有三种状态:Pending(等待)、Fulfilled(已成功) 和 Rejected(已失败)。
触发fulfilled状态:
- 调用 resolve(value),其中 value 是异步操作成功的结果。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("操作成功");
}, 1000);
});
promise.then((result) => {
console.log(result); // 输出:操作成功
});
触发 Rejected 状态:
- 调用 reject(reason),其中 reason 是异步操作失败的原因(通常是一个错误对象或字符串)。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject("操作失败");
}, 1000);
});
promise.catch((error) => {
console.error(error); // 输出:操作失败
});
26、vite优化:
1、分包
原理是通过vite配置把一些不变的依赖,分开打包,这样就可以利用浏览器的缓存机制,减少http请求。
2、gzip压缩
原理是压缩生产包的体积,当我们请求的时候服务端返回压缩包,浏览器去自动的解压缩(这个是需要浏览器花时间去处理的)。所以一般不建议使用,除非生产包的体积很大。
3、动态导入:
原理是使用es6的import(‘./index.vue’)函数。这样配置到router路由里面就会动态的记载,只有页面渲染这个页面的时候才会执行页面的代码。
4、CDN加速:
原理是通过把第三方依赖,配置到cdn上,这样假如生产包的服务器是在杭州,而你现在在美国,当你发送请求的时候,配置的cdn会从美国去获取,而不会从杭州去拿,这样从杭州的服务端请求压力会更小。
完整代码如下:vite.config.ts
import { defineConfig } from "vite";
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { resolve } from 'path'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import {ViteAliases } from 'vite-aliases'
import postcssPresetEnv from 'postcss-preset-env';
export default defineConfig({
plugins: [
ViteAliases(),//别名插件
vue(),//vue插件
vueJsx(),//vue-jsx插件
Components({//自动导入组件插件
resolvers: [ElementPlusResolver()],
}),
createSvgIconsPlugin({//svg图标插件
// 指定需要缓存的图标文件夹
iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
}),
],
optimizeDeps:{
exclude:[] //将指定数组中的依赖不进行预构建处理
},
envPrefix:'ENV', //设置环境变量前缀
css:{
modules:{//对 css模块化的默认行为进行覆盖
localsConvention:'camelCaseOnly', //将类名转换为驼峰命名法
scopeBehaviour:'local', // global/local 默认是 local,即只在当前模块内有效,global 则在整个项目中有效,但是不建议使用,因为会导致样式污染
generateScopedName:'[name]_[local]_[hash:base64:5]', //自定义生成的类名格式
hashPrefix:"hello",
globalModulePaths:['src/assets/css'], //设定你不想参与到css模块化的路径,默认是node_modules下的文件不会被处理到,这里可以额外添加路径
},
preprocessorOptions:{//对预处理器进行配置
less:{
//配置参数可以查询less官方文档
//webpack里面给less-loader配置就行
math:'always', //控制less中的数学计算是否开启,默认是parens-division,即只有在用到除法时才会进行计算
globalVars:{//全局变量,使用@mainColor引用
mainColor:'#ff0'
}
},
sass:{
//配置参数可以查询sass官方文档
}
},
devSourcemap:true, //是否开启sourceMap,默认是false,这样在开发环境下可以看到源代码,便于调试
postcss:{
plugins:[postcssPresetEnv()]
}
},
build:{//配置构建选项
rollupOptions:{//配置rollup打包选项
output:{//配置输出选项
//配置静态资源文件的名字,默认是[name].[hash][ext]
assetFileNames:'[name]-[hash][ext]',
}
},
assetsInlineLimit:4096000, // 4000kb 小于这个值的图片会被转为base64编码,以减少http请求数。单位是字节(byte)
outDir:'dist', //指定输出路径
assetsDir:'assets', //指定生成静态资源的存放路径,相对于 outputDir。默认是 assets
emptyOutDir:true, //打包前清空输出目录,默认为 true。如果你的项目是多页面应用,并且每个页面的入口文件都使用了不同的 HTML 模板,则可以设置为 false 以避免重新
}
});
27 vite跨域配置:
当我们使用A地址去请求B地址的时候,B会返回数据(浏览器在请求阶段不会做拦截,只有在响应的时候做拦截),如果B认识A就会携带一个标识,来告诉浏览器这是我认识的,可以放行的。但是如果不携带浏览器就知道,A和B是不认识的,不能放行,这时候就报数据拦截,并通知A,你们不认识,数据已经被我拦截,如果想要数据,可以让A告诉我。
当我们使用了不满足同源策略的地址时,请求的地址会正常返回数据,status是200(说明正常返回了数据),但是会提示跨域。
vite是如何实现代理解决跨域的呢?
原理是:vite本地新建了一个服务器,这个服务器跟我们的客户端是同源的,通过这个服务器去B请求数据,并成功拿到数据(因为服务端是没有同源策略的,只有浏览器有同源策略)。再返回给客户端。这样就拿到了数据,骗过了浏览器。
开发时态:
我们一般就利用 构建工具或者脚手架或者第三方的proxy代理配置,或者我们自己搭一个开发服务器来解决这个问题。
生产时态:
我们一般都是交给后端或者运维去处理跨域的【后端和运维的工作】
下面了解一下后端如何解决跨域:
- ngnix:代理服务,本质 原理就和我们的本地开发vite服务器跨域一样。
- 配置身份标识: Access-Control-Allow-Origin 配置这个参数,代表了那些域名是我的朋友,标记了以后,浏览器就不会拦截了。
什么时候生产的包需要配置跨域
我们一般打包之后,会有一个域名 :https:xxx.com/ 一般都会把后端包,和前端包都放在这个域名下面。所以不会发生跨域。
但是也有特殊情况:
比如我们跨部门的时候:
假如我们有很多子公司,各个子公司下面有一个域名,然后我们去访问总公司的域名的时候,这个肯定是要跨域的。
如何解决这种情况呢,就是配置上面说的身份标识。
28 echarts 如果加载很多的图表如何优化?
29 如何实现token过期的用户无感登录?
核心思路:
1、token过期机制:
-
使用 Access Token 和 Refresh Token 双 Token 机制。
-
Access Token:用于接口鉴权,有效期较短(如 2 小时)。
-
Refresh Token:用于刷新 Access Token,有效期较长(如 7 天)。
2、无感刷新流程:
-
当 Access Token 过期时,前端自动使用 Refresh Token 请求新的 Access Token。
-
如果 Refresh Token 也过期,则跳转到登录页。
实现步骤:
1、双token机制:用户登录成功后,后端返回两个 Token:
{
"access_token": "xxx",
"refresh_token": "yyy",
"expires_in": 7200 // Access Token 有效期(秒)
}
前端将 access_token 和 refresh_token 分别存储到内存或 localStorage 中。
2、Token过期检测:
在每次请求时,检查 Access Token 是否过期:
function isTokenExpired(token) {
const decoded = jwtDecode(token); // 使用库解码 JWT
return decoded.exp * 1000 < Date.now(); // 检查是否过期
}
3、自动刷新token:
在请求拦截器中,检测 Access Token 是否过期。如果过期,则使用 Refresh Token 请求新的 Access Token:
let isRefreshing = false; // 是否正在刷新 Token
let refreshSubscribers = []; // 存储等待刷新的请求
// 请求拦截器
axios.interceptors.request.use(async (config) => {
const accessToken = localStorage.getItem('access_token');
if (isTokenExpired(accessToken)) {
if (!isRefreshing) {
isRefreshing = true;
try {
const newToken = await refreshAccessToken();
localStorage.setItem('access_token', newToken);
refreshSubscribers.forEach(cb => cb(newToken));
refreshSubscribers = [];
} catch (error) {
logout(); // 刷新失败,跳转到登录页
} finally {
isRefreshing = false;
}
}
// 将当前请求加入队列,等待刷新完成后重试
return new Promise((resolve) => {
refreshSubscribers.push((newToken) => {
config.headers['Authorization'] = `Bearer ${newToken}`;
resolve(config);
});
});
}
config.headers['Authorization'] = `Bearer ${accessToken}`;
return config;
});
// 刷新 Token 的函数
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
const response = await axios.post('/api/refresh-token', { refresh_token: refreshToken });
return response.data.access_token;
}
4、处理并发请求:
- 如果多个请求同时检测到 Token 过期,只发起一次刷新请求,其他请求等待刷新完成后再重试。
- 通过 refreshSubscribers 队列实现。
5、Refesh Token 过期处理:
如果 Refresh Token 也过期,则跳转到登录页:
function logout() {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
window.location.href = '/login';
}
30 http请求如何发送?
1、用户输入url:
用户在浏览器地址栏输入 URL(如 https://www.example.com)。
2、DNS解析:
浏览器将域名(如 www.example.com)解析为对应的 IP 地址。
解析过程:
浏览器缓存 → 2. 操作系统缓存 → 3. 本地 DNS 服务器 → 4. 递归查询。
结果:
获取目标服务器的 IP 地址(如 93.184.216.34)。
3、建立TCP链接:
浏览器通过 IP 地址和端口号(默认 80 或 443)与服务器建立 TCP 连接。
如果是 HTTPS,还需要进行 TLS/SSL 握手,加密通信。
三次握手:
-
客户端发送 SYN 包(同步请求)。
-
服务器回复 SYN-ACK 包(同步确认)。
-
客户端发送 ACK 包(确认)。
TLS/SSL握手(HTTPS):
-
客户端发送 ClientHello。
-
服务器回复 ServerHello 和证书。
-
客户端验证证书并生成会话密钥。
-
双方使用会话密钥加密通信。
4、发送HTTP请求:
请求报文结构:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
5、服务器请求处理:
处理逻辑:
-
根据 URL 路径定位资源。
-
执行后端逻辑(如查询数据库)。
-
生成响应数据。
6、返回HTTP响应:
响应报文结构:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
<html>...</html>
常见状态码:
-
200:请求成功。
-
301:永久重定向。这个应该是走浏览器缓存
-
404:资源未找到。
-
500:服务器内部错误。
7、浏览器渲染页面:
渲染过程:
-
解析 HTML,构建 DOM 树。
-
解析 CSS,构建 CSSOM 树。
-
合并 DOM 和 CSSOM,生成渲染树。
-
布局(计算元素位置和大小)。
-
绘制(将内容显示到屏幕上)。
8、关闭TCP连接:
四次挥手:
-
客户端发送 FIN 包(终止连接)。
-
服务器回复 ACK 包(确认)。
-
服务器发送 FIN 包(终止连接)。
-
客户端回复 ACK 包(确认)。
31 两个数组如何 合并去重?
const array1 = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
const array2 = [{ id: 2, name: 'Bob' }, { id: 3, name: 'Charlie' }];
// 合并数组
const combinedArray = array1.concat(array2);
// 去重
const uniqueArray = Array.from(new Set(combinedArray.map(JSON.stringify))).map(JSON.parse);
console.log(uniqueArray);
vue3 中的watch 和 watchEffect 的区别?
使用方式:
watch:
语法:
watch(source, callback, options);
-
source:要监听的数据源(可以是 ref、reactive、computed 或一个返回值的函数)。
-
callback:数据变化时的回调函数,接收新值和旧值。
-
options:配置对象(如 immediate、deep)。
示例:
import { ref, watch } from 'vue';
const count = ref(0);
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`);
});
count.value++; // 输出:count changed from 0 to 1
watchEffect:
语法:
watchEffect(callback);
callback:立即执行的函数,自动追踪函数内的响应式依赖。
示例:
import { ref, watchEffect } from 'vue';
const count = ref(0);
watchEffect(() => {
console.log(`count is: ${count.value}`);
});
count.value++; // 输出:count is: 1
使用场景:
watch:
-
需要监听特定数据源的变化。
-
需要获取变化前后的值(新值和旧值)。
-
需要控制监听时机(如延迟监听或立即监听)。
-
需要深度监听对象或数组。
watchEffect:
-
需要监听多个响应式数据的变化。
-
不需要显式指定数据源,自动追踪依赖。
-
适合处理副作用(如发送请求、操作 DOM)
32、vue3 setup语法糖是在哪个生命周期?
setup 函数在 beforeCreate 和 created 之间执行,是 Composition API 的核心。
33、vue2中attrs 和 listeners的使用?
参考文章如下:点击查看
34、vue2中的data为啥是一个函数?
造成的原因是:在我们实际的开发中,可能会有多个地方实例化同一个组件,这个时候我们使用对象的话,就会实例化多次这个组件,但是每个实例化的对象都指向了同一个地址,所以修改了对象的其中一个属性,其他的实例化对象的属性都会发发生变化。
但是函数的话,就会返回一个新的地址,实例化多少次都不会受影响。
35、响应式开发实现?
36、iframe的使用?
37、uniapp如何调试?
38、BFC?
39、uniapp 如何实现热更新?
40、uniapp 如何实现语法降级?
这个里面有对应的选项,打包的时候勾选一下es6 转 es就行了。
还有压缩代码的选项等等。
41、ref 和 reactive 的使用场景?
使用ref的场景:
1、基本数据类型:
const count = ref(0); // number
const message = ref("Hello"); // string
2、需要频繁解构或传递的响应式数据:
const user = ref({ name: "Alice", age: 25 });
// 解构后仍保持响应性
const { name, age } = user.value;
3、需要强制类型推断的场景:
const isLoading = ref<boolean>(false); // 明确类型声明
4、组合式函数(composables) 的返回值:
// 返回 ref 更易与其他逻辑组合
function useCounter() {
const count = ref(0);
return { count };
}
优先使用reactive的场景
1、复杂对象或者嵌套:
const formState = reactive({
user: { name: "Bob", role: "admin" },
settings: { darkMode: true }
});
2、需要直接操作对象的多个属性
const state = reactive({ x: 0, y: 0 });
// 直接修改属性,无需 .value
state.x = 10;
state.y = 20;
3、需要与 Options API 风格兼容的代码
// 类似 Vue2 的 data 对象
const data = reactive({ list: [], loading: false });
注意事项:
ref在陷阱:
- 避免滥用 .value:在模板或 watch 中无需使用 .value,但在 JS 逻辑中必须使用。
<!-- 模板中自动解包 -->
<div>{{ count }}</div>
// JS 中需要 .value
count.value++;
reactive的陷阱:
- 直接解构对象属性会失去响应性:
const { x, y } = reactive({ x: 0, y: 0 }); // ❌ 非响应式
- 需用 toRefs 保持响应性:
const state = reactive({ x: 0, y: 0 });
const { x, y } = toRefs(state); // ✅ 响应式
实际开发建议:
默认使用 ref
-
大多数场景下,ref 更灵活且避免了解构问题,尤其是基本类型和组合式逻辑。
-
复杂对象使用 reactive
当需要统一管理多个关联属性时(如表单状态),reactive 更简洁。 -
结合 toRef/toRefs
将 reactive 对象属性转换为 ref,方便传递和解构:
const state = reactive({ a: 1, b: 2 });
const aRef = toRef(state, "a"); // 单个属性转换
const { a, b } = toRefs(state); // 全部属性转换