记录一些曾经遇到的前端问题, 内容很杂, 也欢迎评论区提出问题, 一起讨论, 查漏补缺
问题列表
html2canvas合成长图片显示不全html2canvasiOS合成后返回data:- 页面元素上下层遮挡导致点击失效的问题
- 多行显示省略号
- Vue子组件更新问题(使用slot插槽)
- 微信“关怀模式”造成的字体错乱问题
- Vue BusEvent问题
- JSON深拷贝问题
window.history骚操作- Vant UI使用问题
- 微信环境下的异步复制问题
html2canvas 合成长图片显示不全
根本原因: html2canvas组件问题, 页面不在顶部时, 对于偏移量的计算有问题
解决方案: window.scrollTo(0 ,0)
在合成前将页面滚动到最上方
html2canvas iOS合成后返回data:
根本原因: 合成返回的图片过大或者没有给定需要合成元素的宽高
解决方案: 设置scala最大为2, 在待合成的元素完成渲染后再进行合成操作
在合成前将页面滚动到最上方
页面元素上下层遮挡导致点击失效的问题
解决方案: 为被遮挡元素上层使用z-index属性的元素添加以下样式:
pointer-events: none;
这样点击事件就能穿透上层元素,可点击到被遮挡元素,但是此时,上层元素无法响应点击事件
然后为被遮挡元素添加以下样式,让上层元素可以响应点击事件(仅让被遮挡元素自身可以响应点击事件):
pointer-events: auto;
多行显示省略号
解决方案:
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2; //行数
-webkit-box-orient: vertical;
Vue子组件更新问题(使用slot插槽)
根本原因: 父组件的数据发生了变化,但子组件没有及时响应。 解决方案: 给子组件传入:key, 在父组件中维护key值
<my-component :data="rawData" :key="key"></my-component>
当rawData更新时, 手动更新key
衍生问题: 父组件的数据发生了变化,不想更新子组件视图
解决方案: 使用v-once指令
<my-component :data="rawData" v-once></my-component>
在这个例子中,my-component组件只会渲染一次,即使data属性的值发生变化。这可以避免不必要的更新。
微信浏览器环境下, 微信“关怀模式”造成的字体错乱问题
根本原因: 没做适老化改造, 用户手动设置微信字体大小
解决方案: 强制设置字体大小
document.addEventListener("WeixinJSBridgeReady", function () {
WeixinJSBridge.invoke("setFontSizeCallback", {
fontSize: '2'
});
}, false);
Event Bus问题
当在A页面使用 bus.$emit('...', obj) 方法触发事件时,在B页面使用 bus.$on('...') 订阅事件时,事件被多次触发
原因
- 订阅未被正确取消:如果在B页面中使用了
$on方法订阅了事件,但没有在组件销毁时使用$off方法取消订阅,那么每次进入该组件时都会创建新的订阅。这可能导致事件被多次触发。 - 监听事件的组件被多次创建:如果组件被多次创建,每个组件都会创建自己的订阅,导致同一事件被多次触发。
- 事件名称相同,但传递的数据不同:如果在A页面使用
$emit方法时,每次传递的数据不同,那么每次触发事件时都会传递不同的数据。如果B页面中的订阅没有考虑到这一点,就可能导致触发多次事件。
解决方案
- 在B页面组件销毁时,使用
$off方法取消事件订阅,以确保每个组件只订阅一次事件。 - 确保组件只被创建一次,可以使用
v-if或者v-show等指令来控制组件的显示和隐藏,以避免组件被多次创建。 - 在B页面中的订阅事件时,考虑到传递的数据可能不同,可以使用
$on方法的第二个参数来过滤事件触发。比如:bus.$on('event', (data) => { if (data === expectedData) { // do something } })。这样只有当传递的数据与期望的数据相同时,才会触发相应的操作。
JSON深拷贝问题
原因
- 函数和undefined类型不能被序列化为JSON格式,因此无法进行深拷贝。
JSON.stringify()方法将JavaScript对象序列化为JSON字符串,然后通过JSON.parse()方法将JSON字符串转换回JavaScript对象。然而,这种方法无法处理对象中的循环引用。如果一个对象引用了自身,或者对象A引用了对象B,对象B又引用了对象A,则JSON.stringify()和JSON.parse()将无法正确处理这些循环引用。JSON.stringify()和JSON.parse()方法只能序列化对象中的可枚举属性。如果对象中有不可枚举的属性,比如Symbol属性,那么这些属性将会在序列化和反序列化过程中丢失。- 在序列化和反序列化过程中,日期对象
Date会被转换为字符串。在反序列化时,需要手动将日期字符串转换为日期对象。
解决方案
使用递归方式自己实现一个~
function deepCopy(obj) {
// 如果不是对象或者为 null,直接返回
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 根据对象的类型创建一个新的对象或者数组
const newObj = Array.isArray(obj) ? [] : {};
// 遍历对象的属性并递归复制
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
当然, 这个深拷贝方法还存在很多问题, 比如循环引用问题、函数、Symbol、Map、Set 等类型的处理、对象的属性描述符的处理和性能问题, 因为是递归实现, 如果复制的对象过大, 会导致堆栈溢出.
一个优化方案抛砖引玉:
function deepCopy(obj) {
const stack = [{ source: obj, target: undefined }];
const seen = new WeakMap();
while (stack.length > 0) {
const { source, target } = stack.pop();
if (typeof source !== 'object' || source === null) {
continue;
}
if (seen.has(source)) {
continue;
}
seen.set(source, target);
let newObj;
if (Array.isArray(source)) {
newObj = target || [];
newObj.length = source.length;
for (let i = 0; i < source.length; i++) {
stack.push({ source: source[i], target: newObj[i] });
}
} else {
newObj = target || {};
for (const propName in source) {
if (source.hasOwnProperty(propName)) {
stack.push({ source: source[propName], target: newObj[propName] });
}
}
}
}
return seen.get(obj);
}
如何手动模拟地址栏跳转, 在更改页面的同时, 不触发页面跳转, 且浏览器操作能够正常使用
解决方案
使用 window.history.pushState() 方法来模拟地址栏的跳转,而不触发页面的实际跳转。
// 更改页面 URL 和标题,但不会触发页面跳转
window.history.pushState(null, null, '/new-url');
// To do something..
// 当用户点击“后退”按钮时,返回到旧的 URL
window.addEventListener('popstate', function(event) {
window.history.pushState(null, null, '/old-url');
});
这段代码将当前页面的 URL 更改为 /new-url,并在 popstate 事件监听器中添加了一个回调函数,以便在用户单击“后退”按钮时返回到旧的 URL。
通过使用 pushState() 方法,浏览器操作会保持正常,因为浏览器仍然可以根据 URL 进行导航,而不会刷新页面。
Vant UI使用问题
组件嵌套使用, 事件冒泡异常
在 Vant Popover 组件中嵌套 Vant Field 组件时,可能会出现事件冒泡异常的情况。原因是当在 Field 组件中输入内容时,会触发 input 事件并向上冒泡,导致 Popover 组件也会收到该事件并执行相关逻辑。
<van-popover>
<template #reference>
<van-field v-model="value" @input.stop></van-field>
</template>
<div>{{ value }}</div>
</van-popover>
在上面的示例中,使用了 @input.stop 修饰符来阻止 input 事件的冒泡,确保只有 Field 组件接收到该事件。这样就可以避免 Popover 组件收到该事件并执行相关逻辑的情况。
微信环境下的异步复制问题
当在微信环境中使用异步方法获取数据后复制到剪切板时复制失败
原因
浏览器的安全策略对复制到剪切板的行为进行了控制
解决方案
引入老版JSSDK文件, 使用setClipboardData方法即可.. 该说不说这个文件藏的就很深, 就没想着让开发者用吧... 新版以及老版那几个jweixin文件里也看不到这个方法, WX真有你的!