百度前端面试准备
以下是对该前端面试考点总结的超详细解释,从技术原理、使用场景、代码示例等维度展开:
一、JavaScript 核心考点
1. 数据类型与判断
- JS 类型体系:JavaScript 有 7 种基本数据类型(
string
、number
、boolean
、undefined
、null
、symbol
、bigint
)和 1 种引用数据类型(object
,包含数组、函数、对象等)。 typeof
相关- 作用:用于检测变量的类型,返回一个表示类型的字符串。
- 特殊情况:
typeof null
返回'object'
,这是 JavaScript 早期设计的历史遗留问题,因为null
被认为是一个空的对象指针。例如:console.log(typeof 123); // 'number' console.log(typeof 'abc'); // 'string' console.log(typeof null); // 'object'
- 判断数组的方式
Array.isArray()
:专门用于判断一个值是否为数组,是最直接可靠的方法。示例:console.log(Array.isArray([1, 2, 3])); // true console.log(Array.isArray({})); // false
instanceof
:通过判断对象的原型链中是否存在构造函数的prototype
属性来确定类型。但它存在跨窗口/框架的问题,因为不同窗口的Array
构造函数是不同的。示例:console.log([1, 2, 3] instanceof Array); // true console.log({} instanceof Array); // false
Object.prototype.toString.call()
:调用对象的toString
方法,返回一个[object 类型]
格式的字符串,可精准判断类型。示例:console.log(Object.prototype.toString.call([1, 2, 3])); // '[object Array]' console.log(Object.prototype.toString.call({})); // '[object Object]'
2. 进阶概念
- 闭包
- 定义:闭包是指有权访问另一个函数作用域中变量的函数,即使外部函数已经执行完毕,闭包仍能访问其作用域内的变量。通常是在一个函数内部创建另一个函数,内部函数引用外部函数的变量。
- 作用:可以实现变量的私有化(模块化)、延迟执行等。例如,创建一个计数器:
function createCounter() {let count = 0;return function() {return ++count;}; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2
- 内存泄漏:如果闭包中引用的变量是大型对象,且闭包长期存在(如被全局变量引用),会导致这些变量无法被垃圾回收机制回收,从而引发内存泄漏。
- 事件循环机制
- 宏任务(MacroTask):包括
setTimeout
、setInterval
、I/O
操作、UI 渲染
等。 - 微任务(MicroTask):包括
Promise.then/catch/finally
、MutationObserver
、process.nextTick
(Node.js 中)等。 - 执行顺序:执行栈中的同步代码执行完毕后,先清空所有微任务队列,然后再从宏任务队列中取出一个宏任务执行,之后又会清空微任务队列,如此循环。例如:
console.log('同步1'); setTimeout(() => {console.log('宏任务'); }, 0); Promise.resolve().then(() => {console.log('微任务'); }); console.log('同步2'); // 执行顺序:同步1 → 同步2 → 微任务 → 宏任务
- 宏任务(MacroTask):包括
3. 数据结构对比(Map 与对象)
- 键类型:对象的键只能是字符串或
Symbol
类型;而Map
的键可以是任意类型,包括基本类型(如数字、字符串、布尔值等)和引用类型(如对象、数组、函数等)。示例:const obj = {}; const key1 = {}; obj[key1] = 'value'; // 实际存储为 obj['[object Object]'] = 'value' console.log(obj[key1]); // 'value',但键是字符串化后的结果const map = new Map(); const key2 = {}; map.set(key2, 'value'); console.log(map.get(key2)); // 'value',键是原对象
- 键顺序:
Map
中的键是按照插入顺序进行排列的;对象在 ES6 之前,键的顺序是不确定的,ES6 之后,对象的键按照一定规则排序(数字优先升序,然后是字符串按插入顺序,最后是Symbol
按插入顺序)。 - 性能表现:在频繁进行增删操作的场景下,
Map
的性能优于对象。因为Map
是专门为键值对存储设计的,其内部实现针对增删查等操作做了优化;而对象的属性操作在某些情况下(如大量属性时)性能会稍差。
4. 算法实现(快速排序)
快速排序是一种分治算法,核心思想是选择一个基准元素,将数组分成比基准小和比基准大的两部分,然后对这两部分分别递归进行快速排序。
function quickSort(arr,low=0,high=arr.length-1){if(low>=high)return;const pivot=arr[high];let i=slow;for(let j=slow;j<high;j++){if(arr[j]<=pivot){[arr[i],arr[j]]=[arr[j],arr[i]];i++;}}[arr[i],arr[high]]=[arr[high],arr[i]];quickSort(arr,low,i-1);quick(arr,i+1,high);
}
二、CSS 核心考点
1. 性能相关(回流与重绘)
- 回流(重排)
- 定义:当元素的几何属性(如宽度、高度、位置等)发生变化时,浏览器需要重新计算元素的几何位置和大小,然后重新构建渲染树,这个过程称为回流。
- 触发场景:
- 页面初次渲染。
- 元素的尺寸、位置、内容变化(如修改
width
、height
、padding
、margin
、border
、textContent
等)。 - 浏览器窗口大小变化(
resize
事件)。 - 添加或删除可见的 DOM 元素。
- 优化方式:
- 避免频繁操作样式,可通过修改
class
来批量修改样式。 - 将 DOM 操作离线处理,如先将元素设置为
display: none
,操作完成后再显示。 - 使用
DocumentFragment
进行 DOM 批量操作。
- 避免频繁操作样式,可通过修改
- 重绘
- 定义:当元素的外观属性(如颜色、背景色等)发生变化,而几何属性不变时,浏览器只需要重新绘制元素的外观,这个过程称为重绘。
- 触发场景:修改元素的
color
、background
、border-color
等不影响几何结构的属性。 - 优化方式:同回流的优化思路,减少不必要的样式修改。
visibility: hidden
、display: none
、opacity: 0
)
2. 元素显示控制区别(display: none
- 元素会从文档流中消失,不再占据空间,也不会触发任何事件。
- 不会被子元素继承,子元素也会一同消失。
- 切换显示状态时,会触发回流和重绘,因为元素的几何结构发生了变化。示例:
.box {display: none; }
visibility: hidden
- 元素仍占据文档流中的空间,只是视觉上不可见,会触发事件(如点击事件等)。
- 会被子元素继承,子元素可以通过设置
visibility: visible
来显示。 - 切换显示状态时,只会触发重绘,因为元素的几何结构未变。示例:
.box {visibility: hidden; } .box .child {visibility: visible; }
opacity: 0
- 元素仍占据文档流中的空间,视觉上透明,会触发事件。
- 会被子元素继承,子元素无法通过设置
opacity
来显示(因为 opacity 是叠加的)。 - 切换透明度时,一般会触发重绘,某些浏览器可能会利用 GPU 加速,可能只触发合成操作(性能较好)。示例:
.box {opacity: 0; }
三、Vue 框架考点(含 Vue2/Vue3 差异)
1. 基础指令与属性
v-if
与v-show
的区别- 渲染机制:
v-if
是“真正”的条件渲染,会根据条件动态创建或销毁元素;v-show
是通过修改元素的display
属性来控制显示隐藏,元素始终会被渲染。 - 适用场景:
v-if
适合条件不常变化的场景,因为频繁创建销毁元素有性能开销;v-show
适合条件频繁切换的场景,因为只是修改样式,性能更好。示例:<template><div v-if="isShow">v-if 控制</div><div v-show="isShow">v-show 控制</div> </template> <script> export default {data() {return {isShow: true};} }; </script>
- 渲染机制:
- Vue 中
key
的作用- 在 Vue 的虚拟 DOM diff 算法中,
key
用于标识节点的唯一性。当节点的key
不变时,Vue 会认为是同一个节点,从而尽可能复用节点,提升 diff 效率;如果没有key
或key
不正确,可能会导致组件复用错误(如状态残留等问题)。示例:<template><div v-for="item in list" :key="item.id">{{ item.name }}</div> </template>
- 在 Vue 的虚拟 DOM diff 算法中,
2. 响应式原理
- Vue2 响应式(基于
Object.defineProperty
)- 原理:通过递归遍历对象的所有属性,使用
Object.defineProperty
为每个属性添加 getter 和 setter,当属性被访问或修改时,触发 getter 或 setter,进而通知视图更新。 - 缺陷:
无法监听数组的索引和长度变化
(如arr[0] = 1
、arr.length = 0
等操作无法触发响应式),Vue2 是通过重写数组
的变异方法(如push
、pop
等)来实现部分数组操作的响应式。无法监听对象的新增或删除属性
,需要使用Vue.set
或this.$set
来添加响应式属性,使用Vue.delete
或this.$delete
来删除属性。
- 原理:通过递归遍历对象的所有属性,使用
- Vue3 响应式(基于
Proxy
)- 原理:使用
Proxy
代理整个对象,拦截对象的各种操作(如属性的读取、设置、删除等),从而实现对对象所有变化的监听。同时,使用Reflect
来操作目标对象,保持行为的一致性。 - 优势:
可以监听数组的索引、长度变化以及对象的新增、删除属性
,无需额外的 API(如Vue.set
)。- 支持
深层监听
的优化,只有在访问到嵌套对象时才会递归创建代理
,提升了初始化性能。示例(Vue3 中):import { reactive } from 'vue'; const state = reactive({arr: [1, 2, 3],obj: { name: 'vue' } }); state.arr[0] = 10; // 会触发响应式 state.obj.age = 18; // 会触发响应式
- 原理:使用
3. 状态管理
ref
与reactive
的区别- 适用类型:
ref
主要用于包装基本类型的值(如number
、string
、boolean
等),使其成为响应式;也可用于包装引用类型,但内部还是会用reactive
来处理。reactive
用于将对象(引用类型)转换为响应式对象。 - 访问方式:
ref
包装后的值需要通过.value
来访问和修改;reactive
包装的对象可以直接访问和修改属性。示例:import { ref, reactive } from 'vue'; const count = ref(0); console.log(count.value); // 0 count.value++;const user = reactive({ name: 'vue' }); console.log(user.name); // 'vue' user.name = 'vue3';
- 适用类型:
- Pinia 相比 Vuex 的优点
- 简化 API:Pinia 没有
mutations
,只有state
、getters
和actions
,actions
既可以处理同步操作也可以处理异步操作,使用更简洁。 - 支持 TypeScript:Pinia 对 TypeScript 有更好的类型推断支持,无需额外的类型声明配置,开发体验更优。
- 无需模块化嵌套:Vuex 中如果有多个模块,需要进行
模块的注册和命名空间
的管理,较为繁琐;Pinia 中每个store 都是独立的
,不需要嵌套模块,结构更清晰。 - 更轻量。
- 简化 API:Pinia 没有
4. 组件通信
- Props/emit:父组件通过
props
向子组件传递数据,子组件通过$emit
触发自定义事件向父组件传递数据。示例:
<template><Child :msg="message" @custom-event="handleEvent" />
</template><script setup>
import { ref } from 'vue';
import Child from './Child.vue';// 响应式数据
const message = ref('父组件消息');// 事件处理函数
const handleEvent = (data) => {console.log('子组件传递的数据:', data);
};
</script>
<template><div @click="sendData">{{ msg }}</div>
</template><script setup>
import { defineProps, defineEmits } from 'vue';// 定义props
const props = defineProps({msg: {type: String,default: ''}
});// 定义emit事件
const emit = defineEmits(['custom-event']);// 发送数据方法
const sendData = () => {emit('custom-event', '子组件数据');
};
</script>
- Vuex/Pinia:通过全局状态管理库,实现任意组件间的通信。组件可以直接读取全局状态,也可以通过提交 mutations(Vuex)或调用 actions(Vuex/Pinia)来修改全局状态。
- EventBus:创建一个全局的事件总线(通常是一个 Vue 实例),组件通过
$on
监听事件,通过$emit
触发事件来实现通信。但在大型项目中,EventBus 可能会导致事件管理混乱,逐渐被 Vuex/Pinia 取代。示例:// eventBus.js import Vue from 'vue'; export default new Vue();// 组件 A import eventBus from './eventBus.js'; eventBus.$emit('event-name', data);// 组件 B import eventBus from './eventBus.js'; eventBus.$on('event-name', (data) => {console.log(data); });
- provide/inject:用于祖先组件向后代组件传递数据,无论组件层级多深。
provide
提供数据,inject
注入数据。示例:
<!-- 祖先组件 Ancestor.vue -->
<template><div><!-- 后代组件(无论层级多深) --><Descendant /></div>
</template><script setup>
import { provide } from 'vue';
import Descendant from './Descendant.vue';// 提供数据:第一个参数是注入的 key,第二个参数是要传递的值
provide('message', '祖先组件通过 provide 传递的数据');// 也可以提供响应式数据(使用 ref 或 reactive)
import { ref } from 'vue';
const count = ref(0);
provide('count', count); // 传递响应式数据
</script>
<!-- 后代组件 Descendant.vue(可以是子组件、孙组件等任意层级) -->
<template><div><p>注入的普通数据:{{ message }}</p><p>注入的响应式数据:{{ count }}</p><button @click="count++">修改响应式数据</button></div>
</template><script setup>
import { inject } from 'vue';// 注入数据:参数为祖先组件提供的 key
// 第二个参数可选,作为默认值(当找不到对应 key 时使用)
const message = inject('message', '默认值(如果未提供数据)');// 注入响应式数据(注入后仍保持响应式)
const count = inject('count');
</script>
好的,根据您提供的图片内容,这是一份非常经典和实用的前端面试提纲。下面我将针对这几个核心考点,为您提供一份详细的解答和知识梳理,希望能帮助您准备面试。
前端核心面试题解答
1. 布局常用方式:Flex 和 Grid 布局
这是考察 CSS 现代布局能力的核心。
Flex 布局(弹性盒子布局)
- 设计初衷:为一维布局提供更有效的方式,即处理行或列中的一个维度。
- 核心概念:容器(
display: flex;
)和项目(flex items)。通过主轴和交叉轴来控制对齐。 - 常用容器属性:
justify-content
:定义项目在主轴上的对齐方式(如flex-start
,center
,space-between
)。align-items
:定义项目在交叉轴上的对齐方式(如stretch
,center
,flex-start
)。flex-direction
:决定主轴方向(row
,column
,row-reverse
)。flex-wrap
:定义项目是否换行(nowrap
,wrap
)。
- 适用场景:导航栏、垂直居中、等高分栏、流动式内容排列。
Grid 布局(网格布局)
- 设计初衷:为二维布局设计,可以同时处理行和列。
- 核心概念:容器(
display: grid;
)通过定义网格线和轨道来创建单元格。 - 常用容器属性:
grid-template-columns/rows
:定义列和行的尺寸和数量(如1fr 200px 1fr
)。gap
:设置网格间隙(row-gap
,column-gap
)。grid-template-areas
:通过给区域命名来进行布局,非常直观。justify-items
/align-items
:项目在网格单元格内的对齐方式。
- 适用场景:复杂的整体页面布局(如 Holy Grail Layout)、杂志式卡片布局。
面试官可能追问:
- Flex 和 Grid 最主要的区别是什么?(一维 vs 二维)
- 用什么属性可以实现一个元素的完美垂直居中?
- Flex: 父容器
display: flex; justify-content: center; align-items: center;
- Grid: 父容器
display: grid; place-items: center;
(或使用justify/align-items
)
- Flex: 父容器
- 解释一下
fr
单位。
2. 防抖和节流
这是性能优化的重要知识点,用于控制函数执行频率。
防抖
- 概念:在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。
- 比喻:电梯门,如果不断有人进,电梯门会一直保持打开,直到没人进了才会关闭。
- 适用场景:
搜索框输入联想、窗口 resize 事件
。 - 实现(修复常见瑕疵):
常见瑕疵是未处理this
指向和事件对象event
。
function debounce(func, wait) {let timeoutId;// 返回一个闭包函数return function (...args) {// 保存本次调用的上下文(this)和参数(args)const context = this;// 如果已经有定时器,就清除它,实现“重新计时”clearTimeout(timeoutId);// 设置新的定时器timeoutId = setTimeout(() => {// 使用 apply 确保 func 中的 this 指向正确,并传入参数func.apply(context, args);}, wait);};
}// 使用示例
const myInput = document.getElementById('search');
myInput.addEventListener('input', debounce(function(e) {console.log('发送搜索请求:', e.target.value);
}, 500));
节流
- 概念:规定在一个单位时间内,只能触发一次函数执行。如果这个单位时间内触发多次,只有一次生效。
- 比喻:地铁,每 5 分钟一班,不管你来了多少人,都按固定频率发车。
- 适用场景:
滚动加载、按钮频繁点击、鼠标移动事件
。 - 实现(时间戳+定时器版,更准确):
function throttle(fn, limit) {let lastRan = 0;let timeout = null;return function(...args) {const now = Date.now();if (now - lastRan >= limit) {fn.apply(this, args);lastRan = now;}//不配合会导致最后一次出发不执行 scroll、resize不友好else {// 结合时间戳 + setTimeout,既立即执行又确保尾部调用。if (timeout) clearTimeout(timeout);timeout = setTimeout(() => {fn.apply(this, args);lastRan = Date.now();}, limit - (now - lastRan));}};
}
面试官可能追问:
- 防抖和节流的区别?分别举一个实际应用例子。
- 为什么要在防抖函数里保存
this
和args
?
3. 事件循环机制
考察对 JavaScript 单线程、异步编程底层原理的理解。通常会给出一段包含 setTimeout
、Promise
的代码让分析输出顺序。
核心规则:
- 同步代码优先执行。
- 微任务 优先于 宏任务。
- 常见宏任务:
setTimeout
,setInterval
,setImmediate
(Node.js), I/O 操作, UI rendering。 - 常见微任务:
Promise.then
,Promise.catch
,Promise.finally
,queueMicrotask
,MutationObserver
。
例题分析:
console.log('1');setTimeout(function() {console.log('2');new Promise(function(resolve) {console.log('3');resolve();}).then(function() {console.log('4');});
}, 0);new Promise(function(resolve) {console.log('5');resolve();
}).then(function() {console.log('6');
});console.log('7');
输出顺序:1 -> 5 -> 7 -> 6 -> 2 -> 3 -> 4
执行步骤分析:
- 执行同步代码:输出
1
,5
,7
。new Promise
内部的 executor 函数是同步执行的,所以输出5
。
- 同步代码执行完毕,检查微任务队列。此时微任务队列中有
Promise.then
的回调(输出6
的那个)。执行它,输出6
。 - 微任务队列清空,开始执行下一个宏任务(即
setTimeout
的回调)。 - 执行
setTimeout
回调:- 输出
2
。 - 遇到
new Promise
,同步执行 executor,输出3
。 - 将
Promise.then
的回调(输出4
的那个)放入微任务队列。
- 输出
- 当前宏任务执行完毕,检查微任务队列。发现有微任务(输出
4
的那个),执行它,输出4
。
面试官可能追问:
async/await
的本质是什么?(Generator 的语法糖,其后的代码相当于放在Promise.then
中,是微任务)。process.nextTick
(Node.js) 和微任务的优先级?
4. Webpack 和 Vite 常用配置
考察对前端工程化工具的熟悉程度。
Webpack
- 核心概念:入口(Entry)、输出(Output)、加载器(Loaders)、插件(Plugins)、模式(Mode)。
- 常用配置:
entry
:定义打包入口文件。output
:定义打包后的文件输出路径和文件名(如path
,filename
,chunkFilename
,clean
)。module.rules
:配置 Loader,处理非 JS 文件(CSS,图片,转译 JS)。module: {rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader'] },{ test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' },{ test: /\.(png|jpg|gif)$/, type: 'asset/resource' }] }
plugins
:用于执行更广泛的任务,如打包优化、资源管理、环境变量注入。HtmlWebpackPlugin
:自动生成 HTML 文件并注入 JS 包。MiniCssExtractPlugin
:将 CSS 提取到单独的文件。DefinePlugin
:定义全局常量。
optimization.splitChunks
:代码分割配置。devServer
:配置开发服务器(端口、代理等)。
Vite
- 核心优势:基于原生 ES Module,启动速度极快。利用 Esbuild 进行预构建和依赖预打包。
- 与 Webpack 主要区别:
- 开发环境:Vite 无需打包,服务器随起随用。Webpack 需要先打包构建。
- 生产环境:Vite 使用 Rollup 进行打包,配置更简洁。
- 常用配置 (
vite.config.js
):plugins
:集成 Rollup 插件生态,如@vitejs/plugin-vue
、@vitejs/plugin-react
。resolve.alias
:配置路径别名。server
:开发服务器配置(端口、代理、打开浏览器)。server: {port: 3000,proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true}} }
build
:生产构建配置(输出目录、资源路径、打包优化)。build: {outDir: 'dist',rollupOptions: {output: {chunkFileNames: 'js/[name]-[hash].js',assetFileNames: '[ext]/[name]-[hash].[ext]'}} }
面试官可能追问:
- Vite 为什么比 Webpack 快?(开发环境利用浏览器原生 ESM,无需打包)。
- 谈一下你对 Tree-shaking 的理解。
- 如何配置 Webpack/Vite 来解决跨域问题?(使用
devServer.proxy
/server.proxy
)。