vue前端面试题——记录一次面试当中遇到的题(4)
目录
1.小程序打包时会进行分包处理吗?
一、为什么要进行分包?
二、实际项目中的分包策略
三、独立分包的使用场景
四、分包开发的最佳实践
五、性能监控与优化
六、实际项目案例
七、遇到的挑战与解决方案
2.同套代码怎么区分小程序和App的实现方案
一、构建时环境判断
二、运行时平台检测
三、核心模块的平台适配
四、组件层面的平台适配
五、项目架构组织
六、构建配置和打包策略
七、实际项目案例
1.小程序打包时会进行分包处理吗?
分包是一个非常重要的性能优化手段。我不仅使用过,还根据不同的业务场景制定了相应的分包策略。下面我从几个方面来详细说明:
一、为什么要进行分包?
1. 解决主包体积限制
小程序官方限制: - 整个小程序所有分包大小不超过 20M - 单个分包/主包大小不能超过 2M - 主包大小直接影响小程序的启动速度
2. 优化启动性能
-
主包体积越小,下载和解包速度越快
-
用户首次打开时只加载主包,按需加载分包
-
提升首屏加载速度和用户体验
3. 业务模块化
-
不同团队可以独立开发不同分包
-
便于代码维护和功能迭代
-
支持按功能模块独立更新
二、实际项目中的分包策略
1. 按业务功能划分
// 电商小程序的分包策略
{"subpackages": [// 商品模块分包{"root": "packageGoods","pages": ["pages/goods/list","pages/goods/detail", "pages/goods/search","pages/goods/category"]},// 订单模块分包{"root": "packageOrder", "pages": ["pages/order/list","pages/order/detail","pages/order/confirm","pages/order/refund"]},// 用户中心分包{"root": "packageUser","pages": ["pages/user/profile","pages/user/address","pages/user/coupon","pages/user/favorite"]},// 营销活动分包(可独立更新){"root": "packagePromotion","pages": ["pages/promotion/seckill","pages/promotion/groupon","pages/promotion/coupon"]}]
}
2. 预加载优化策略
// app.json 中的预加载配置
{"preloadRule": {// 首页预加载商品和订单分包"pages/index/index": {"network": "wifi", // 仅在wifi下预加载"packages": ["packageGoods", "packageOrder"]},// 商品列表页预加载商品详情"pages/goods/list": {"network": "all","packages": ["packageGoods"]},// 购物车预加载订单确认页"pages/cart/index": {"network": "all", "packages": ["packageOrder"]}}
}
三、独立分包的使用场景
1. 独立分包配置
{"subpackages": [{"root": "independentPackage","name": "independent","pages": ["pages/activity/special","pages/activity/limited"],"independent": true // 声明为独立分包}]
}
2. 独立分包的优势
-
可以不依赖主包独立运行
-
独立分包的页面启动速度更快
-
适合营销活动、分享页面等场景
四、分包开发的最佳实践
1. 公共代码处理
// 问题:分包中无法直接引用主包的组件和工具函数
// 解决方案:复制必要文件或使用分包专用版本// packageA/utils/request.js (分包专用)
const request = (url, options) => {// 分包独立的网络请求封装return new Promise((resolve, reject) => {wx.request({url: `https://api.example.com${url}`,...options,success: resolve,fail: reject})})
}export default request
2. 组件复用策略
// 方案一:公共组件放在主包(适合高频使用)
// 方案二:业务组件随分包(适合特定功能)
// 方案三:组件库拆分为独立分包(大型项目)// components/ 主包公共组件
// packageA/components/ 分包专用组件
3. 路由跳转优化
// 分包页面跳转
wx.navigateTo({url: '/packageA/pages/shop/shop?id=123'
})// 使用封装的路由工具
import Router from '../utils/router'Router.navigateTo('shop', { id: 123 }, 'packageA')
五、性能监控与优化
1. 分包大小监控
// 在开发者工具中查看包大小分布
// 或使用构建脚本分析
const fs = require('fs')
const path = require('path')function analyzePackageSize() {const mainPackageSize = calculateDirSize('./miniprogram')const packageASize = calculateDirSize('./miniprogram/packageA')const packageBSize = calculateDirSize('./miniprogram/packageB')console.log(`主包大小: ${(mainPackageSize / 1024 / 1024).toFixed(2)}M`)console.log(`分包A大小: ${(packageASize / 1024 / 1024).toFixed(2)}M`) console.log(`分包B大小: ${(packageBSize / 1024 / 1024).toFixed(2)}M`)
}
2. 加载性能优化
// 监控分包加载时间
const loadStartTime = Date.now()require('../packageA/pages/shop/shop', () => {const loadEndTime = Date.now()console.log(`分包A加载耗时: ${loadEndTime - loadStartTime}ms`)// 上报性能数据wx.reportAnalytics('subpackage_load', {package: 'packageA',duration: loadEndTime - loadStartTime})
})
六、实际项目案例
"在我负责的一个电商小程序项目中,我们遇到了主包体积超过限制的问题。通过实施分包策略:
优化前:
-
主包大小:2.3M(超出限制)
-
首屏加载时间:2.8s
-
所有功能都在主包中
优化后:
// 分包策略
{"pages": ["pages/index/index", // 首页"pages/cart/cart", // 购物车"pages/category/category" // 分类],"subpackages": [{"root": "packageGoods", // 商品相关:1.2M"pages": ["pages/goods/..."]},{"root": "packageOrder", // 订单相关:0.8M "pages": ["pages/order/..."]},{"root": "packageUser", // 用户中心:0.6M"pages": ["pages/user/..."]}],"preloadRule": {"pages/index/index": {"packages": ["packageGoods"]}}
}
优化效果:
-
主包大小:1.2M(符合要求)
-
首屏加载时间:1.5s(提升46%)
-
用户感知性能明显改善"
七、遇到的挑战与解决方案
1. 公共依赖问题
// 问题:多个分包都需要相同的工具函数
// 解决方案:提取到主包或各自维护// 主包 utils/common.js
export const formatTime = (date) => {// 公共时间格式化
}// 或者使用 npm 包管理公共依赖
2. 组件通信问题
// 问题:分包间组件无法直接通信
// 解决方案:使用全局状态管理// stores/userStore.js
class UserStore {constructor() {this.userInfo = null}setUserInfo(info) {this.userInfo = info// 通知所有页面更新this.notifyListeners()}
}// 在主包中初始化,分包通过getApp()访问
2.同套代码怎么区分小程序和App的实现方案
在同套代码中区分小程序和App,我主要采用条件编译 + 平台适配层的架构方案。核心思路是通过构建时和运行时的双重判断,实现代码的智能分发和平台特定逻辑的隔离。
一、构建时环境判断
1. 使用构建工具的条件编译
// 在构建工具中配置环境变量
// vue.config.js 或 webpack.config.js
module.exports = {chainWebpack: config => {config.plugin('define').tap(args => {// 根据构建目标设置全局变量args[0]['process.env.PLATFORM'] = JSON.stringify(process.env.VUE_APP_PLATFORM || 'h5')return args})}
}// package.json 脚本
{"scripts": {"build:mp": "VUE_APP_PLATFORM=mp npm run build","build:app": "VUE_APP_PLATFORM=app npm run build","build:h5": "VUE_APP_PLATFORM=h5 npm run build"}
}
2. 条件编译的多种实现方式
// 方式一:文件后缀条件编译
// 文件结构
src/
├── utils/
│ ├── request.js # 通用逻辑
│ ├── request.mp.js # 小程序特定
│ └── request.app.js # App特定
└── components/├── Image.vue├── Image.mp.vue└── Image.app.vue// 方式二:目录条件编译
src/
├── platforms/
│ ├── mp/ # 小程序平台代码
│ │ ├── utils/
│ │ └── components/
│ ├── app/ # App平台代码
│ └── h5/ # H5平台代码
└── common/ # 通用代码
二、运行时平台检测
1. 统一的平台检测工具
// utils/platform.js
class Platform {constructor() {this._detectPlatform()}_detectPlatform() {// 微信小程序环境if (typeof wx !== 'undefined' && wx.getSystemInfo) {this.platform = 'mp-weixin'this.env = 'miniprogram'}// 支付宝小程序else if (typeof my !== 'undefined' && my.getSystemInfo) {this.platform = 'mp-alipay'this.env = 'miniprogram'}// UniApp 环境else if (typeof uni !== 'undefined') {// #ifdef MP-WEIXINthis.platform = 'mp-weixin'// #endif// #ifdef APP-PLUSthis.platform = 'app'// #endif// #ifdef H5this.platform = 'h5'// #endifthis.env = 'uniapp'}// App 环境 (如 Cordova、Capacitor)else if (window.cordova || window.Capacitor) {this.platform = 'app'this.env = 'hybrid'}// H5 环境else {this.platform = 'h5'this.env = 'web'}}// 平台判断方法isMP() {return this.env === 'miniprogram'}isApp() {return this.platform === 'app'}isH5() {return this.platform === 'h5'}// 具体平台判断isWeixinMP() {return this.platform === 'mp-weixin'}isAlipayMP() {return this.platform === 'mp-alipay'}// 获取平台信息getPlatform() {return this.platform}
}export default new Platform()
2. 在代码中的使用
// 在任何需要区分平台的地方
import platform from '@/utils/platform'// 条件执行
if (platform.isMP()) {// 小程序特定逻辑wx.request({ ... })
} else if (platform.isApp()) {// App特定逻辑nativeBridge.call('api', { ... })
} else {// H5逻辑fetch('/api', { ... })
}// 或者使用策略模式
const strategies = {'mp': () => { /* 小程序实现 */ },'app': () => { /* App实现 */ },'h5': () => { /* H5实现 */ }
}const executePlatformSpecific = () => {const strategy = strategies[platform.getPlatform()]return strategy ? strategy() : strategies.h5()
}
三、核心模块的平台适配
1. 网络请求模块适配
// utils/request.js
import platform from './platform'class Request {constructor() {this.adapter = this._getAdapter()}_getAdapter() {if (platform.isMP()) {return this._mpAdapter()} else if (platform.isApp()) {return this._appAdapter()} else {return this._h5Adapter()}}// 小程序适配器_mpAdapter() {return (config) => {return new Promise((resolve, reject) => {wx.request({url: config.url,method: config.method,data: config.data,header: config.headers,success: (res) => resolve(this._transformResponse(res)),fail: reject})})}}// App适配器 (UniApp示例)_appAdapter() {return (config) => {return new Promise((resolve, reject) => {uni.request({url: config.url,method: config.method,data: config.data,header: config.headers,success: (res) => resolve(this._transformResponse(res)),fail: reject})})}}// H5适配器_h5Adapter() {return (config) => {return fetch(config.url, {method: config.method,body: JSON.stringify(config.data),headers: config.headers}).then(response => response.json())}}// 统一请求方法async request(config) {try {const response = await this.adapter(config)return response} catch (error) {throw this._transformError(error)}}_transformResponse(response) {// 统一响应格式if (platform.isMP() || platform.isApp()) {return {data: response.data,status: response.statusCode,headers: response.header}} else {return response}}
}export default new Request()
2. 存储模块适配
// utils/storage.js
import platform from './platform'class Storage {// 统一存储接口setItem(key, value) {const serializedValue = JSON.stringify(value)if (platform.isMP()) {wx.setStorageSync(key, serializedValue)} else if (platform.isApp()) {// UniAppuni.setStorageSync(key, serializedValue)// 或原生App// localStorage.setItem(key, serializedValue)} else {localStorage.setItem(key, serializedValue)}}getItem(key) {let valueif (platform.isMP()) {value = wx.getStorageSync(key)} else if (platform.isApp()) {value = uni.getStorageSync(key)} else {value = localStorage.getItem(key)}try {return value ? JSON.parse(value) : null} catch {return value}}removeItem(key) {if (platform.isMP()) {wx.removeStorageSync(key)} else if (platform.isApp()) {uni.removeStorageSync(key)} else {localStorage.removeItem(key)}}
}export default new Storage()
3. 导航路由适配
// utils/router.js
import platform from './platform'class Router {// 统一跳转方法navigateTo(url, params = {}) {const queryString = this._buildQueryString(params)const targetUrl = queryString ? `${url}?${queryString}` : urlif (platform.isMP()) {wx.navigateTo({ url: targetUrl })} else if (platform.isApp()) {uni.navigateTo({ url: targetUrl })} else {// H5使用Vue Router或window.locationwindow.location.href = targetUrl}}// 返回上一页navigateBack() {if (platform.isMP()) {wx.navigateBack()} else if (platform.isApp()) {uni.navigateBack()} else {window.history.back()}}// 重定向redirectTo(url, params = {}) {const queryString = this._buildQueryString(params)const targetUrl = queryString ? `${url}?${queryString}` : urlif (platform.isMP()) {wx.redirectTo({ url: targetUrl })} else if (platform.isApp()) {uni.redirectTo({ url: targetUrl })} else {window.location.replace(targetUrl)}}_buildQueryString(params) {return Object.keys(params).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join('&')}
}export default new Router()
四、组件层面的平台适配
1. 平台特定组件
<!-- components/PlatformImage.vue -->
<template><!-- 小程序使用 image 组件 --><image v-if="platform.isMP()" :src="src" :mode="mode"@load="handleLoad"@error="handleError"/><!-- App使用 uni-app 的 image --><uni-imagev-else-if="platform.isApp()":src="src":mode="mode"@load="handleLoad"@error="handleError"/><!-- H5使用 img 标签 --><imgv-else:src="src":class="imgClass"@load="handleLoad"@error="handleError"/>
</template><script setup>
import { computed } from 'vue'
import platform from '@/utils/platform'const props = defineProps({src: String,mode: {type: String,default: 'scaleToFill'}
})const emit = defineEmits(['load', 'error'])// 转换模式为CSS object-fit
const imgClass = computed(() => {const modeMap = {'scaleToFill': 'object-fill','aspectFit': 'object-contain','aspectFill': 'object-cover'}return modeMap[props.mode] || 'object-fill'
})const handleLoad = (event) => {emit('load', event)
}const handleError = (error) => {emit('error', error)
}
</script>
2. 条件编译在Vue组件中的使用
<template><div class="container"><!-- 通用内容 --><h1>{{ title }}</h1><!-- 平台特定内容 --><div v-if="platform.isMP()" class="mp-specific"><button open-type="share">分享</button></div><div v-else-if="platform.isApp()" class="app-specific"><button @click="nativeShare">原生分享</button></div><div v-else class="h5-specific"><button @click="webShare">Web分享</button></div><!-- 平台特定的插槽内容 --><slot name="mp-content" v-if="platform.isMP()"></slot><slot name="app-content" v-else-if="platform.isApp()"></slot><slot name="h5-content" v-else></slot></div>
</template><script setup>
import { ref, onMounted } from 'vue'
import platform from '@/utils/platform'const title = ref('多平台应用')// 平台特定的生命周期
onMounted(() => {if (platform.isMP()) {// 小程序特定的初始化initMP()} else if (platform.isApp()) {// App特定的初始化initApp()} else {// H5特定的初始化initH5()}
})// 平台特定的方法
const initMP = () => {// 小程序API调用// #ifdef MP-WEIXINwx.showShareMenu({ withShareTicket: true })// #endif
}const initApp = () => {// App原生功能初始化if (window.cordova) {document.addEventListener('deviceready', onDeviceReady, false)}
}const initH5 = () => {// H5特定的初始化if (navigator.share) {// 支持Web Share API}
}// 分享功能 - 平台特定实现
const handleShare = () => {if (platform.isMP()) {mpShare()} else if (platform.isApp()) {nativeShare()} else {webShare()}
}const mpShare = () => {// 小程序分享逻辑
}const nativeShare = () => {// App原生分享// #ifdef APP-PLUSplus.share.sendWithSystem({ content: '分享内容' })// #endif
}const webShare = () => {// H5分享if (navigator.share) {navigator.share({title: '分享标题',text: '分享内容',url: window.location.href})}
}
</script><style scoped>
/* 通用样式 */
.container {padding: 20rpx;
}/* 平台特定样式 */
/* #ifdef MP-WEIXIN */
.mp-specific {color: #07C160;
}
/* #endif *//* #ifdef APP-PLUS */
.app-specific {color: #007AFF;
}
/* #endif *//* #ifdef H5 */
.h5-specific {color: #1890FF;
}
/* #endif */
</style>
五、项目架构组织
1. 多平台项目结构
src/
├── platforms/ # 平台特定代码
│ ├── mp/ # 小程序平台
│ │ ├── components/ # 小程序特有组件
│ │ ├── utils/ # 小程序工具函数
│ │ └── apis/ # 小程序API封装
│ ├── app/ # App平台
│ └── h5/ # H5平台
├── common/ # 通用代码
│ ├── components/ # 跨平台组件
│ ├── utils/ # 通用工具函数
│ ├── styles/ # 通用样式
│ └── constants/ # 常量定义
├── services/ # 业务服务层
└── stores/ # 状态管理
2. 平台适配器模式
// common/services/BaseService.js
export class BaseService {constructor(platformAdapter) {this.adapter = platformAdapter}// 通用业务方法async fetchData(params) {// 预处理const processedParams = this.preProcess(params)// 调用平台特定的网络请求const response = await this.adapter.request({url: this.getApiUrl(),data: processedParams})// 后处理return this.postProcess(response)}// 由子类实现平台特定方法getApiUrl() {throw new Error('子类必须实现 getApiUrl 方法')}preProcess(params) { return params }postProcess(response) { return response }
}// platforms/mp/services/UserService.js
import { BaseService } from '@/common/services/BaseService'
import { mpAdapter } from '@/platforms/mp/adapters/requestAdapter'export class UserService extends BaseService {constructor() {super(mpAdapter)}getApiUrl() {return '/api/user'}// 小程序特定的预处理preProcess(params) {return {...params,appid: wx.getAccountInfoSync().miniProgram.appId}}
}// 工厂函数创建服务实例
export function createUserService() {if (platform.isMP()) {return new (require('@/platforms/mp/services/UserService').UserService)()} else if (platform.isApp()) {return new (require('@/platforms/app/services/UserService').UserService)()} else {return new (require('@/platforms/h5/services/UserService').UserService)()}
}
六、构建配置和打包策略
1. 多平台构建配置
// vue.config.js
const PlatformConfig = {mp: {css: {extract: false},configureWebpack: {output: {filename: 'static/js/[name].js',chunkFilename: 'static/js/[name].js'}}},app: {configureWebpack: {output: {filename: 'static/js/[name].[hash].js',chunkFilename: 'static/js/[name].[hash].js'}}},h5: {// H5特定配置}
}module.exports = {chainWebpack: config => {const platform = process.env.VUE_APP_PLATFORM// 平台特定的入口文件if (platform === 'mp') {config.entry('app').clear().add('./src/platforms/mp/main.js')} else if (platform === 'app') {config.entry('app').clear().add('./src/platforms/app/main.js')}// 平台特定的别名配置config.resolve.alias.set('@platform', `@/platforms/${platform}`)},// 合并平台特定配置...PlatformConfig[process.env.VUE_APP_PLATFORM] || {}
}
七、实际项目案例
"在我负责的一个电商项目中,我们使用这套方案成功实现了小程序、App、H5三端代码的统一管理:
项目结构:
src/
├── common/ # 85% 通用代码
│ ├── components/ # 业务组件
│ ├── services/ # API服务
│ └── utils/ # 工具函数
├── platforms/
│ ├── mp/ # 8% 小程序特定代码
│ ├── app/ # 5% App特定代码
│ └── h5/ # 2% H5特定代码
└── entries/ # 多入口文件
关键实现:
-
统一的平台检测工具,准确识别运行环境
-
适配器模式封装平台差异,如网络请求、存储、导航
-
条件编译处理平台特定的UI和交互
-
构建配置实现按平台打包,减少包体积