Q1:说说微信小程序登录流程
- 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
- 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
登录时序图
微信官方文档链接: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数量。
如图所示,加入缓冲区域可使页面更加丝滑
主要步骤如下:
- 将盒子高度设置为两百条数据的高度(其父盒子被撑开,产生滚动条)
- 通过计算属性等,将两百条数组进行截取(开始下标根据scrollTop的值计算,长度可自定义,如10条)
- 监听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:微信小程序的订阅消息流程
微信小程序订阅消息的实现流程主要包括以下几个步骤:
-
获取模板ID:
- 登录微信公众平台(mp.weixin.qq.com),找到“订阅消息”功能。,-mw3bh4663hv2ai42htze8s2bg46c4e5aut6a./)
- 如果平台已提供合适的默认模板,则直接选用;否则,需要申请自定义模板。
- 申请自定义模板时,需填写模板内容并等待审核,审核通过后即可获得模板ID。
-
获取下发权限:
- 在小程序端,当用户触发某个行为(如点击按钮)时,调用
wx.requestSubscribeMessage接口请求用户订阅该消息。 - 用户同意后,小程序会获得下发该消息的权限。
- 注意:这个权限是用户主动选择的,不能强制用户订阅。
- 在小程序端,当用户触发某个行为(如点击按钮)时,调用
-
调用接口下发订阅消息:
-
在服务端,使用
subscribeMessage.send接口向用户下发订阅消息。 -
下发消息时需要提供以下参数:
touser:用户的openid,通过wx.login等接口获取。template_id:之前获取的模板ID。page(可选):用户点击消息卡片后打开的小程序页面,如pages/index/index。miniprogram_state(可选):小程序的状态,如developer(开发版)。lang(可选):推送语言,如zh_CN。data:消息模板中的数据,按照模板中的字段填写。
-
在发送消息之前,需要先获取
access_token,这通常是通过小程序的appid和appsecret向微信服务器请求得到的。
-
-
注意事项:
- 订阅消息的下发需要在用户同意订阅后才能进行,不能强制推送。
- 订阅消息的下发频率和内容需要遵守微信平台的规定,避免过度打扰用户。
- 在开发过程中,需要注意处理各种异常情况,如用户拒绝订阅、网络错误等。
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))
Q6:不同图片类型的处理方式
场景:从A系统获取并处理图片,并通过图片文件的形式传给B系统
现分析两种格式的图片处理成文件
Base64
- 使用正则处理Base64图片数据
- 使用Buffer将其处理为二进制文件
- 通过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
- 发请求下载图片,请求type为流的形式
- 创建一个可写流,将请求的流对象通过管道写入可写流
- 监听写入过程,处理完成时逻辑
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,使用
async或defer属性来异步加载。 - 减少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属性,它可以接受一个自定义的过滤函数。通过这个属性,我们可以实现更复杂的搜索逻辑。
步骤
- 定义自定义过滤函数:这个函数将会根据
name和id进行匹配。 - 将自定义过滤函数绑定到
<el-select>组件。
示例代码
假设我们有以下数据结构:
const options = [
{ id: '1', name: 'Option 1' },
{ id: '2', name: 'Option 2' },
{ id: '3', name: 'Option 3' },
// 更多选项
];
我们希望通过name或id进行搜索匹配,可以按以下方式实现:
<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>
说明
filterable:使<el-select>组件可过滤。filter-method:绑定自定义的过滤函数customFilter。customFilter函数:在该函数中,我们根据queryString对option.label(即name)和option.value(即id)进行匹配。如果queryString在name或id中存在,函数将返回true,否则返回false。