微信小程序开发岗前端面试题(含答案)
一、CSS核心知识点
1. 网页布局方式
答案: 常见布局方式包括:
- 流式布局(百分比布局):宽度用百分比,适配不同屏幕,小程序中基础适配方案;
- 浮动布局(float):通过float: left/right + 清除浮动实现多列布局,需注意BFC清除浮动;
- 定位布局(position):absolute/fixed/relative/sticky,适合局部精准定位;
- Flex弹性布局:小程序主流布局方式,一维布局,灵活适配多端;
- Grid网格布局:二维布局,适合复杂栅格场景(小程序中也可使用);
- 响应式布局:结合媒体查询@media,适配不同设备尺寸。
2. BFC什么是BFC?什么条件下触发BFC?
答案:
- BFC定义:块级格式化上下文(Block Formatting Context),是CSS的渲染规则,创建BFC的元素会形成独立渲染区域,内部元素布局不受外部影响,也不影响外部元素。
- 核心作用:清除浮动、解决margin重叠、避免元素被浮动元素覆盖。
- 触发条件(满足其一即可):
- 根元素(<html>);
- 浮动元素(float不为none);
- 定位元素(position为absolute/fixed);
- 行内块元素(display: inline-block);
- 弹性/网格容器(display: flex/grid);
- 溢出元素(overflow不为visible,如hidden/auto/scroll,小程序中常用此方式创建BFC)。
3. Flex布局:flex=1的含义及flex属性(flex是哪些属性的简写)
答案:
- flex属性:是flex-grow(放大比例)、flex-shrink(缩小比例)、flex-basis(基准尺寸)的简写,默认值0 1 auto。
- flex:1:等价于flex: 1 1 0%,含义:
- flex-grow: 1:剩余空间按比例分配给该元素(小程序中实现“自适应占满剩余宽度”核心);
- flex-shrink: 1:空间不足时该元素会等比例缩小;
- flex-basis: 0%:元素基准宽度为0,宽度完全由flex-grow决定。
- 补充:Flex布局是小程序默认推荐布局,父容器设display: flex后,子元素可通过flex属性实现灵活适配(如左侧固定、右侧自适应)。
4. 盒子模型:标准盒子模型和怪异盒子模型
答案:
- 核心区别:宽高的计算范围不同(通过box-sizing控制);
- 标准盒子模型(content-box):
- 默认值,宽高 = 内容(content)宽高,padding、border会额外增加元素总尺寸;
- 例:width: 100px; padding: 10px → 实际宽度=120px;
- 怪异盒子模型(border-box):
- 宽高 = content + padding + border,padding和border不会超出设定的宽高;
- 小程序中推荐使用(可全局设置box-sizing: border-box),方便精准控制元素尺寸。
5. 元素隐藏的方法(含CSS隐藏属性、v-if和v-show的区别)
答案:
(1)CSS隐藏元素的方法:
| display: none | 否 | 否 | 完全隐藏,不占布局空间 |
| visibility: hidden | 是 | 否 | 隐藏但保留位置,适合临时隐藏 |
| opacity: 0 | 是 | 是 | 视觉隐藏,元素仍可点击(如小程序遮罩层) |
| position: absolute; left: -9999px | 否(脱离文档流) | 否 | 隐藏且不影响布局 |
| height: 0; overflow: hidden | 否 | 否 | 隐藏内容,保留元素本身 |
(2)v-if vs v-show(Vue/小程序Vue框架):
- v-if:
- 原理:动态创建/销毁DOM元素;
- 特点:初始渲染开销大,切换开销大,隐藏时不占DOM结构;
- 适用:条件不频繁切换(如权限控制);
- v-show:
- 原理:通过display: none控制显示/隐藏,DOM始终存在;
- 特点:初始渲染开销小,切换开销小,隐藏时占空间;
- 适用:条件频繁切换(如小程序tab切换);
- 小程序原生:对应wx:if(同v-if)、hidden(同v-show)。
6. 盒子居中方式(重点:水平垂直居中)
答案:
(1)水平居中:
- 行内元素:父元素设text-align: center;
- 块级元素:margin: 0 auto(需设置宽度);
- Flex布局:父元素display: flex; justify-content: center。
(2)垂直居中:
- 行内元素:line-height = 父元素高度(单行文本);
- Flex布局:父元素display: flex; align-items: center;
- 定位+transform:.parent { position: relative; }
.child {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
(3)水平垂直居中(小程序高频):
- 方案1(Flex,推荐):.parent {
display: flex;
justify-content: center;
align-items: center;
} - 方案2(定位+transform):.parent { position: relative; }
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
} - 方案3(Grid):parent { display: grid; place-items: center; }。
7. 定位方式及对应的CSS属性
答案: CSS定位属性为position,共4种核心值:
- static:默认值,静态定位,遵循正常文档流,top/left等属性无效;
- relative:相对定位,相对于自身原位置偏移,仍占文档流空间(小程序中常用于作为绝对定位的父容器);
- absolute:绝对定位,相对于最近的已定位(非static)祖先元素偏移,脱离文档流;
- fixed:固定定位,相对于视口偏移,脱离文档流(小程序中注意适配手机状态栏);
- 补充:sticky:粘性定位,滚动到阈值时切换为fixed,小程序中部分场景可用。
8. CSS display属性详解
答案: display控制元素的显示类型,核心值:
- none:隐藏元素,不占空间(同v-show隐藏逻辑);
- block:块级元素,独占一行,可设置宽高(如div/p);
- inline:行内元素,不独占一行,不可设置宽高(如span/a);
- inline-block:行内块元素,不独占一行,可设置宽高(如小程序中button默认);
- flex:弹性容器,小程序主流布局方式;
- grid:网格容器,二维布局;
- inherit:继承父元素的display值。
9. CSS如何创建动画
答案: CSS实现动画有2种方式:
(1)transition过渡动画(简单动画):
- 触发条件:元素状态变化(如hover、点击);
- 语法:transition: 属性 时长 缓动函数 延迟;
- 示例(小程序按钮点击动效):.btn {
transition: background-color 0.3s ease;
}
.btn:active {
background-color: #ccc;
}
(2)@keyframes关键帧动画(复杂动画):
- 自定义关键帧,控制动画全过程;
- 示例(小程序加载动画):@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading {
animation: rotate 1s linear infinite;
} - 核心属性:animation-name(关键帧名称)、animation-duration(时长)、animation-timing-function(缓动)、animation-iteration-count(循环次数)。
10. 如何处理单行文本溢出,用省略号显示
答案: 核心CSS(小程序高频,需设置宽度):
.text-ellipsis {
width: 200px; /* 必须设置宽度 */
white-space: nowrap; /* 强制单行 */
overflow: hidden; /* 隐藏溢出内容 */
text-overflow: ellipsis; /* 溢出显示省略号 */
}
补充:多行文本溢出(小程序也常用):
.text-ellipsis-multi {
display: -webkit-box;
-webkit-line-clamp: 2; /* 显示2行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
11. 重绘和回流(重排)
(1)定义及区别
答案:
- 回流(重排):DOM元素的几何属性(宽高、位置、尺寸)变化,导致浏览器重新计算布局,触发页面重绘,性能开销大;
- 重绘:元素样式(颜色、背景、透明度)变化,不影响几何属性,仅重新绘制视觉效果,性能开销小;
- 关系:回流一定会触发重绘,重绘不一定触发回流。
(2)哪些属性修改会引发重绘/回流
答案:
- 触发回流的操作:
- 改变元素宽高、位置、尺寸(如width、height、top);
- 增删DOM元素(如小程序动态渲染列表);
- 窗口大小变化(如手机旋转);
- 计算offsetWidth/offsetHeight等布局属性;
- 修改display、float、position等属性。
- 触发重绘的操作(不触发回流): 修改color、background、opacity、box-shadow、outline等视觉属性。
(3)如何避免回流
答案:
- 先隐藏元素(display: none),修改后再显示;
- 使用文档碎片(DocumentFragment)批量添加DOM;
- 小程序中:一次性更新data,避免频繁setData(高频考点);
- 使用transform(仅触发合成层,无回流/重绘)实现动画(如translate代替top/left);
- 避免使用table布局(修改一个单元格就触发整个表格回流);
12. 样式冲突解决方案
答案:
- 优先级规则:!important > 行内样式 > ID选择器 > 类/伪类/属性选择器 > 标签/伪元素选择器 > 通配符;
- 小程序中:可通过::v-deep(Vue2)/:deep()(Vue3)穿透组件样式隔离;
- 小程序原生:组件开启styleIsolation: 'isolated'(默认),组件样式不影响外部;
- Vue:scoped属性,样式仅作用于当前组件;
13. 经典左右布局,左侧固定,右侧自适应
答案: 小程序中最常用的2种实现方式:
方案1(Flex,推荐):
.wrapper {
display: flex;
width: 100%;
}
.left {
width: 200px; /* 左侧固定宽度 */
flex-shrink: 0; /* 防止压缩 */
}
.right {
flex: 1; /* 右侧自适应 */
}
方案2(BFC+浮动):
.left {
float: left;
width: 200px;
}
.right {
overflow: hidden; /* 触发BFC,避免被浮动元素覆盖 */
}
二、JavaScript核心知识点
1. JS数据类型有哪些?判断数据类型的方法及区别(含特例)
答案:
(1)数据类型:
- 基本类型(值类型):String、Number、Boolean、Null、Undefined、Symbol(ES6)、BigInt(ES11);
- 引用类型:Object(包含Array、Function、Date、RegExp、普通对象等)。
(2)判断方法及区别:
| typeof | 检测数据类型的字符串表示 | 简单快捷,适合基本类型 | 1. typeof null === 'object'(历史bug);2. 引用类型除Function外均返回’object’ |
| instanceof | 检测原型链是否包含构造函数的prototype | 可区分引用类型(如Array/Object) | 1. 无法判断基本类型;2. 可被原型链篡改;3. 跨窗口/iframe失效 |
| Object.prototype.toString.call() | 读取对象的[[Class]]内部属性 | 最准确,可区分所有类型 | 写法稍繁琐 |
| Array.isArray() | 专门判断数组 | 精准判断数组 | 仅适用于数组 |
(3)示例:
typeof '123' // 'string'
typeof null // 'object'(特例)
[] instanceof Array // true
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(null) // '[object Null]'
Array.isArray([]) // true
2. Object.prototype.toString.call()判断使用是什么原理?
答案:
- 原理:每个JS对象都有一个内部属性[[Class]],该属性存储了对象的类型信息(如Array、Object、Null);
- Object.prototype.toString()方法的作用是返回[object [[Class]]]格式的字符串;
- 由于其他类型(如Array、Number)会重写toString方法,因此需要用call()改变this指向,调用最顶层的Object.prototype.toString,从而获取准确的[[Class]]属性。
- 示例:// 数组重写了toString,直接调用返回元素拼接
[1,2].toString() // '1,2'
// call改变this,调用Object的toString
Object.prototype.toString.call([1,2]) // '[object Array]'
3. 数组相关
(1)数组的所有API(方法),会改变原数组的方法有哪些?
答案:
① 会改变原数组的方法(9个):
- 增删改:push()(尾部加)、pop()(尾部删)、unshift()(头部加)、shift()(头部删)、splice()(任意位置增删改);
- 排序/反转:sort()、reverse();
- 填充/复制:fill()、copyWithin();
② 不改变原数组的方法(常用):
- 遍历:forEach()、map()、filter()、some()、every()、find()、findIndex();
- 拼接/截取:concat()、slice();
- 转换:join()、toString();
- 查找:indexOf()、includes()、lastIndexOf();
- 归并:reduce()、reduceRight();
(2)数组新增元素的方法,splice方法有几个参数,返回值是什么,push方法的返回值是什么?
答案:
① 数组新增元素的方法:
- 尾部新增:push()(改变原数组);
- 头部新增:unshift()(改变原数组);
- 任意位置新增:splice(起始索引, 0, 新增元素)(改变原数组);
- 生成新数组新增:concat()(不改变原数组)、[…arr, 新元素](ES6扩展运算符);
② splice参数及返回值:
- 语法:arr.splice(start[, deleteCount[, item1[, item2[, …]]]]);
- 参数:
- start:起始索引(必填,负数表示从末尾开始);
- deleteCount:删除元素个数(可选,0则不删除,省略则删除到末尾);
- item1…:新增的元素(可选);
- 返回值:被删除的元素组成的数组(无删除则返回空数组);
- 示例:[1,2,3].splice(1, 0, 4) → 原数组变为[1,4,2,3],返回[];
③ push返回值:
- 语法:arr.push(item1, item2, …);
- 返回值:新增后数组的长度;
- 示例:[1,2].push(3) → 返回3,原数组变为[1,2,3]。
(3)forEach 和 Map 的区别,是否都会改变原数组?
答案:
| 返回值 | 无返回值(undefined) | 返回新数组(由回调返回值组成) |
| 核心用途 | 遍历执行操作(如打印、修改原数组元素) | 遍历并转换数组(如数据格式化) |
| 是否改变原数组 | 本身不改变,但回调中可修改原数组元素 | 本身不改变,回调也不会修改原数组 |
| 中断遍历 | 无法中断(break/return无效) | 无法中断 |
| 性能 | 略高(无返回值) | 略低(需生成新数组) |
- 示例:const arr = [1,2,3];
// forEach
arr.forEach(item => item * 2); // 返回undefined,arr仍为[1,2,3]
// map
const newArr = arr.map(item => item * 2); // newArr=[2,4,6],arr仍为[1,2,3] - 核心结论:两者本身都不会改变原数组,forEach侧重“执行操作”,map侧重“转换数组”。
(4)求两个数组并集、交集的方法
答案:
① 并集(去重):
const arr1 = [1,2,3];
const arr2 = [3,4,5];
// 方法1:Set + 扩展运算符(ES6,推荐)
const union = […new Set([…arr1, …arr2])]; // [1,2,3,4,5]
// 方法2:filter + includes
const union2 = arr1.concat(arr2.filter(item => !arr1.includes(item)));
② 交集:
// 方法1:filter + includes(推荐)
const intersection = arr1.filter(item => arr2.includes(item)); // [3]
// 方法2:Set优化(大数据量)
const set2 = new Set(arr2);
const intersection2 = arr1.filter(item => set2.has(item));
4. 字符串相关
(1)常用字符串方法
答案:
| charAt(index) | 获取指定索引字符 | 'abc'.charAt(1) → ‘b’ |
| indexOf(str) | 查找子串首次出现位置,无则-1 | 'abc'.indexOf('b') → 1 |
| includes(str) | 判断是否包含子串(ES6) | 'abc'.includes('bc') → true |
| slice(start, end) | 截取子串(不包含end) | 'abc'.slice(1,3) → ‘bc’ |
| substring(start, end) | 截取子串(同slice,start>end自动交换) | 'abc'.substring(2,1) → ‘b’ |
| replace(old, new) | 替换子串(默认只替换第一个) | 'aaa'.replace('a', 'b') → ‘baa’ |
| split(sep) | 分割为数组 | 'a,b,c'.split(',') → [‘a’,‘b’,‘c’] |
| trim() | 去除首尾空格 | ' a b '.trim() → ‘a b’ |
| toUpperCase()/toLowerCase() | 大小写转换 | 'Ab'.toUpperCase() → ‘AB’ |
| startsWith(str)/endsWith(str) | 判断开头/结尾(ES6) | 'abc'.startsWith('a') → true |
(2)字符串转数组和数组转字符串的方法
答案:
① 字符串转数组:
- 方法1:split(分隔符)(常用):'123'.split('') → [‘1’,‘2’,‘3’];
- 方法2:Array.from():Array.from('123') → [‘1’,‘2’,‘3’];
- 方法3:扩展运算符:[…'123'] → [‘1’,‘2’,‘3’];
② 数组转字符串:
- 方法1:join(分隔符)(常用):[1,2,3].join(',') → ‘1,2,3’;
- 方法2:toString():[1,2,3].toString() → ‘1,2,3’;
- 方法3:String():String([1,2,3]) → ‘1,2,3’。
5. 拷贝相关
(1)JS中有哪些是实现浅拷贝的方法?
答案: 浅拷贝:只拷贝第一层属性,引用类型属性仍指向原地址; 常用方法:
const obj = { a: 1, b: { c: 2 } };
const newObj = { …obj };
newObj.b.c = 3; // 原obj.b.c也变为3(浅拷贝特性)
(2)JSON转换实现深拷贝方法的局限性。写实现深拷贝的方法(需处理Date、正则、循环引用)
答案:
① JSON转换深拷贝的局限性:
const deepClone = obj => JSON.parse(JSON.stringify(obj));
缺点:
② 完整深拷贝实现(处理Date、正则、循环引用):
function deepClone(target, map = new WeakMap()) {
// 处理null/undefined
if (target === null || typeof target !== 'object') return target;
// 处理循环引用
if (map.has(target)) return map.get(target);
// 处理日期
if (target instanceof Date) {
const newDate = new Date();
newDate.setTime(target.getTime());
map.set(target, newDate);
return newDate;
}
// 处理正则
if (target instanceof RegExp) {
const newReg = new RegExp(target.source, target.flags);
map.set(target, newReg);
return newReg;
}
// 处理数组/对象(保留原型)
const cloneTarget = new target.constructor();
map.set(target, cloneTarget);
// 遍历属性(包含不可枚举、Symbol属性)
Reflect.ownKeys(target).forEach(key => {
cloneTarget[key] = deepClone(target[key], map);
});
return cloneTarget;
}
6. 函数相关
(1)手撕 – sleep(delay)函数
答案: sleep函数:延迟指定时间后执行后续代码,有2种实现方式:
方式1(Promise + async/await,推荐):
function sleep(delay) {
return new Promise(resolve => {
setTimeout(resolve, delay);
});
}
// 使用
async function test() {
console.log('开始');
await sleep(1000); // 延迟1秒
console.log('1秒后执行');
}
test();
方式2(回调函数):
function sleep(delay, callback) {
setTimeout(callback, delay);
}
// 使用
sleep(1000, () => {
console.log('1秒后执行');
});
(2)防抖和节流是什么意思?说实现思路
答案: 核心解决:高频事件(如滚动、输入、点击)触发过多导致性能问题,小程序中常用于搜索框输入、下拉刷新等场景。
① 防抖(Debounce):
- 定义:触发事件后,延迟n秒执行回调;若n秒内再次触发,则重置延迟时间;
- 核心:“延迟执行,重复触发重置”,适合搜索框输入、按钮防重复点击;
- 实现思路:function debounce(fn, delay) {
let timer = null;
return function(…args) {
// 重置定时器
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}// 使用:搜索框输入防抖
const inputHandler = debounce((value) => {
console.log('搜索:', value);
}, 500);
② 节流(Throttle):
- 定义:触发事件后,n秒内只执行一次回调,剩余触发忽略;
- 核心:“固定频率执行”,适合滚动加载、窗口resize;
- 实现思路(时间戳版):function throttle(fn, interval) {
let lastTime = 0;
return function(…args) {
const now = Date.now();
// 间隔时间内不执行
if (now – lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}// 使用:滚动节流
const scrollHandler = throttle(() => {
console.log('滚动中');
}, 200);
(3)call apply区别
答案:
- 相同点:
- 都用于改变函数this指向;
- 第一个参数都是this指向的对象(非严格模式下传null/undefined则指向window);
- 都会立即执行函数;
- 不同点:参数传递方式不同;
- call:第二个及以后参数是逐个传递; 示例:fn.call(obj, 1, 2, 3);
- apply:第二个参数是数组/类数组,一次性传递; 示例:fn.apply(obj, [1,2,3]);
- 应用场景:
- 参数数量固定用call;
- 参数数量不固定/已有数组用apply(如Math.max.apply(null, arr));
- 补充:ES6可通过fn(…arr)替代apply。
(4)async和defer有什么区别?
答案: 两者均用于优化<script>标签加载,避免阻塞页面渲染,小程序中虽不直接使用,但需理解原理:
| 加载时机 | 异步加载(不阻塞HTML解析) | 异步加载(不阻塞HTML解析) |
| 执行时机 | 加载完成后立即执行(可能中断HTML解析) | HTML解析完成后、DOMContentLoaded前执行 |
| 执行顺序 | 不保证(按加载完成顺序) | 保证(按标签书写顺序) |
| 适用场景 | 独立脚本(如统计、广告) | 依赖DOM/顺序的脚本(如业务逻辑) |
7. 原型与原型链
(1)说说原型?
答案: 原型是JS实现继承的核心机制,核心概念:
- prototype(原型对象):函数特有的属性,指向一个对象,该对象是该函数实例的原型;
- __proto__(隐式原型):所有对象(除null)都有的属性,指向其构造函数的prototype;
- constructor:原型对象上的属性,指向构造函数本身;
- 示例:function Person() {}
const p = new Person();
p.__proto__ === Person.prototype; // true
Person.prototype.constructor === Person; // true
(2)原型链查找?
答案:
- 定义:当访问对象的属性/方法时,先在自身查找,找不到则通过__proto__向上查找原型对象,直到Object.prototype,若仍找不到则返回undefined,这个查找链条就是原型链;
- 核心规则:
- 读取属性时触发原型链查找,赋值时仅修改自身属性(除非原型属性是可写的);
- Object.prototype.__proto__ === null(原型链终点);
- 示例:const arr = [1,2];
// 访问forEach:arr自身没有 → arr.__proto__(Array.prototype)有 → 找到
arr.forEach();
// 访问abc:arr → Array.prototype → Object.prototype → 找不到 → undefined
arr.abc; // undefined
8. 其他核心概念
(1)this的指向?怎么改变?
答案:
① this指向规则(优先级从高到低):
- 特殊:箭头函数无this,其this继承自外层作用域;
② 改变this指向的方法:
- 示例:const obj = { a: 1 };
function fn() { console.log(this.a); }
fn.call(obj); // 1(call改变)
const newFn = fn.bind(obj);
newFn(); // 1(bind改变)
(2)闭包?
答案:
- 定义:函数嵌套时,内部函数引用外部函数的变量/参数,外部函数执行后,变量仍被内部函数引用而不被销毁,形成闭包;
- 核心作用:
- 延长变量生命周期(如缓存数据);
- 私有化变量(避免全局污染);
- 示例(小程序中常用):function createCounter() {
let count = 0; // 私有变量,外部无法访问
return function() {
count++;
return count;
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2(count未被销毁) - 注意:闭包可能导致内存泄漏,需及时释放引用。
(3)事件循环机制?
答案: JS是单线程,事件循环(Event Loop)是异步执行的核心机制,分为浏览器/Node环境,核心规则:
- 宏任务(Macrotask):script整体、setTimeout/setInterval、AJAX、DOM事件、setImmediate;
- 微任务(Microtask):Promise.then/catch/finally、async/await、MutationObserver、process.nextTick;
- 执行同步代码(宏任务);
- 执行所有微任务;
- 渲染页面;
- 取一个宏任务执行,重复上述步骤;
- 示例:console.log(1); // 同步
setTimeout(() => console.log(2)); // 宏任务
Promise.resolve().then(() => console.log(3)); // 微任务
// 执行顺序:1 → 3 → 2
(4)nextTick是什么,原理是什么?
答案:
- 定义:Vue/小程序中提供的方法,用于在DOM更新完成后执行回调;
- 原理:
- Vue2:优先使用微任务(Promise.then),降级使用宏任务(setTimeout);
- Vue3:统一使用微任务(queueMicrotask);
- 核心作用:解决“修改数据后立即操作DOM,DOM未更新”的问题;
- 示例(Vue):this.msg = 'new msg';
// 此时DOM未更新
this.$nextTick(() => {
// DOM已更新,可操作
console.log(document.querySelector('.msg').textContent);
});
(5)数据类型隐式转换规则和显示转换的做法?
答案:
① 隐式转换(自动转换,常见于运算/判断):
- 转字符串:+运算符(一侧为字符串),如1 + '2' → '12';
- 转数字:算术运算(-/*%)、比较运算(>/<),如'123' – 1 → 122;
- 转布尔:逻辑运算(&&/||/!)、if判断,假值:0/''/null/undefined/NaN/false,其余为真;
- 特殊:null == undefined → true,NaN != NaN → true;
② 显式转换(手动转换):
- 转字符串:String(obj)、obj.toString();
- 转数字:Number(obj)、parseInt()、parseFloat();
- 转布尔:Boolean(obj)、!!obj;
- 示例:String(123); // '123'
Number('123a'); // NaN
Boolean(''); // false
(6)讲讲Promise?(含输出题)
答案:
① Promise核心:
- 定义:异步编程解决方案,解决回调地狱,有3种状态:pending(进行中)、fulfilled(成功)、rejected(失败),状态一旦改变不可逆转;
- 核心方法:
- Promise.resolve()/Promise.reject():快速创建成功/失败的Promise;
- then():成功回调,返回新Promise;
- catch():失败回调,捕获错误;
- finally():无论成功失败都执行;
- Promise.all():所有Promise成功才成功,一个失败则失败;
- Promise.race():第一个完成的Promise决定结果;
② 输出题示例:
console.log(1);
const p = new Promise((resolve) => {
console.log(2);
resolve();
console.log(3);
});
p.then(() => console.log(4));
console.log(5);
// 执行顺序:1 → 2 → 3 → 5 → 4
- 解析:Promise构造函数内是同步代码,then是微任务,同步代码执行完后执行微任务。
三、Vue相关知识点
1. vue组件间通信?
答案: 按通信场景分类(Vue2/Vue3通用):
- 父→子:props(小程序中为properties);
- 子→父:$emit(Vue2)/emit(Vue3)触发自定义事件(小程序中为triggerEvent);
- 事件总线(Vue2:new Vue(),Vue3:mitt库);
- 父组件中转;
- Vue2:provide/inject;
- Vue3:provide/inject(支持响应式);
- Vuex/Pinia(状态管理,小程序中为Mobx/全局变量);
- Vuex(Vue2)/Pinia(Vue3);
- 全局挂载(Vue2:Vue.prototype,Vue3:app.config.globalProperties);
2. vue生命周期?
答案:
(1)Vue2生命周期(8个核心钩子):
- 创建阶段(初始化):
- beforeCreate:实例创建前,data/methods未初始化;
- created:实例创建完成,可访问data/methods,DOM未生成;
- 挂载阶段(渲染DOM): 3. beforeMount:挂载前,模板已编译,DOM未挂载; 4. mounted:挂载完成,DOM已生成,可操作DOM(小程序中对应onReady);
- 更新阶段(数据变化): 5. beforeUpdate:数据更新前,DOM未更新; 6. updated:数据更新后,DOM已更新;
- 销毁阶段: 7. beforeDestroy:销毁前,实例仍可用,可清除定时器/解绑事件; 8. destroyed:销毁完成,实例不可用;
(2)Vue3生命周期(组合式API):
- 替换为钩子函数:onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted;
- 核心区别:Vue3取消beforeCreate/created,直接在setup中编写初始化逻辑。
3. computed和watch有什么区别呢?
答案:
| 核心用途 | 计算派生值(如拼接字符串、格式化数据) | 监听数据变化,执行异步/复杂操作(如请求接口) |
| 返回值 | 有返回值(必须return) | 无返回值 |
| 缓存 | 依赖数据不变时,缓存结果,重复访问不重新计算 | 无缓存,数据变化即触发 |
| 同步/异步 | 仅支持同步 | 支持同步/异步 |
| 触发时机 | 依赖数据变化时自动更新 | 数据变化时触发回调 |
| 使用场景 | 简单数据计算(如fullName = firstName + lastName) | 复杂逻辑(如监听输入框变化请求搜索接口) |
4. vue的响应式原理有了解吗?
答案:
(1)Vue2响应式原理:
- 核心:Object.defineProperty() 劫持对象属性的get/set;
- 流程:
- 初始化时,遍历data对象,通过Object.defineProperty为每个属性添加get/set;
- get:收集依赖(Watcher);
- set:触发依赖更新,通知Watcher更新视图;
- 缺点:
- 无法监听数组下标修改、数组长度修改;
- 无法监听对象新增/删除属性;
(2)Vue3响应式原理:
- 核心:Proxy 代理整个对象 + Reflect 操作对象;
- 优势:
- 可监听数组下标/长度修改;
- 可监听对象新增/删除属性;
- 支持Map/Set等数据结构;
5. Vue2对数组的方法进行了一个重写是吧?那我动态的给对象增加一个属性可以触发响应式吗?
答案:
(1)Vue2重写数组方法:
- 原因:Object.defineProperty无法监听数组下标修改,因此Vue2重写了数组的7个变异方法(push/pop/unshift/shift/splice/sort/reverse);
- 原理:重写后的方法在执行原逻辑的同时,触发依赖更新(通知视图刷新);
(2)动态给对象新增属性:
- 默认不能触发响应式(因为Object.defineProperty只能劫持已有属性);
- 解决方案:
- Vue.set(obj, key, value)(全局方法);
- this.$set(obj, key, value)(实例方法);
- 示例:this.obj = { a: 1 };
// 新增属性b,无响应式
this.obj.b = 2;
// 正确方式,触发响应式
this.$set(this.obj, 'b', 2);
6. vue2和vue3有什么区别呢?
答案:
| 响应式原理 | Object.defineProperty | Proxy + Reflect |
| 组合式API | 无(仅选项式API) | setup + 组合式API(更灵活组织代码) |
| 生命周期 | beforeCreate/created等 | onMounted/onUnmounted等(组合式) |
| 根节点 | 只能有一个根节点 | 支持多个根节点 |
| 性能 | 打包体积大,响应式有局限 | 打包体积小,响应式更完善,编译优化 |
| TypeScript | 支持差 | 原生支持TS,类型推断更完善 |
| 全局API | Vue.prototype挂载 | app.config.globalProperties挂载 |
| 指令 | v-on.native | 移除,组件直接监听原生事件 |
7. 常用Hook,usememo和memo,usecallback怎么用的,useeffect想只在销毁时执行应该怎么写依赖项?
答案: Vue3组合式API/React Hook(面试中常问,小程序中也有类似思想):
(1)memo(React)/shallowRef(Vue3):
- 作用:组件/数据浅比较,避免不必要的重渲染;
- 使用:React中包裹组件const MemoComponent = memo(Component);
(2)useMemo(React)/computed(Vue3):
- 作用:缓存计算结果,避免重复计算;
- 语法:const result = useMemo(() => 计算逻辑, [依赖项]);
(3)useCallback(React)/useCallback(Vue3):
- 作用:缓存函数引用,避免函数重新创建导致子组件重渲染;
- 语法:const fn = useCallback(() => {}, [依赖项]);
(4)useEffect仅在销毁时执行:
- 原理:依赖项为空数组,返回清理函数;
- 语法:useEffect(() => {
// 初始化逻辑
return () => {
// 销毁时执行的逻辑(如清除定时器、解绑事件)
};
}, []); // 空依赖项
8. axios封装:封装方式、axios里面如何取消一个请求呢?
答案:
(1)axios封装(小程序中常用):
import axios from 'axios';
// 创建实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000,
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 添加token
config.headers.Authorization = localStorage.getItem('token');
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 统一处理响应数据
const res = response.data;
if (res.code !== 200) {
// 错误提示
return Promise.reject(res.msg);
}
return res;
},
error => Promise.reject(error)
);
export default service;
(2)取消axios请求:
- 原理:使用axios的CancelToken(axios v0.22.0后推荐使用AbortController);
- 方式1(CancelToken):const CancelToken = axios.CancelToken;
const source = CancelToken.source();// 发起请求
axios.get('/api/data', {
cancelToken: source.token
}).catch(err => {
if (axios.isCancel(err)) {
console.log('请求取消:', err.message);
}
});// 取消请求
source.cancel('用户取消了请求'); - 方式2(AbortController,推荐):const controller = new AbortController();
axios.get('/api/data', {
signal: controller.signal
});// 取消请求
controller.abort();
9. ES6新内容?
答案: 核心新特性(高频考点):
四、网络相关知识点
1. HTTP状态码
答案: 按状态码分类记忆(高频):
- 1xx(信息):请求已接收,继续处理(如100 Continue);
- 2xx(成功):
- 200 OK:请求成功;
- 201 Created:创建成功(如POST新增数据);
- 3xx(重定向):
- 301 Moved Permanently:永久重定向;
- 302 Found:临时重定向;
- 304 Not Modified:资源未修改,使用缓存;
- 4xx(客户端错误):
- 400 Bad Request:请求参数错误;
- 401 Unauthorized:未授权(无token);
- 403 Forbidden:禁止访问(权限不足);
- 404 Not Found:资源不存在;
- 405 Method Not Allowed:请求方法不允许(如GET用了POST);
- 5xx(服务端错误):
- 500 Internal Server Error:服务端内部错误;
- 502 Bad Gateway:网关错误;
- 503 Service Unavailable:服务不可用(过载/维护);
2. GET请求和POST请求的区别
答案:
| 请求参数 | 拼接在URL后(查询字符串) | 放在请求体中 |
| 数据长度 | 受URL长度限制(约2KB) | 无限制 |
| 安全性 | 低(参数暴露在URL) | 高(参数在请求体) |
| 缓存 | 可缓存(浏览器默认缓存) | 不可缓存 |
| 幂等性 | 幂等(多次请求结果一致) | 非幂等(多次请求可能创建多个资源) |
| 用途 | 查询数据(如列表、详情) | 提交/修改数据(如新增、修改) |
3. 跨域:实际项目中怎么解决的?跨域的请求会到服务端吗?
答案:
(1)跨域定义:
浏览器同源策略限制(协议、域名、端口任一不同即为跨域),小程序中默认不限制跨域(需配置合法域名)。
(2)实际解决跨域的方案:
- 服务端设置响应头:Access-Control-Allow-Origin: *(或指定域名);
- 支持所有请求方法,小程序中常用;
- Vue项目:配置vue.config.js的devServer.proxy;
- 原理:前端请求本地服务器,本地服务器转发请求到目标服务器(避开浏览器同源策略);
- 原理:利用<script>标签无跨域限制,仅支持GET请求;
- 缺点:安全性低,仅适用于简单场景;
- 生产环境常用,Nginx配置转发规则,前端请求Nginx,Nginx转发到后端;
(3)跨域请求是否会到服务端?
- 简单请求(GET/POST,Content-Type为application/x-www-form-urlencoded等):请求会到达服务端,服务端处理后返回,浏览器检查响应头,若无CORS则拦截响应;
- 预检请求(OPTIONS):先发送OPTIONS请求到服务端,验证是否允许跨域,服务端通过后才发送真实请求;
- 结论:跨域请求会到达服务端,但浏览器会拦截不符合CORS的响应。
4. 输入URL到页面加载的过程(浏览器从输入url到渲染的整个过程)
答案: 核心流程(分8步):
- 解析HTML:构建DOM树;
- 解析CSS:构建CSSOM树;
- 合并DOM+CSSOM:构建渲染树;
- 布局(回流):计算元素位置/尺寸;
- 绘制(重绘):绘制像素到屏幕;
- 合成:将图层合成,显示页面;
- 补充:小程序中流程类似,但无DOM/CSSOM,解析为自定义渲染树。
五、工程化与工具
1. 常用的git命令。在自己主分支上修改一些内容后怎样再提交到主分支?
答案:
(1)常用Git命令:
- 基础操作: git init(初始化仓库)、git clone <url>(克隆仓库)、git add .(添加所有文件)、git commit -m "备注"(提交到本地)、git push(推送到远程);
- 分支操作: git branch(查看分支)、git checkout -b <分支名>(创建并切换分支)、git merge <分支名>(合并分支);
- 其他: git pull(拉取远程代码)、git status(查看状态)、git log(查看提交记录)、git reset –hard <commit-id>(回滚提交);
(2)主分支修改后提交流程:
# 1. 查看修改状态
git status
# 2. 添加修改的文件
git add .
# 3. 提交到本地仓库
git commit -m "修改了xxx功能"
# 4. 拉取远程主分支最新代码(避免冲突)
git pull origin main(或master)
# 5. 推送本地提交到远程主分支
git push origin main(或master)
- 补充:规范流程应先创建feature分支,修改后合并到主分支,避免直接修改主分支。
2. 为什么vite比webpack打包快呢?
答案: 核心原因:构建原理不同,Vite采用“按需编译”,Webpack采用“全量编译”;
- Webpack:启动时打包所有模块,生成bundle,启动慢;
- Vite:基于ESModule,不打包,启动时仅启动服务器,请求时按需编译模块,启动快;
- Webpack:热更新需重新打包相关模块,速度慢;
- Vite:热更新仅更新修改的模块,速度快;
- Vite使用Rollup打包,比Webpack更轻量,打包体积更小;
六、移动端/小程序适配与性能优化
1. 如何适配多种不同的手机屏幕大小?自适应布局有哪些方法呢?
答案: 小程序适配核心方法:
- 小程序特有单位,1rpx = 屏幕宽度/750,适配所有屏幕;
- 设计稿750px → 1:1使用rpx,如设计稿375px → 375rpx;
- 弹性布局,子元素通过flex:1自适应剩余空间,适配不同屏幕;
- @media (min-width: 750px) { … },针对不同屏幕尺寸设置样式;
- 根元素font-size设为屏幕宽度/10,子元素用rem,小程序中可结合postcss-px-to-viewport;
- 小程序默认viewport-fit=cover,适配刘海屏;
2. 讲讲长列表的渲染?除了监听滚动实现,你还知道什么方法吗?滚动到底部全部渲染出来会不会卡?
答案:
(1)长列表渲染问题:
- 全部渲染DOM会导致DOM节点过多,占用内存,滚动卡顿(重绘/回流频繁);
(2)优化方案(小程序高频):
- 核心:只渲染可视区域内的列表项,滚动时动态替换内容;
- 小程序原生:recycle-view组件;
- 第三方:vue-virtual-scroller(Vue)、react-window(React);
- 监听滚动到底部,加载下一页数据,每次只渲染部分数据;
- 图片懒加载(小程序lazy-load属性);
- 列表项懒加载(进入可视区域再渲染);
- 简化列表项结构,减少嵌套,降低重绘/回流开销;
3. 除了懒加载,你还知道其他优化加载速度的方案吗?
答案: 小程序/前端加载速度优化方案:
- 图片优化:压缩图片(tinypng)、使用webp格式、CDN加载;
- 代码压缩:压缩JS/CSS,移除无用代码(Tree Shaking);
- 分包加载:小程序分包,首屏加载主包,其他包按需加载;
- 接口缓存:本地缓存接口数据(wx.setStorage),避免重复请求;
- 静态资源缓存:利用HTTP缓存(304)、小程序缓存;
- 预加载下一页资源(如点击前预加载);
- 非核心脚本异步加载(async/defer);
- 小程序中使用wx.loadSubpackage预加载分包;
- 接口合并:减少请求次数;
- 接口缓存:服务端缓存热点数据;
七、表单相关知识点
1. 谈谈内部表单组件和Formily之间的差异?
答案:
| 定位 | 基础表单组件(输入框、选择器等) | 高性能表单解决方案(基于JSON Schema) |
| 核心能力 | 基础输入/校验,需手动编写联动/校验逻辑 | 内置联动、校验、状态管理,支持复杂表单 |
| 开发效率 | 简单表单快,复杂表单需大量自定义逻辑 | 复杂表单开发效率高,配置化开发 |
| 扩展性 | 需手动扩展(如自定义校验、联动) | 高扩展性,支持自定义组件、校验规则 |
| 状态管理 | 需手动维护表单状态(如value、error) | 自动管理表单状态,支持表单快照/回滚 |
| 适用场景 | 简单表单(如登录、注册) | 复杂表单(如配置页、多步骤表单) |
2. 内部的表单组件怎么实现也联动关系?
答案: 表单联动核心:监听一个表单字段变化,修改另一个字段的状态(值/禁用/可选值);
(1)Vue/小程序实现方式:
// 示例:选择省份后,联动更新城市选项
// 1. 监听省份字段变化
watch: {
province(val) {
// 2. 根据省份获取城市列表
const cityList = this.cityData.filter(item => item.province === val);
// 3. 更新城市选项
this.cityOptions = cityList;
// 4. 重置城市值(可选)
this.city = '';
// 5. 禁用/启用其他字段(可选)
this.disabledField = val === '北京';
}
}
(2)核心思路:
八、项目相关面试问题
1. 可以介绍一下你的项目吗?是看视频做的吗?pc打开还是手机打开的?项目有遇到什么问题?
答案(答题框架):
- 项目介绍:核心功能(如“小程序商城,实现商品展示、购物车、支付”)、技术栈(Vue/小程序原生、UniApp、Axios)、负责模块;
- 是否看视频:“参考过视频思路,但核心逻辑(如购物车联动、支付对接)是自己实现的,还做了优化(如长列表虚拟滚动)”;
- 运行终端:“主要适配手机端(小程序),也兼容H5端(PC)”;
- 项目问题:举例1-2个具体问题(如“长列表滚动卡顿→优化为虚拟列表”、“接口请求频繁→添加防抖+缓存”),说明问题、原因、解决方案、优化效果。
2. 我看到了你优化了加载速度,你是怎么做的这个呢?
答案(结合小程序):
九、通用面试问题
1. 有做过什么AI的项目吗,遇到了什么难点?
答案(无AI项目也可回答):
- 有项目:“做过基于AI的图片识别小程序(如车牌识别),难点是识别准确率低→解决方案:对接百度AI接口,优化图片预处理(压缩、转正),准确率从80%提升到95%;另外是接口响应慢→添加加载动画,优化用户体验”;
- 无项目:“暂时没有直接做过AI项目,但了解过AI相关技术(如大模型API对接),学习过如何在小程序中集成ChatGPT API,难点是接口限流→解决方案:添加请求队列,缓存常用回答”。
网硕互联帮助中心
![[特殊字符] 纯前端M3U8视频处理工具:在线播放、录制与转换的一站式解决方案-网硕互联帮助中心](https://www.wsisp.com/helps/wp-content/uploads/2026/02/20260131225721-697e88d1ece9e-220x150.png)





评论前必须登录!
注册