建了一个前端面经的分享群,分享自己的面试经历,大家可+v:first9839
面试题目
1、ref和reactive的底层区别
2、vue中怎么透传
3、 props校验的几种方式
4、 vue中页面初始化,给输入框focus
5、 reactive解构之后怎么还能保持响应式
6、 项目中怎么实现ant主题样式的修改
7、 vue中怎么用defineModel封装组件的v-model
7、 路由hash和history的区别
8、 vue和react页面渲染的区别
10、react性能优化
11、怎么在一个图片的部分区域添加点击事件,区域不规则
12、冒泡排序
13、封装组件的思路
14、语义化标签
15、自定义hooks有哪些
16、封装过哪些组件
17、给出12543,用给出的这几个数输出比这个数小的最大的值,它的时间复杂度和空间复杂度
面试详解
一、ref和reactive的底层区别
- ref:
- ref 用于创建单个值的响应式引用,适用于基本数据类型,如数字、字符串等。
- 通过 .value 属性访问和修改内部值,例如 const count = ref(0); count.value++。
- ref 对象在模板中使用时会自动解包,不需要使用 .value 来访问
- reactive:
- reactive 用于创建包含多个属性的响应式对象,适用于对象和数组。
- 直接访问和修改属性,无需通过 .value 属性,例如 const state = reactive({name: 'John'}); state.name = 'Jane'。
- reactive 创建的对象是深度响应式的,可以自动追踪内部属性的变化
ref 的内部实现实际上是对 reactive 的封装。当使用 ref 创建响应式引用时,Vue 内部会创建一个包含指定值的对象,并将其标记为响应式对象。这个对象有一个特殊的 value 属性,用于存储实际的值。而 reactive 函数则直接将对象转换为响应式,允许直接访问属性,无需通过 .value 属性
二、vue中怎么透传
透传 attribute 指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute(属性) 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上
如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并
// 父组件中这样透传
<MyButton class="large" />
<!-- <MyButton> 的模板 -->
<button class="btn">Click Me</button>
// 则最后渲染出的 DOM 结果会变成
<button class="btn large">Click Me</button>
-
这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。
-
这个 $attrs 对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 class,style,v-on 监听器等等。
-
attrs 不提供验证或默认值
-
attrs 不是响应性的,不能像props一样跟踪它的变化。
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
三、props校验的几种方式
- 类型校验:使用构造器函数如 String, Number, Boolean, Array, Object 等来限制 props 的类型
props: {
myString: String,
myNumber: Number,
myBoolean: Boolean,
myArray: Array,
myObject: Object
}
// vue3 setup 使用 defineProps
defineProps<{
title?: string
likes?: number
}>()
// defineProps配合ts的接口
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const { foo, bar = 100 } = defineProps<Props>()
</script>
- 自定义类型校验:使用 function 来定义更复杂的类型校验
props: {
myProp: function (val) {
return typeof val === 'string' || val instanceof String;
}
}
- 高级校验validator 方法 Props 支持使用一个 validator 函数。这个函数接受 prop 原始值,并且必须返回一个布尔值来确定这个 prop 是否有效。
prop: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
}
四、vue中页面初始化,给输入框focus
在组件的mounted生命周期钩子中,使用nextTick确保DOM已经渲染完成,然后通过ref获取输入框元素并调用其focus方法。
<template>
<input ref="inputField" type="text" />
</template>
<script>
import { ref, onMounted, nextTick } from 'vue';
export default {
mounted() {
nextTick(() => {
this.$refs.inputField.focus();
});
}
};
</script>
五、reactive解构之后怎么还能保持响应式
- 使用toRefs: Vue提供了toRefs函数,它可以将reactive对象的每个属性转换为一个ref,这样即使解构后,每个属性也能保持响应式。
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
name: 'Vue'
});
const { count, name } = toRefs(state);
// 现在 count 和 name 都是响应式的 ref
count.value++; // 触发视图更新
name.value = 'Vue 3'; // 触发视图更新
六、项目中怎么实现ant主题样式的修改
-
使用less变量覆盖:在项目的配置文件中,可以使用less-loader的modifyVars选项来覆盖Ant Design Vue的样式变量
-
使用CSS-in-JS动态主题:通过ConfigProvider组件的theme属性,可以实时修改主题
<template>
<a-config-provider
:theme="{
token: {
colorPrimary: '#00b96b',
},
}"
>
<a-button />
</a-config-provider>
</template>
七、vue中怎么用defineModel封装组件的v-model
使用 defineModel 时,它返回一个对象,该对象包含两个属性:
- value:当前绑定的值,这是一个响应式属性。
- update:一个方法,当调用时会触发一个事件,该事件通知父组件更新其状态。
<template>
<input type="text" :value="model.value" @input="model.update($event.target.value)">
</template>
<script>
import { defineComponent, defineModel } from 'vue';
export default defineComponent({
props: ['value'],
emits: ['update:modelValue'],
setup(props, { emit }) {
const model = defineModel(props, emit, 'value', 'update:modelValue');
return { model };
}
});
</script>
八、路由hash和history的区别
- URL格式:
- hash模式的URL是这样:example.com/#/anchor。这里…
- history模式的URL是这样:example.com/path。这种模式下,…
- 浏览器历史记录:
- 在hash模式下,URL的hash部分不会包含在浏览器的历史记录中。这意味着,使用浏览器的后退按钮可能不会按照预期工作,因为它不会影响hash部分。
- history模式利用了HTML5的History API,允许你更改浏览器的历史记录,包括pushState和replaceState方法。这样,使用浏览器的后退和前进按钮时,应用的行为更符合用户预期。
- SEO(搜索引擎优化)
- hash模式由于不改变实际的页面内容,对SEO不友好,因为搜索引擎爬虫通常不解析hash部分。
- history模式的URL对SEO更友好,因为它看起来像传统的URL路径,有助于搜索引擎更好地索引内容。
九、vue和react页面渲染的区别
-
Vue 双向绑定,修改数据自动更新视图,而 React 单向数据流,需要手动 setState
-
都可以通过 props 进行父子组件数据传递,只是 Vue props 要声明,React 不用声明可能直接使用,Vue 可以用插槽,React 是万物皆可 props
-
响应式
-
Vue2 响应式的特点就是依赖收集,数据可变,自动派发更新,初始化时通过
Object.defineProperty递归劫持 data 所有属性添加 getter/setter,触发 getter 的时候进行依赖收集,修改时触发 setter 自动派发更新找到引用组件重新渲染 -
Vue3 响应式使用原生 Proxy 重构了响应式,一是 proxy 不存在响应式存在的缺陷,二是性能更好,不仅支持更多的数据结构,而且不再一开始递归劫持对象属性,而是代理第一层对象本身。运行时才递归,用到才代理,用 effect 副作用来代替 Vue2 里的 watcher,用一个依赖管理中心 trackMap 来统一管理依赖代替 Vue2 中的 Dep,这样也不需要维护特别多的依赖关系,性能上取得很大进步
-
相比 Vue 的自动化,React 则是基于状态,单向数据流,数据不可变,需要手动 setState 来更新,而且当数据改变时会以组件根为目录,默认全部重新渲染整个组件树,只能额外用 pureComponent/shouldComponentUpdate/useMemo/useCallback 等方法来进行控制,更新粒度更大一些
- Diff 算法:
-
Vue2 是同层比较新老 vnode,新的不存在老的存在就删除,新的存在老的不存在就创建,子节点采用双指针头对尾两端对比的方式,全量diff,然后移动节点时通过 splice 进行数组操作
-
Vue3 是采用 Map 数据结构以及动静结合的方式,在编译阶段提前标记静态节点,Diff 过程中直接跳过有静态标记的节点,并且子节点对比会使用一个 source 数组来记录节点位置及最长递增子序列算法优化了对比流程,快速 Diff,需要处理的边际条件会更少
-
React 是递归同层比较,标识差异点保存到 Diff 队列保存,得到 patch 树,再统一操作批量更新 DOM。Diff 总共就是移动、删除、增加三个操作,如果结构发生改变就直接卸载重新创建,如果没有则将节点在新集合中的位置和老集合中的 lastIndex 进行比较是否需要移动,如果遍历过程中发现新集合没有,但老集合有就删除
十、react性能优化
-
使用React.memo():这是一个高阶组件,用于缓存函数组件的渲染结果,通过比较当前和之前的props来避免不必要的重新渲染。
-
使用useMemo和useCallback钩子:useMemo可以对计算结果进行记忆化,而useCallback则可以记忆化函数,确保它们不会在每次渲染时重新创建
-
代码分割:使用React.lazy和动态import()来实现代码分割,这有助于减少初始加载时间,从而提高用户体验
-
列表虚拟化:当处理长列表时,只渲染可视区域内的项目,可大幅提高渲染性能
-
延迟加载图片:使用延迟加载库,如react-lazyload,以提高页面加载速度
-
使用不可变数据结构:避免直接修改React状态,而应使用不可变数据结构来管理状态变化
-
使用React.Fragment避免额外的HTML元素包装:这有助于减少DOM节点的数量,从而提高性能
-
服务端渲染(SSR):SSR可以提高首屏加载速度并改善SEO,但需要注意服务器性能和渲染效率的平衡
十一、怎么在一个图片的部分区域添加点击事件,区域不规则
-
使用SVG图形:如果图片实际上是一个SVG文件,你可以分析SVG的结构,找到对应的区域,并为这些区域绑定点击事件。例如,SVG中的
<rect>或<polygon>元素可以用来定义不规则区域,然后通过JavaScript添加事件监听器 -
HTML图片映射:对于静态图像,可以使用
<map>标签和<area>标签来定义图片上的可点击区域。<area>标签的shape属性可以设置为矩形(rect)、圆形(circle)或多边形(poly),并通过coords属性指定区域的位置和大小。然后可以为每个区域添加href属性或onclick事件处理函数来定义点击行为 -
使用canvas绘制图片,再绘制到指定的区域添加点击事件
十二、冒泡排序
比较每相邻两个数,如果前者大于后者,就把两个数交换位置;这样一来,第一轮就可以选出一个最大的数放在最后面;那么经过 n-1(数组的 length - 1) 轮,就完成了所有数的排序。
- 外层 for 循环控制循环次数
- 内层 for 循环进行两数交换,找每次的最大数,排到最后
- 设置一个标志位,减少不必要的循环
function bubbleSort(arr) {
const len = arr.length;
let swapped;
for (let j = 0; j < len - 1; j++) {
swapped = false; // 添加标志变量
for (let i = 0; i < len - 1 - j; i++) {
if (arr[i] > arr[i + 1]) {
let temp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = temp;
swapped = true; // 发生了交换
}
}
if (!swapped) break; // 如果没有交换发生,则提前结束
}
return arr;
}
var arr = [3, 4, 1, 2];
console.log(bubbleSort(arr)); // 打印排序后的数组
冒泡排序算法在每次内层循环结束后,如果没有发生任何交换,就会提前结束排序过程,从而提高效率
- 时间复杂度: 平均时间复杂度O(n*n) 、最好情况O(n)、最差情况O(n * n)
- 空间复杂度: O(1)
十三、封装组件的思路
-
确定组件功能:首先要明确组件的职责和功能。它应该完成什么任务?它需要哪些数据?它应该有哪些交互?
-
设计组件接口:确定组件的props(输入参数)和事件(如回调函数)。这包括数据类型、是否必填、默认值等。
-
编写样式:根据设计稿或需求,使用CSS或预处理器(如Sass)来编写组件的样式。考虑使用CSS模块或BEM方法来避免样式冲突。
-
实现组件逻辑:用JavaScript或框架特定的模板语法来添加组件的逻辑。这可能包括状态管理、事件处理、数据绑定等。
-
编写可复用的代码:考虑将通用的功能抽象成更小的子组件或工具函数,这样它们可以在不同的组件中复用。
-
测试组件:编写单元测试和/或集成测试来验证组件的行为。确保组件在各种条件下都能正常工作。
十四、语义化标签
在HTML文档中使用具有明确语义的标签来描述网页内容的结构和意义。这样做不仅有助于提高网页的可读性和可维护性,还能提升搜索引擎优化(SEO)的效果,同时对屏幕阅读器等辅助技术更为友好
十五、自定义hooks有哪些
-
useState:通过 useState,可以创建一个状态变量及其更新函数,并在组件内使用该变量来保存和更新组件的状态。
-
useEffect:用于在组件渲染完成后执行一些副作用操作(例如订阅数据、更新 DOM 等)。通过 useEffect,可以在组件加载、更新和卸载时设置和清理副作用操作,并且可以在副作用操作之间共享状态。
-
useContext:用于在组件之间共享一些全局的状态或函数,以避免通过多层嵌套的 Props 传递进行数据传输。通过 useContext,可以让组件在全局状态或函数的上下文中运行,并让它们能够方便地读取或更新全局状态或函数。
-
useReducer:通过 useReducer,可以创建一个状态容器及其更新函数,并在组件内使用该容器来保存和更新组件的状态。
-
useMemo:用于在组件渲染完成后缓存一些计算结果,以避免因为重复计算导致的性能问题。通过 useMemo,可以创建一个缓存变量,并在组件内使用该变量来保存计算结果并缓存。
-
useCallback:用于在组件渲染完成后,将一些函数进行缓存,以避免因函数重复创建导致的性能问题。通过 useCallback,可以创建一个缓存函数,并在组件内使用该函数来代替重复创建的函数。
-
useRef:用于在组件渲染完成后创建一个引用,以便在组件多次渲染时能够保留上一次渲染中的值。通过 useRef,可以创建一个引用变量,并在组件内使用该变量来保存一些持久化的数据。