前端场景题

202 阅读7分钟

Q1:说说微信小程序登录流程

  1. 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
  2. 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key

之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

登录时序图

image.png

微信官方文档链接:developers.weixin.qq.com/miniprogram…

Q2:vue响应式原理中,为什么使用 Reflect

this绑定问题

const obj = {
  firstName: "李",
  lastName: "二狗",
  get getFullName() {
      return this.firstName + this.lastName
  }
}
const proxyObj = new Proxy(obj, {
  get(target, key, receiver) {
    console.log(`get:${key}`)
    return target[key] // 调用一次get
    // return Reflect.get(target, key, receiver)//调用三次get
  }
})
console.log(proxyObj.getFullName)

由于target为原obj对象,使用在访问getFullName属性时,它的this指向obj,而非代理对象proxyObj,则会导致在访问firstName和lastName时,无法触发proxyObj代理对象的get。

Q3:饿了么组件 el-table 一次性接收两百条数据会造成卡顿,如何优化?

思路:虚拟列表

数据一共有两百条,全部一次性渲染dom元素过多,对性能造成一定的影响,通过只渲染用户可视区域的列表元素,可以极大的减少dom数量。

image.png

如图所示,加入缓冲区域可使页面更加丝滑

主要步骤如下:

  1. 将盒子高度设置为两百条数据的高度(其父盒子被撑开,产生滚动条)
  2. 通过计算属性等,将两百条数组进行截取(开始下标根据scrollTop的值计算,长度可自定义,如10条)
  3. 监听scroll事件,动态改变开始下标,触发计算属性改变,改变页面内容(可视区域的translateY由scrollTop决定)

主要代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div class="box" @scroll="fnn">
      <ul class="content" :style="{height: 200*10 + 'px'}">
        <li v-for="item in _list" :style="{transform: `translateY(${item*30 + 'px'})`}" >
          NO {{item}}
        </li>
      </ul>
    </div>
  </div>

<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
  
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        list: [],
        startIdx: 0,
      };
    },
    methods: {
      fnn(e) {
        this.startIdx = Math.floor(e.target.scrollTop / 30)
      }
    },
    created() {
      for(var i = 0 ; i < 200; i++) {
        this.list.push(i)
      }
    },
    computed: {
      _list() {
        return this.list.slice(this.startIdx,this.startIdx + 8)
      }
    }
  })
</script>
  
  <style scoped>
    .box {
      width: 200px;
      height: 300px;

      margin: 100px auto;
      border: 1px solid skyblue;
      overflow: auto;
    }
    .content {
      margin: 0;
      padding: 0;
      list-style: none;
      padding: 2px 3px;
    }
    li {
      text-align: center;
      border: 1px solid hotpink;
      height: 30px;
      line-height: 30px;
      margin-bottom: 3px;
    }
  </style>
    
  
</body>
</html>

Q4:微信小程序的订阅消息流程

微信小程序订阅消息的实现流程主要包括以下几个步骤:

  1. 获取模板ID

    • 登录微信公众平台(mp.weixin.qq.com),找到“订阅消息”功能。,-mw3bh4663hv2ai42htze8s2bg46c4e5aut6a./)
    • 如果平台已提供合适的默认模板,则直接选用;否则,需要申请自定义模板。
    • 申请自定义模板时,需填写模板内容并等待审核,审核通过后即可获得模板ID。
  2. 获取下发权限

    • 在小程序端,当用户触发某个行为(如点击按钮)时,调用wx.requestSubscribeMessage接口请求用户订阅该消息。
    • 用户同意后,小程序会获得下发该消息的权限。
    • 注意:这个权限是用户主动选择的,不能强制用户订阅。
  3. 调用接口下发订阅消息

    • 在服务端,使用subscribeMessage.send接口向用户下发订阅消息。

    • 下发消息时需要提供以下参数:

      • touser:用户的openid,通过wx.login等接口获取。
      • template_id:之前获取的模板ID。
      • page(可选):用户点击消息卡片后打开的小程序页面,如pages/index/index
      • miniprogram_state(可选):小程序的状态,如developer(开发版)。
      • lang(可选):推送语言,如zh_CN
      • data:消息模板中的数据,按照模板中的字段填写。
    • 在发送消息之前,需要先获取access_token,这通常是通过小程序的appidappsecret向微信服务器请求得到的。

  4. 注意事项

    • 订阅消息的下发需要在用户同意订阅后才能进行,不能强制推送。
    • 订阅消息的下发频率和内容需要遵守微信平台的规定,避免过度打扰用户。
    • 在开发过程中,需要注意处理各种异常情况,如用户拒绝订阅、网络错误等。

Q5:如何将线性的组织架构转换成树形?

实现方式很多种,这里主要用到递归。(会了又忘……)

// 数据准备
const linearStructure = [
  { id: 1, name: '总经理', parentId: null },
  { id: 2, name: '财务总监', parentId: 1 },
  { id: 3, name: '技术总监', parentId: 1 },
  { id: 4, name: '工程部副总', parentId: 3 },
  { id: 5, name: '软件开发部经理', parentId: 4 },
  { id: 6, name: '软件工程师', parentId: 5 },
  { id: 7, name: '质量保证部经理', parentId: 4 },
  { id: 8, name: '质量保证工程师', parentId: 7 },
  { id: 9, name: '销售总监', parentId: 3 },
  { id: 10, name: '销售经理', parentId: 9 },
  { id: 11, name: '销售团队', parentId: 10 }
];

function buildTree(arr, parentId = null) {
  let res = []
  for (let item of arr) {
    if (item.parentId === parentId) {
      let child = buildTree(arr, item.id)
      if(child.length){
        item.children = child;
      }
      res.push(item)
    }
  }
  return res
}

