vue前端面试题——记录一次面试当中遇到的题(4)

目录

1.小程序打包时会进行分包处理吗?

一、为什么要进行分包?

二、实际项目中的分包策略

三、独立分包的使用场景

四、分包开发的最佳实践

五、性能监控与优化

六、实际项目案例

七、遇到的挑战与解决方案

2.同套代码怎么区分小程序和App的实现方案

一、构建时环境判断

二、运行时平台检测

三、核心模块的平台适配

四、组件层面的平台适配

五、项目架构组织

六、构建配置和打包策略

七、实际项目案例


1.小程序打包时会进行分包处理吗?

分包是一个非常重要的性能优化手段。我不仅使用过,还根据不同的业务场景制定了相应的分包策略。下面我从几个方面来详细说明:

一、为什么要进行分包?

1. 解决主包体积限制

复制代码
小程序官方限制:
- 整个小程序所有分包大小不超过 20M
- 单个分包/主包大小不能超过 2M
- 主包大小直接影响小程序的启动速度

2. 优化启动性能

  • 主包体积越小,下载和解包速度越快

  • 用户首次打开时只加载主包,按需加载分包

  • 提升首屏加载速度和用户体验

3. 业务模块化

  • 不同团队可以独立开发不同分包

  • 便于代码维护和功能迭代

  • 支持按功能模块独立更新

二、实际项目中的分包策略

1. 按业务功能划分

