面试题复盘

110 阅读6分钟

- 今天去面试,碰到了几个有意思的题目,回答没到面试官想得到的答案,所以回来复盘一下,总结一下。

1. watch如果监听的属性层级很深的情况下你会怎么办。

  • 这个其实是面试官考察我对watch的第三个参数层级了解,第一个参数为监听的对象,第二个参数为回调,第三个参数为options。

  • 答案为:第三个参数options的设置为deep:true就可以了

- 举个例子

import { watch, reactive } from 'vue'

const arr = reactive({
  brr: {
    crr: {
      err: {
        frr: '初始值'
      }
    }
  }
})

// 监听整个arr对象,并开启深度监听
watch(
  arr,
  (newVal, oldVal) => {
    console.log('深层属性变化了', newVal.brr.crr.err.frr)
  },
  { deep: true } // 关键配置:开启深度监听
)

// 修改深层属性时会触发watch
arr.brr.crr.err.frr = '修改后的值'

2. watch如何实现watchEffect的第一次加载就进行监听呢。

  • 先说答案,在 Vue3 中,watch 默认不会在初始加载时执行回调,而 watchEffect 会在初始化时立即执行一次(收集依赖)。如果想让 watch 实现类似 watchEffect 的初始执行效果,可以通过 options 参数中的 immediate: true 来实现,没想到第三个参数这么重要。
import { watch, ref } from 'vue'

const count = ref(0)

// 使用 immediate: true 让 watch 在初始时就执行一次
watch(
  count,
  (newVal, oldVal) => {
    console.log('count的值:', newVal)
  },
  { immediate: true } // 关键配置:初始时立即执行
)

都到这里了,顺便就科普下watch的第三个参数都有哪些选项吧,对了这些都是可选属性,可写可不写。

  • immediate:是否在初始时立即执行一次回调,默认为false
  • deep:是否深度监听对象内部属性的变化,默认为false
  • flush:控制回调函数的执行时机,可选值为'pre'(默认,DOM 更新前)、'post'(DOM 更新后)、'sync'(同步执行)

3.ref能做到的事为什么要reactive?

  • 这个很有意思,这个是面试官问,其实ref也能定义reactive的object对象,那么我为什么还要使用reactive?

  • 这个给我问懵了,我只能说习惯这么写了,面试官是这么回答我的。

  • 其实Vue3并没有抛弃掉Object.defineProperty,如果我们使用ref的话,其实底层还是Object.defineProperty 去进行数据劫持,那么使用ref定义对象的话,是可以的,不过为了避免出现无法响应式,所以会在底层使用reactive去再包一层,这样就会显得多此一举,对于对象 / 数组类型,当用 ref 包裹时,Vue3 会自动将其转换为 Proxy 代理对象(相当于 ref(reactive(obj)))。也就是说,ref 定义的对象底层最终还是通过 Proxy 实现响应式,而非 Object.defineProperty

4.跨窗口通信。

  • 这个其实我之前接触过,被问到过一次,就是同一个项目,然后我A窗口点击一下,让B窗口跳出个弹窗,我回答用BroadcastChannel,面试官对这个方法挺满意的,但是后续又问了句还有其他方法吗,我就回答不上来了。

  • 直接说答案

1. window.postMessage()(最常用)

postMessage 是 HTML5 提供的跨窗口通信 API,支持同源和跨域场景,是最灵活的方式。

语法