console.log(buildTree(linearStructure))

image.png

Q6:不同图片类型的处理方式

场景:从A系统获取并处理图片,并通过图片文件的形式传给B系统

现分析两种格式的图片处理成文件

Base64

  1. 使用正则处理Base64图片数据
  2. 使用Buffer将其处理为二进制文件
  3. 通过fs模块写入指定目录
// 解码 Base64 图片数据
const base64Data = imgSrc.replace(/^data:image/\w+;base64,/, '');
const buffer = Buffer.from(base64Data, 'base64');

// 创建一个唯一的文件名
const fileName = `man.png`;

// 指定保存图片的目录
const uploadDir = path.join(__dirname, 'uploads');

// 如果目录不存在,创建它
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}

// 构建完整的文件路径
const filePath = path.join(uploadDir, fileName);

// 将数据写入文件
fs.writeFileSync(filePath, buffer);

URL

  1. 发请求下载图片,请求type为流的形式
  2. 创建一个可写流,将请求的流对象通过管道写入可写流
  3. 监听写入过程,处理完成时逻辑
const response = await axios({
  url: imgSrc,
  responseType: 'stream',
});
const uploadDir = path.join(__dirname, 'uploads');
// 如果目录不存在,创建它
if (!fs.existsSync(uploadDir)) {
  fs.mkdirSync(uploadDir);
}
// 构建完整的文件路径
const imgPath = path.join(uploadDir, `user_${userId}.jpg`);
const imgStream = fs.createWriteStream(imgPath);

response.data.pipe(imgStream);

imgStream.on('finish', async () => {
    // TODO
});

Q7:首屏加载慢,如何优化?

1. 减少首屏内容的加载时间

  • 优先加载首屏内容:确保首屏显示所需的HTML、CSS和JavaScript优先加载。
  • 精简首屏HTML:尽量减少首屏内容的HTML代码量,移除不必要的标签和内容。

2. 优化CSS

  • 内联关键CSS:将首屏所需的关键CSS直接内联在HTML中,以减少请求次数。
  • 异步加载非关键CSS:将非首屏关键的CSS异步加载,避免阻塞页面渲染。
  • 压缩和合并CSS:压缩CSS文件,并将多个CSS文件合并以减少请求数。

3. 优化JavaScript

  • 异步加载JavaScript:对于不影响首屏渲染的JavaScript,使用asyncdefer属性来异步加载。
  • 减少JavaScript大小:通过压缩和混淆JavaScript文件,减少文件大小。
  • 延迟加载:延迟加载不必要的JavaScript代码,直到用户需要时再加载。

4. 优化图片和媒体资源

  • 延迟加载图片:使用懒加载技术,延迟加载首屏外的图片和视频。
  • 压缩图片:使用适当的图片格式和压缩技术,减少图片文件大小。
  • 使用CDN:将静态资源托管在内容分发网络(CDN)上,以加速资源加载。

5. 启用服务器端渲染(SSR)

如果你使用的是单页应用(SPA),考虑启用服务器端渲染,这样可以在服务器上预先渲染首屏内容,减少客户端的渲染时间。

6. 缓存策略

  • 利用浏览器缓存:为静态资源设置合适的缓存策略,以减少重复加载时间。
  • 启用HTTP/2:如果服务器支持,启用HTTP/2可以同时加载多个资源,提高加载速度。

7. 减少第三方资源

减少页面上不必要的第三方资源请求,如第三方库、广告、跟踪脚本等。

8. 分析和监控

  • 使用性能分析工具:如Google Lighthouse、WebPageTest等工具,分析页面加载性能,找出瓶颈。
  • 监控用户体验:持续监控用户的加载体验,收集和分析性能数据,以便持续优化。

Q8:element ui 中 select 组件搜索时,默认通过 name 匹配,如何修改为通过 name 或 id 匹配?

Element UI 的<el-select>组件有一个filter-method属性,它可以接受一个自定义的过滤函数。通过这个属性,我们可以实现更复杂的搜索逻辑。

步骤

  1. 定义自定义过滤函数:这个函数将会根据nameid进行匹配。
  2. 将自定义过滤函数绑定到<el-select>组件

示例代码

假设我们有以下数据结构:

const options = [
  { id: '1', name: 'Option 1' },
  { id: '2', name: 'Option 2' },
  { id: '3', name: 'Option 3' },
  // 更多选项
];

我们希望通过nameid进行搜索匹配,可以按以下方式实现:

<template>
  <div>
    <el-select
      v-model="selectedOption"
      filterable
      :filter-method="customFilter"
      placeholder="Select an option"
    >
      <el-option
        v-for="option in options"
        :key="option.id"
        :label="option.name"
        :value="option.id"
      ></el-option>
    </el-select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedOption: '',
      options: [
        { id: '1', name: 'Option 1' },
        { id: '2', name: 'Option 2' },
        { id: '3', name: 'Option 3' },
        // 更多选项
      ],
    };
  },
  methods: {
    customFilter(queryString, option) {
      // 使用name和id进行匹配
      return (
        option.label.toLowerCase().includes(queryString.toLowerCase()) ||
        option.value.toLowerCase().includes(queryString.toLowerCase())
      );
    },
  },
};
</script>

说明

  1. filterable:使<el-select>组件可过滤。
  2. filter-method:绑定自定义的过滤函数customFilter
  3. customFilter函数:在该函数中,我们根据queryStringoption.label(即name)和option.value(即id)进行匹配。如果queryStringnameid中存在,函数将返回true,否则返回false