javascript 复制代码
// 电商小程序的分包策略
{
  "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. 预加载优化策略

javascript 复制代码
// 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. 独立分包配置

javascript 复制代码
{
  "subpackages": [
    {
      "root": "independentPackage",
      "name": "independent",
      "pages": [
        "pages/activity/special",
        "pages/activity/limited"
      ],
      "independent": true  // 声明为独立分包
    }
  ]
}

2. 独立分包的优势

  • 可以不依赖主包独立运行

  • 独立分包的页面启动速度更快

  • 适合营销活动、分享页面等场景

四、分包开发的最佳实践

1. 公共代码处理

javascript 复制代码
// 问题:分包中无法直接引用主包的组件和工具函数
// 解决方案:复制必要文件或使用分包专用版本

// 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. 组件复用策略

javascript 复制代码
// 方案一:公共组件放在主包(适合高频使用)
// 方案二:业务组件随分包(适合特定功能)
// 方案三:组件库拆分为独立分包(大型项目)

// components/ 主包公共组件
// packageA/components/ 分包专用组件

3. 路由跳转优化

javascript 复制代码
// 分包页面跳转
wx.navigateTo({
  url: '/packageA/pages/shop/shop?id=123'
})

// 使用封装的路由工具
import Router from '../utils/router'

Router.navigateTo('shop', { id: 123 }, 'packageA')
五、性能监控与优化

1. 分包大小监控

javascript 复制代码
// 在开发者工具中查看包大小分布
// 或使用构建脚本分析
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. 加载性能优化

javascript 复制代码
// 监控分包加载时间
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

  • 所有功能都在主包中

优化后:

javascript 复制代码
// 分包策略
{
  "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. 公共依赖问题

javascript 复制代码
// 问题:多个分包都需要相同的工具函数
// 解决方案:提取到主包或各自维护

// 主包 utils/common.js
export const formatTime = (date) => {
  // 公共时间格式化
}

// 或者使用 npm 包管理公共依赖

2. 组件通信问题

javascript 复制代码
// 问题:分包间组件无法直接通信
// 解决方案:使用全局状态管理

// stores/userStore.js
class UserStore {
  constructor() {
    this.userInfo = null
  }
  
  setUserInfo(info) {
    this.userInfo = info
    // 通知所有页面更新
    this.notifyListeners()
  }
}

// 在主包中初始化,分包通过getApp()访问
2.同套代码怎么区分小程序和App的实现方案

在同套代码中区分小程序和App,我主要采用条件编译 + 平台适配层的架构方案。核心思路是通过构建时和运行时的双重判断,实现代码的智能分发和平台特定逻辑的隔离。

一、构建时环境判断

1. 使用构建工具的条件编译

javascript 复制代码
// 在构建工具中配置环境变量
// 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. 条件编译的多种实现方式

javascript 复制代码
// 方式一:文件后缀条件编译
// 文件结构
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. 统一的平台检测工具

javascript 复制代码
// 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-WEIXIN
      this.platform = 'mp-weixin'
      // #endif
      // #ifdef APP-PLUS
      this.platform = 'app'
      // #endif
      // #ifdef H5
      this.platform = 'h5'
      // #endif
      this.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. 在代码中的使用

javascript 复制代码
// 在任何需要区分平台的地方
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. 网络请求模块适配

javascript 复制代码
// 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. 存储模块适配

javascript 复制代码
// 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()) {
      // UniApp
      uni.setStorageSync(key, serializedValue)
      // 或原生App
      // localStorage.setItem(key, serializedValue)
    } else {
      localStorage.setItem(key, serializedValue)
    }
  }
  
  getItem(key) {
    let value
    
    if (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. 导航路由适配

javascript 复制代码
// utils/router.js
import platform from './platform'

class Router {
  // 统一跳转方法
  navigateTo(url, params = {}) {
    const queryString = this._buildQueryString(params)
    const targetUrl = queryString ? `${url}?${queryString}` : url
    
    if (platform.isMP()) {
      wx.navigateTo({ url: targetUrl })
    } else if (platform.isApp()) {
      uni.navigateTo({ url: targetUrl })
    } else {
      // H5使用Vue Router或window.location
      window.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}` : url
    
    if (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. 平台特定组件

javascript 复制代码
<!-- components/PlatformImage.vue -->
<template>
  <!-- 小程序使用 image 组件 -->
  <image 
    v-if="platform.isMP()" 
    :src="src" 
    :mode="mode"
    @load="handleLoad"
    @error="handleError"
  />
  
  <!-- App使用 uni-app 的 image -->
  <uni-image
    v-else-if="platform.isApp()"
    :src="src"
    :mode="mode"
    @load="handleLoad"
    @error="handleError"
  />
  
  <!-- H5使用 img 标签 -->
  <img
    v-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组件中的使用

javascript 复制代码
<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-WEIXIN
  wx.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-PLUS
  plus.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. 多平台项目结构

javascript 复制代码
src/
├── platforms/           # 平台特定代码
│   ├── mp/             # 小程序平台
│   │   ├── components/ # 小程序特有组件
│   │   ├── utils/      # 小程序工具函数
│   │   └── apis/       # 小程序API封装
│   ├── app/            # App平台
│   └── h5/             # H5平台
├── common/              # 通用代码
│   ├── components/     # 跨平台组件
│   ├── utils/          # 通用工具函数
│   ├── styles/         # 通用样式
│   └── constants/      # 常量定义
├── services/           # 业务服务层
└── stores/             # 状态管理

2. 平台适配器模式

javascript 复制代码
// 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. 多平台构建配置

javascript 复制代码
// 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三端代码的统一管理:

项目结构:

javascript 复制代码
src/
├── common/              # 85% 通用代码
│   ├── components/     # 业务组件
│   ├── services/       # API服务
│   └── utils/          # 工具函数
├── platforms/
│   ├── mp/            # 8% 小程序特定代码
│   ├── app/           # 5% App特定代码  
│   └── h5/            # 2% H5特定代码
└── entries/            # 多入口文件

关键实现:

  1. 统一的平台检测工具,准确识别运行环境

  2. 适配器模式封装平台差异,如网络请求、存储、导航

  3. 条件编译处理平台特定的UI和交互

  4. 构建配置实现按平台打包,减少包体积

相关推荐
Ratten2 小时前
【uniapp】---- 在 uniapp 实现 app 的版本检查、下载最新版本、自动安装
前端
立方世界3 小时前
HTML编写规则及性能优化深度解析:从基础到企业级实践
前端·性能优化·html
JarvanMo3 小时前
请停止用 Java 习惯来破坏你的 Flutter 代码
前端
道可到3 小时前
阿里面试原题 java面试直接过06 | 集合底层——HashMap、ConcurrentHashMap、CopyOnWriteArrayList
java·后端·面试
超级神性造梦机器3 小时前
当“提示词工程”过时,谁来帮开发者管好 AI 的“注意力”?
前端·后端
被巨款砸中3 小时前
Jessibuca 播放器
前端·javascript·vue.js·web
吃饺子不吃馅3 小时前
小明问:要不要加入创业公司?
前端·面试·github
不渡_3 小时前
Web项目-版本号
前端·javascript
Asort3 小时前
JavaScript设计模式(十一):享元模式(Flyweight) - 优化内存与性能的利器
前端·javascript·设计模式