[持续更新] 前端跨坑手册

281 阅读6分钟

记录一些曾经遇到的前端问题, 内容很杂, 也欢迎评论区提出问题, 一起讨论, 查漏补缺

问题列表

  1. html2canvas 合成长图片显示不全
  2. html2canvas iOS合成后返回data:
  3. 页面元素上下层遮挡导致点击失效的问题
  4. 多行显示省略号
  5. Vue子组件更新问题(使用slot插槽)
  6. 微信“关怀模式”造成的字体错乱问题
  7. Vue BusEvent问题
  8. JSON深拷贝问题
  9. window.history骚操作
  10. Vant UI使用问题
  11. 微信环境下的异步复制问题

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('...') 订阅事件时,事件被多次触发

原因

  1. 订阅未被正确取消:如果在B页面中使用了 $on 方法订阅了事件,但没有在组件销毁时使用 $off 方法取消订阅,那么每次进入该组件时都会创建新的订阅。这可能导致事件被多次触发。
  2. 监听事件的组件被多次创建:如果组件被多次创建,每个组件都会创建自己的订阅,导致同一事件被多次触发。
  3. 事件名称相同,但传递的数据不同:如果在A页面使用 $emit 方法时,每次传递的数据不同,那么每次触发事件时都会传递不同的数据。如果B页面中的订阅没有考虑到这一点,就可能导致触发多次事件。

解决方案

  1. 在B页面组件销毁时,使用 $off 方法取消事件订阅,以确保每个组件只订阅一次事件。
  2. 确保组件只被创建一次,可以使用 v-if 或者 v-show 等指令来控制组件的显示和隐藏,以避免组件被多次创建。
  3. 在B页面中的订阅事件时,考虑到传递的数据可能不同,可以使用 $on 方法的第二个参数来过滤事件触发。比如:bus.$on('event', (data) => { if (data === expectedData) { // do something } })。这样只有当传递的数据与期望的数据相同时,才会触发相应的操作。

JSON深拷贝问题

原因

  1. 函数和undefined类型不能被序列化为JSON格式,因此无法进行深拷贝。
  2. JSON.stringify()方法将JavaScript对象序列化为JSON字符串,然后通过JSON.parse()方法将JSON字符串转换回JavaScript对象。然而,这种方法无法处理对象中的循环引用。如果一个对象引用了自身,或者对象A引用了对象B,对象B又引用了对象A,则JSON.stringify()JSON.parse()将无法正确处理这些循环引用。
  3. JSON.stringify()JSON.parse()方法只能序列化对象中的可枚举属性。如果对象中有不可枚举的属性,比如Symbol属性,那么这些属性将会在序列化和反序列化过程中丢失。
  4. 在序列化和反序列化过程中,日期对象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真有你的!

e1b1f040491f84d46161173eea153df4.jpg