// 发送消息
targetWindow.postMessage(message, targetOrigin, [transfer])
  • targetWindow:接收消息的窗口对象(如 iframe.contentWindowwindow.open() 返回的窗口等

  • message:要发送的数据(可序列化的任意类型,如对象、字符串等)。

  • targetOrigin:指定接收窗口的域名(* 表示不限制,但不安全)。

接收消息

// 监听 message 事件
window.addEventListener('message', (event) => {
  // 验证发送方域名(重要!防止恶意消息)
  if (event.origin !== 'https://example.com') return;
  
  // event.data 为发送的消息
  console.log('收到消息:', event.data);
  
  // 可选:向发送方回复消息
  event.source.postMessage('已收到', event.origin);
});

2. 共享全局变量(仅限同源且有引用关系)

如果两个窗口存在直接引用关系(如父窗口与子窗口通过 window.open() 打开),可以通过共享全局变量通信。

示例(父窗口打开子窗口)

// 父窗口打开子窗口
const childWindow = window.open('https://example.com/child.html');

// 向子窗口发送数据(需等待子窗口加载完成)
childWindow.onload = () => {
  childWindow.sharedData = { name: 'Parent' }; // 子窗口可访问此变量
};
// 子窗口中
console.log(window.sharedData); // 访问父窗口设置的变量

// 向父窗口发送数据
window.opener.sharedData = { name: 'Child' }; // 父窗口通过 window.sharedData 访问

3. localStorage/sessionStorage(利用存储事件)

同源窗口可以通过监听 localStorage 的 storage 事件实现通信(sessionStorage 仅在同一窗口的标签页共享,不适合跨窗口)。

原理:当一个窗口修改 localStorage 时,其他同源窗口会触发 storage 事件。

偷个懒这玩意其实就是监听,知道就行,感觉用不到,但是知道这个概念以后真用的时候直接百度。

5.vue3的diff算法更新更新了什么

特性Vue2 做法Vue3 优化
静态节点处理每次 diff 都会比较提升到渲染函数外,只创建一次
动态节点识别全量比较节点所有属性通过 PatchFlag 精准定位动态部分
列表 diff 算法双端比较,可能产生较多 DOM 移动最长递增子序列,减少 DOM 操作
根节点限制必须有唯一根节点支持 Fragment 多根节点

6.使用element来实现文件上传的百分比。

  • 这个还是我不够细致,我回答的是根据接口的反应,比如调用接口0%,请求中padding为50%,然后请求完毕100%。

  • 真实操作应该是在 Element UI(或 Element Plus)中主要通过监听上传过程的progress事件来实现。这个事件会实时返回当前上传的进度信息,包括已上传字节数、总字节数等,据此可以计算出百分比。

<template>
  <el-upload
    class="upload-demo"
    action="/api/upload"  <!-- 后端上传接口 -->
    :on-progress="handleProgress"  <!-- 监听上传进度 -->
    :on-success="handleSuccess"
    :on-error="handleError"
    :file-list="fileList"
    :auto-upload="true"
  >
    <el-button type="primary">点击上传</el-button>
    
    <!-- 进度条显示 -->
    <template #file="scope">
      <div class="file-item">
        <span>{{ scope.file.name }}</span>
        <!-- 上传中显示进度条 -->
        <el-progress
          v-if="scope.file.status === 'uploading'"
          :percentage="scope.file.percentage"  <!-- 百分比数据 -->
          stroke-width="2"
          style="margin-top: 5px;"
        ></el-progress>
        <!-- 上传完成/失败状态 -->
        <span 
          v-else-if="scope.file.status === 'success'"
          class="success-status"
        >
          上传成功
        </span>
        <span 
          v-else-if="scope.file.status === 'error'"
          class="error-status"
        >
          上传失败
        </span>
      </div>
    </template>
  </el-upload>
</template>

<script setup>
import { ref } from 'vue';

const fileList = ref([]);

// 处理上传进度
const handleProgress = (event, file, fileList) => {
  // event.loaded: 已上传字节数
  // event.total: 总字节数
  // 计算百分比并赋值给 file 对象(Element 会自动更新视图)
  file.percentage = Math.round((event.loaded / event.total) * 100);
};

// 上传成功回调
const handleSuccess = (response, file, fileList) => {
  console.log('上传成功', response);
};

// 上传失败回调
const handleError = (error, file, fileList) => {
  console.error('上传失败', error);
};
</script>

总结

  • 说实话,这些都是比较基础的东西,没想到栽在这里了,有点不应该,所以回来赶紧复盘了,本身就是想当个笔记,若有错误还请评论区指出,感激不尽,若能帮到你,也祝你工作顺利、面试成功。