运维 · 2023年11月16日 0

这些天,我们前端组一起处理的项目优化点

来自:掘金,作者:沐浴在曙光下的贰货道士

链接:https://juejin.cn/post/7264440609129119804

前言

很早一段时间,我们前端组做了一些国外系统的小优化(集中表现在代码执行速度,系统安全等方面),故新建一篇文章来记录这次优化的经历。如果本文对您有帮助,烦请大家一键三连哦, 蟹蟹大家~

1、减小文件体积/网络请求

  • 方法一:删除需要预先加载和预先获取的资源,一般使用这种方法

    link标签中的preloadprefetch

    1. preload插件用于预加载资源。 即在当前页面加载完成后,立即加载并缓存指定的资源。预加载的资源被认为是当前页面所需的关键资源,因此会优先下载和缓存。
    2. prefetch插件用于预获取资源。 即在当前页面加载完成后,异步地加载并缓存指定的资源,以供将来可能需要的页面使用。预获取的资源被认为是可能会在未来的导航中使用的资源,但不是当前页面所必需的。
`vue.config.js`

module.exports = {
  chainWebpack(config) => {
    `删除需要预先加载(当前页面)的资源,当需要这些资源的时候,页面会自动加载`
    config.plugins.delete('preload')  
    `删除需要预先获取(将来的页面)的资源`
    config.plugins.delete('prefetch')
  }
}
  • 方法二:使用webpack合并小文件

    合并js代码的意义:

    1. 减少网络请求: 每个文件都需要通过网络进行单独的请求和响应。通过将多个文件合并为一个文件,可以减少页面需要发起的网络请求次数,从而降低延迟和提高加载速度。
    2. 缓存优化: 合并代码可以提高浏览器缓存的效率。当多个页面共享同一个文件时,浏览器只需要下载并缓存一次该文件,而不是针对每个页面都下载一次。这样可以减少整体的重复下载和提高缓存命中率。
    3. 减少页面渲染阻塞: 当浏览器下载和执行js代码时,它会阻塞页面的渲染过程。通过合并js代码,可以减少因为多个js文件的下载和执行而造成的页面渲染阻塞时间,提高页面的响应速度和用户体验。
    4. 代码优化和压缩: 在合并js代码之前,可以对代码进行优化和压缩,去除空格、注释和不必要的代码,从而减少文件大小,并提高代码的执行效率。
`vue.config.js: `

const webpack = require('webpack')
const ENV = process.env.NODE_ENV

module.exports = {
  chainWebpack(config) => {
    config.when(ENV === 'production', config => {
      config.plugin('webpackOptimize')
      .use(
        webpack.optimize.LimitChunkCountPlugin, 
        `限制生成的代码块(chunks)的数量`
        [{ maxChunks10 }]
      )
      .use(
        webpack.optimize.MinChunkSizePlugin, 
        `指定代码块的最小字节数`
        [{ minChunkSize50000 }]
      )
    })
  }
}
  • 优化效果截图

优化前的本地环境:

image.png

优化后的本地环境(app.js文件较大,是由于main.js引入了大量的第三方库):

优化后的线上环境(首先,打包后有对这些文件进行压缩处理,app.js被压缩至1.24MB"inspect": "vue inspect > output.js",使用npm run inspect可以查看webPack的配置信息。其次,运维都有对这些文件作gzip压缩处理, 所以体积都减小了很多, 最终app.js的体积减小至405KB):

image.png

2、加减乘除运算集成`big.js`[1],解决js小数精度问题

  • 前提:

当涉及到浮点数计算时,js中的精度丢失问题, 是由于使用IEEE 754标准来表示和计算浮点数的方式引起的。这个问题不仅仅在js中存在,而是在所有使用IEEE 754标准的编程语言中都会遇到。

IEEE 754标准定义了两种常见的浮点数表示形式:单精度(32位)和双精度(64位)。在 js中,采用的是双精度表示法,即64位。

然而,由于二进制和十进制之间的转换存在差异,某些十进制分数无法精确表示为有限位的二进制浮点数。这导致了舍入误差和精度丢失。

  • 安装依赖: npm install --save big.js
  • 方法封装:
import Big from 'big.js'

