云计算百科
云计算领域专业知识百科平台

微信小程序开发岗前端面试题(含答案)

微信小程序开发岗前端面试题(含答案)

一、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)如何避免回流

答案:

  • 批量修改DOM:
    • 先隐藏元素(display: none),修改后再显示;
    • 使用文档碎片(DocumentFragment)批量添加DOM;
    • 小程序中:一次性更新data,避免频繁setData(高频考点);
  • 减少布局查询:避免频繁获取offsetWidth、scrollTop等属性(可缓存结果);
  • 优化CSS:
    • 使用transform(仅触发合成层,无回流/重绘)实现动画(如translate代替top/left);
    • 避免使用table布局(修改一个单元格就触发整个表格回流);
  • 脱离文档流:对动画元素使用absolute/fixed定位,使其不影响其他元素布局。
  • 12. 样式冲突解决方案

    答案:

  • 提高选择器优先级:
    • 优先级规则:!important > 行内样式 > ID选择器 > 类/伪类/属性选择器 > 标签/伪元素选择器 > 通配符;
    • 小程序中:可通过::v-deep(Vue2)/:deep()(Vue3)穿透组件样式隔离;
  • CSS命名规范:使用BEM(块-元素-修饰符)命名,避免命名冲突(如btn__text–active);
  • 样式隔离:
    • 小程序原生:组件开启styleIsolation: 'isolated'(默认),组件样式不影响外部;
    • Vue:scoped属性,样式仅作用于当前组件;
  • 重置/归一化样式:引入reset.css或normalize.css,统一浏览器默认样式;
  • 按需加载样式:避免全局引入大量冗余样式,按页面/组件拆分样式文件。
  • 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 的区别,是否都会改变原数组?

    答案:

    特性forEachmap
    返回值 无返回值(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 newObj = { …obj }、const newArr = […arr];
  • Object.assign():const newObj = Object.assign({}, obj);
  • 数组方法:arr.slice()、arr.concat();
  • 手动遍历:遍历第一层属性赋值; 示例:
  • 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));

    缺点:

  • 无法拷贝函数、undefined、Symbol;
  • 无法拷贝循环引用(会报错);
  • 日期对象会被转为字符串;
  • 正则对象会被转为空对象;
  • 丢失原型链(拷贝后变为普通对象)。
  • ② 完整深拷贝实现(处理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>标签加载,避免阻塞页面渲染,小程序中虽不直接使用,但需理解原理:

    特性asyncdefer
    加载时机 异步加载(不阻塞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指向规则(优先级从高到低):
  • 显式绑定:call/apply/bind指定的this;
  • 隐式绑定:调用对象的方法时,this指向该对象;
  • new绑定:构造函数中this指向新建的实例;
  • 默认绑定:全局环境(浏览器window,Node global),严格模式下为undefined;
    • 特殊:箭头函数无this,其this继承自外层作用域;
    ② 改变this指向的方法:
  • call:立即执行,逐个传参;
  • apply:立即执行,数组传参;
  • bind:返回新函数,不立即执行,可传参(柯里化);
  • 箭头函数:通过绑定外层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有什么区别呢?

    答案:

    特性computedwatch
    核心用途 计算派生值(如拼接字符串、格式化数据) 监听数据变化,执行异步/复杂操作(如请求接口)
    返回值 有返回值(必须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有什么区别呢?

    答案:

    维度Vue2Vue3
    响应式原理 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新内容?

    答案: 核心新特性(高频考点):

  • 变量声明:let(块级作用域)、const(常量);
  • 箭头函数:() => {}(无this,简化写法);
  • 解构赋值:const {a, b} = obj、const [x, y] = arr;
  • 模板字符串:`hello ${name}`;
  • 扩展运算符:…arr、…obj;
  • 类与继承:class、extends、super;
  • 模块化:import/export;
  • Promise:异步编程;
  • 数据结构:Set(去重)、Map(键值对);
  • 其他:默认参数、可选链(ES2020)、空值合并运算符(ES2020)。
  • 四、网络相关知识点

    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请求的区别

    答案:

    特性GETPOST
    请求参数 拼接在URL后(查询字符串) 放在请求体中
    数据长度 受URL长度限制(约2KB) 无限制
    安全性 低(参数暴露在URL) 高(参数在请求体)
    缓存 可缓存(浏览器默认缓存) 不可缓存
    幂等性 幂等(多次请求结果一致) 非幂等(多次请求可能创建多个资源)
    用途 查询数据(如列表、详情) 提交/修改数据(如新增、修改)

    3. 跨域:实际项目中怎么解决的?跨域的请求会到服务端吗?

    答案:

    (1)跨域定义:

    浏览器同源策略限制(协议、域名、端口任一不同即为跨域),小程序中默认不限制跨域(需配置合法域名)。

    (2)实际解决跨域的方案:
  • CORS(跨域资源共享,推荐):
    • 服务端设置响应头:Access-Control-Allow-Origin: *(或指定域名);
    • 支持所有请求方法,小程序中常用;
  • 代理(开发环境):
    • Vue项目:配置vue.config.js的devServer.proxy;
    • 原理:前端请求本地服务器,本地服务器转发请求到目标服务器(避开浏览器同源策略);
  • JSONP:
    • 原理:利用<script>标签无跨域限制,仅支持GET请求;
    • 缺点:安全性低,仅适用于简单场景;
  • Nginx反向代理:
    • 生产环境常用,Nginx配置转发规则,前端请求Nginx,Nginx转发到后端;
  • (3)跨域请求是否会到服务端?
    • 简单请求(GET/POST,Content-Type为application/x-www-form-urlencoded等):请求会到达服务端,服务端处理后返回,浏览器检查响应头,若无CORS则拦截响应;
    • 预检请求(OPTIONS):先发送OPTIONS请求到服务端,验证是否允许跨域,服务端通过后才发送真实请求;
    • 结论:跨域请求会到达服务端,但浏览器会拦截不符合CORS的响应。

    4. 输入URL到页面加载的过程(浏览器从输入url到渲染的整个过程)

    答案: 核心流程(分8步):

  • URL解析:浏览器解析URL,判断是否为合法URL;
  • DNS解析:将域名转换为IP地址(优先查本地缓存,再查DNS服务器);
  • 建立TCP连接:三次握手(SYN→SYN+ACK→ACK);
  • 发送HTTP请求:浏览器向服务端发送请求(请求行、请求头、请求体);
  • 服务端处理请求:服务端接收请求,处理后返回响应(状态码、响应头、响应体);
  • 关闭TCP连接:四次挥手(FIN→ACK→FIN→ACK);
  • 浏览器解析响应:
    • 解析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更轻量,打包体积更小;
  • 其他:Vite内置优化(如预构建依赖),减少重复编译。
  • 六、移动端/小程序适配与性能优化

    1. 如何适配多种不同的手机屏幕大小?自适应布局有哪些方法呢?

    答案: 小程序适配核心方法:

  • rpx单位(推荐):
    • 小程序特有单位,1rpx = 屏幕宽度/750,适配所有屏幕;
    • 设计稿750px → 1:1使用rpx,如设计稿375px → 375rpx;
  • flex布局:
    • 弹性布局,子元素通过flex:1自适应剩余空间,适配不同屏幕;
  • 媒体查询:
    • @media (min-width: 750px) { … },针对不同屏幕尺寸设置样式;
  • rem单位:
    • 根元素font-size设为屏幕宽度/10,子元素用rem,小程序中可结合postcss-px-to-viewport;
  • viewport适配:
    • 小程序默认viewport-fit=cover,适配刘海屏;
  • 2. 讲讲长列表的渲染?除了监听滚动实现,你还知道什么方法吗?滚动到底部全部渲染出来会不会卡?

    答案:

    (1)长列表渲染问题:
    • 全部渲染DOM会导致DOM节点过多,占用内存,滚动卡顿(重绘/回流频繁);
    (2)优化方案(小程序高频):
  • 虚拟列表(可视区域渲染):
    • 核心:只渲染可视区域内的列表项,滚动时动态替换内容;
    • 小程序原生:recycle-view组件;
    • 第三方:vue-virtual-scroller(Vue)、react-window(React);
  • 分页加载:
    • 监听滚动到底部,加载下一页数据,每次只渲染部分数据;
  • 懒加载:
    • 图片懒加载(小程序lazy-load属性);
    • 列表项懒加载(进入可视区域再渲染);
  • 减少DOM层级:
    • 简化列表项结构,减少嵌套,降低重绘/回流开销;
  • 3. 除了懒加载,你还知道其他优化加载速度的方案吗?

    答案: 小程序/前端加载速度优化方案:

  • 资源优化:
    • 图片优化:压缩图片(tinypng)、使用webp格式、CDN加载;
    • 代码压缩:压缩JS/CSS,移除无用代码(Tree Shaking);
    • 分包加载:小程序分包,首屏加载主包,其他包按需加载;
  • 缓存优化:
    • 接口缓存:本地缓存接口数据(wx.setStorage),避免重复请求;
    • 静态资源缓存:利用HTTP缓存(304)、小程序缓存;
  • 预加载:
    • 预加载下一页资源(如点击前预加载);
  • 异步加载:
    • 非核心脚本异步加载(async/defer);
    • 小程序中使用wx.loadSubpackage预加载分包;
  • 服务端优化:
    • 接口合并:减少请求次数;
    • 接口缓存:服务端缓存热点数据;
  • 七、表单相关知识点

    1. 谈谈内部表单组件和Formily之间的差异?

    答案:

    特性内部表单组件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)核心思路:
  • 监听源字段的变化(watch/$watch);
  • 根据源字段值,修改目标字段的属性(值、可选列表、禁用状态);
  • 触发表单重新渲染(小程序中setData,Vue中响应式更新);
  • 八、项目相关面试问题

    1. 可以介绍一下你的项目吗?是看视频做的吗?pc打开还是手机打开的?项目有遇到什么问题?

    答案(答题框架):

    • 项目介绍:核心功能(如“小程序商城,实现商品展示、购物车、支付”)、技术栈(Vue/小程序原生、UniApp、Axios)、负责模块;
    • 是否看视频:“参考过视频思路,但核心逻辑(如购物车联动、支付对接)是自己实现的,还做了优化(如长列表虚拟滚动)”;
    • 运行终端:“主要适配手机端(小程序),也兼容H5端(PC)”;
    • 项目问题:举例1-2个具体问题(如“长列表滚动卡顿→优化为虚拟列表”、“接口请求频繁→添加防抖+缓存”),说明问题、原因、解决方案、优化效果。

    2. 我看到了你优化了加载速度,你是怎么做的这个呢?

    答案(结合小程序):

  • 图片优化:使用小程序lazy-load属性实现图片懒加载,压缩图片体积(从500KB降到100KB);
  • 分包加载:将非首屏页面(如个人中心、订单)分包,首屏加载时间从3s降到1.5s;
  • 接口优化:合并多个接口请求(如商品列表+分类),减少请求次数;
  • 缓存优化:本地缓存商品列表数据,第二次打开无需请求,加载速度提升50%;
  • 代码优化:移除无用代码,压缩JS/CSS,包体积减少20%。
  • 九、通用面试问题

    1. 有做过什么AI的项目吗,遇到了什么难点?

    答案(无AI项目也可回答):

    • 有项目:“做过基于AI的图片识别小程序(如车牌识别),难点是识别准确率低→解决方案:对接百度AI接口,优化图片预处理(压缩、转正),准确率从80%提升到95%;另外是接口响应慢→添加加载动画,优化用户体验”;
    • 无项目:“暂时没有直接做过AI项目,但了解过AI相关技术(如大模型API对接),学习过如何在小程序中集成ChatGPT API,难点是接口限流→解决方案:添加请求队列,缓存常用回答”。

    总结

  • 核心重点:JS基础(数据类型、原型链、深拷贝、Promise、防抖节流)、CSS布局(Flex、BFC、居中、重绘回流)是必考点,需熟练手写核心代码(如深拷贝、防抖节流);
  • 框架重点:Vue响应式原理、组件通信、Vue2/Vue3差异,结合小程序场景(如rpx适配、虚拟列表)作答;
  • 场景重点:跨域解决、长列表优化、加载速度优化是小程序开发高频场景,需掌握落地方法并能解释底层逻辑;
  • 面试技巧:项目问题结合“问题-原因-方案-效果”作答,体现解决问题的能力;技术问题先讲原理,再举示例,最后说应用场景。
  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 微信小程序开发岗前端面试题(含答案)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!