export function accFactory(method = 'add'{
  return function (...nums{
    `将传入的参数转换为Number类型,并过滤掉不是Number类型的结果`
    nums = nums.map(Number).filter((num) => num || num === 0)
    `如果过滤后的结果是长度为1的数组,那就返回数组的第一项`
    `如果过滤后的结果为空数组,则返回0`
    if (nums.length < 2return nums[0] || 0
    `需要为reduce方法赋初值,是因为big.js的运算操作,是基于new Big格式的数字`
    `可以将Big对象转换为浮点数,方便后续Number.toFixed()的操作`
    return parseFloat(nums.slice(1).reduce((prev, num) => prev[method](num), new Big(nums[0]))) || 0
  }
}

`plus、minus、times、div为big.js中的计算方法,分别对应加减乘除这四个运算操作`

`浮点数求和`
export const accAdd = accFactory('plus')
`浮点数相减`
export const accSub = accFactory('minus')
`浮点数相乘`
export const accMul = accFactory('times')
`浮点数相除`
export const accDiv = accFactory('div')
  • 测试:
import { accAdd, accSub, accMul, accDiv } from '@/utils/calculate'

mounted() {
  this.calTestHandler()
},

methods: {
  calTestHandler() {
    const operations = [
      { operator'+'method: accAdd, a0.1b0.2 },
      { operator'-'method: accSub, a0.1b0.3 },
      { operator'*'method: accMul, a0.1b0.2 },
      { operator'/'method: accDiv, a0.1b0.3 }
    ]
 
    operations.forEach((operation) => {
      const { operator, method, a, b } = operation
      const result = method(a, b)
      console.log(`原生js ${operator} 运算:${a} ${operator} ${b}的值是${eval(a + operator + b)}`)
      console.log(`big.js ${operator} 运算:${a} ${operator} ${b}的值是${result}`)
    })
  }
}

结果展示:

image.png

3、使用`bluebird`[2]提升promise的执行速度

  • 前提:

bluebird是一个流行的Promise库,用于处理异步操作。它提供了强大的异步编程工具,使得编写和管理异步代码变得更加简单和可靠。

  1. Promise功能增强: bluebird提供了许多额外的功能和操作,超出了原生Promise的范围。它支持超时控制、并发控制、错误处理、重试、进度报告和取消等功能。这些功能使得处理复杂的异步控制流变得更加容易。
  2. 性能优化:bluebird在性能方面进行了优化,比原生Promise更快。 它实现了高效的异步调度和内存管理,以提供更快的执行速度和更低的资源消耗。这使得在大规模异步操作的情况下,bluebird可以提供更高效的性能。
  3. 错误追踪和调试:bluebird提供了更好的错误追踪和调试支持。 当使用bluebird进行异步操作时,它会生成详细的错误堆栈跟踪信息,包括异步操作链的每个步骤。这使得在调试和排查错误时更容易定位问题所在。
  4. 可互操作性:bluebirdapi与原生Promise相似,因此可以与其他使用Promise的库和代码进行互操作。 这使得在现有的代码基础上,迁移到bluebird更加容易,并且可以充分利用bluebird提供的额外功能。
  • 安装依赖: npm install --save bluebird
  • 方法封装(全局挂载):
import Promise from 'bluebird'

Promise.config({

  `确定是否启用警告输出。当设置为true,bluebird会在控制台输出警告,例如不推荐使用的方法或潜在问题`
  warningsfalse,
  
  `确定是否启用长堆栈跟踪, bluebird会生成详细的异步调用堆栈信息,包括Promise链中的每个步骤。`
  `这对于调试和错误追踪非常有用, 但启用长堆栈跟踪,可能会对性能产生一些影响`
  longStackTracesfalse,
  
  `确定是否启用取消功能。当设置为true时,bluebird允许取消异步操作。`
  `取消一个Promise将导致其相关的操作被中断或忽略,有助于优化资源使用和控制异步流程。`
  cancellationtrue,
  
  `确定是否启用性能监视。当设置为true时,bluebird可以收集异步操作的性能数据,例如执行时间、调用次数等。`
  `这对于分析和优化异步操作的性能非常有用。`
  monitoringtrue,
  
  `确定是否启用异步挂钩。异步挂钩是node.js提供的一种机制,可以在异步操作的不同阶段执行回调函数。`
  `当设置为true时,bluebird将使用异步挂钩来跟踪和管理异步操作`
  asyncHooksfalse
})
window.bluePromise = Promise
`main.js`

import bluebird from '@/utils/bluebird'
  • 测试
mounted() {
 this.proTestHandler()
},

methods: {
  async proTestHandler() {
    const promises = []
    const bluebirds = []
    
    const promiseList = (promise, count, arr) => {
      for (let i = 0; i < count; i++) {
        arr.push(new promise((resolve) => resolve(i)))
      }
    }
    
    const generatePromises = () => {
      promiseList(Promise1000000, promises)
      promiseList(bluePromise, 1000000, bluebirds)
    }
    
    generatePromises()
    console.log('promise')
    console.time()
    await Promise.all(promises)
    console.timeEnd()
    console.log('bluebirds')
    console.time()
    await bluePromise.all(bluebirds)
    console.timeEnd()
  }
}

结果展示

image.png

4、使用`hashids`[3]加密路由id

  • 前提:

在网址上应用hashids有以下4点重要意义:

  1. 加密隐藏真实id: 在某些情况下,你可能希望隐藏网址中的真实id,以增加安全性和防止直接暴露敏感信息。使用hashids,可以将真实的数字id转换为短字符串,并在网址中使用该短字符串代替原始id。这样,外部用户只能看到短字符串,而无法直接推断出真实的id值。
  2. 可读性和美观性: 长的数字id在网址中可能显得冗长和难以理解。使用hashids将其转换为短字符串,可以大大提升网址的可读性和美观性。短字符串通常包含字母和数字的组合,更易于记忆和分享。
  3. 防止猜测和遍历: 使用连续的数字id在网址中可能导致猜测和遍历的风险,因为攻击者可以通过递增id来尝试访问和暴露数据。通过使用hashids生成的短字符串作为id,可以有效地防止这种攻击。由于短字符串是随机生成的,攻击者无法根据短字符串推断出下一个id
  4. URL缩短和分享: hashids生成的短字符串可以用作url缩短服务的替代方案。你可以将长的url转换为短字符串,并在分享时使用该短字符串。这对于限制字符数、简化链接以及在社交媒体和短信中共享链接都非常有用。
  • 安装依赖: npm install --save hashids
  • 方法封装(全局挂载):
`短码方法封装:`

import Hashids from 'hashids'

const hashids = new Hashids(

  `盐值是一个可选的字符串参数,用于增加生成的短码的唯一性和安全性。每个不同的盐值将产生不同的短码序列,`
  `可以将盐值视为项目的名称或标识符。如果不提供盐值,则默认为一个空字符串。`
  
  'toadditWeb'
  
  `是一个可选的整数参数,用于指定生成的短码的最小长度。如果生成的短码长度小于指定的最小长度,`
  `hashids会自动填充短码以达到最小长度。这只是一个最小长度的限制,实际生成的短码长度可能更长。`
  `如果不提供最小长度,则默认为0,即没有最小长度限制。`
  8
  
  `字母表是一个可选的字符串参数,用于定义生成短码时使用的字符集。该字符串包含所有可用于生成短码的字符。` 
  `通常,字母表中应包含一组不容易混淆的字符,以避免生成的短码产生歧义。如果不提供字母表,则默认为`
  `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"`
)

`短码封装`
export function encode(val{
  return hashids.encode(val)
}

`短码解析`
export function decode(val{
  return hashids.decode(val)[0]
}
`glboal.js: 全局注册`

import { encode, decode } from '@/utils/hashids'

export default {
  install(Vue) {
    Vue.prototype.$encode = window.$encode = (data) => encode(data)
    Vue.prototype.$decode = window.$decode = (data) => decode(data)
  }
}
`在main.js中挂载:`

import Vue from 'vue'
import App from './App'
import globalConst from '@/commons/globalConst'

Vue.use(globalConst)

new Vue({
  el'#app',
  router,
  store,
  render(h) => h(App)
})
  • 测试:

mounted() {
  this.hashTestHandler()
},

methods: {
  hashTestHandler() {
    const testId = 18
    const encode = $encode(testId)
    console.log(`hashids编码前: ${testId}`)
    console.log(`hashids编码: ${encode}`)
    console.log(`hashids解码: ${$decode(encode)}`)
  }
}

结果展示(在同一个盐值下,不管页面是否刷新,编码结果都不会改变):

image.png

5、登陆时使用`行为验证码`[4]

  • 前提:

登陆时使用行为验证码有以下5点重要意义:

  1. 增强安全性: 传统的验证码可以被自动化的机器人或恶意程序轻易地破解或绕过。而行为验证码通过分析用户的行为模式,可以更准确地识别是否是真实用户,从而提高安全性,防止恶意活动和机器人攻击。
  2. 用户友好性: 相比于传统的验证码,行为验证码通常对用户来说更加友好和便捷。用户无需输入复杂的文本或解析模糊的图像,而是通过正常的交互行为完成验证,例如简单的滑动、点击、拖拽等操作。
  3. 无感知验证: 行为验证码可以在用户进行正常的操作过程中进行验证,几乎无需用户额外的干预或注意。这样可以减少对用户的干扰和阻碍,提升用户体验。
  4. 自适应性: 行为验证码可以根据用户的行为模式自适应地进行验证。它可以根据用户的设备、IP地址、浏览器指纹、鼠标移动轨迹等因素来综合评估用户的真实性,从而提高准确性和安全性。
  5. 防止数据滥用: 行为验证码可以用于防止恶意用户或攻击者滥用系统或服务。通过分析用户的行为模式和交互方式,可以及时识别和阻止异常行为,保护系统和数据的安全。
前端参考文档:https://github.com/Yunlingfly/vue-captcha/tree/master

最终效果:

image.png


—END—