我工作中用到的性能优化全面指南(1)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 在Web开发中,Web的性能优化是一个重要的话题。无论是页面加载速度,用户体验,或者是程序运行效率,都与Web的性能优化息息相关。最小化和压缩代码在构建过程中,为了减少文件的大小和加载时间,通常会对JavaScript代码进行最小化和压缩处理。这包括移除不必要的空格、换行、注释,以及缩短变量和函数名。工具如UglifyJS和Terser等可以帮助我们完成这个任务。

在Web开发中,Web的性能优化是一个重要的话题。无论是页面加载速度,用户体验,或者是程序运行效率,都与Web的性能优化息息相关。

最小化和压缩代码

在构建过程中,为了减少文件的大小和加载时间,通常会对JavaScript代码进行最小化和压缩处理。这包括移除不必要的空格、换行、注释,以及缩短变量和函数名。工具如UglifyJS和Terser等可以帮助我们完成这个任务。

// 原始代码
function hello(name) {
  let message = 'Hello, ' + name;
  console.log(message);
}
// 压缩后的代码
function hello(n){var e='Hello, '+n;console.log(e)}

利用浏览器缓存

浏览器缓存是提升Web应用性能的一个重要手段。我们可以将一些经常用到的、变化不大的数据存储在本地,以减少对服务器的请求。例如,可以使用localStorage或sessionStorage来存储这些数据。

// 存储数据
localStorage.setItem('name', 'John');
// 获取数据
var name = localStorage.getItem('name');
// 移除数据
localStorage.removeItem('name');
// 清空所有数据
localStorage.clear();

避免过度使用全局变量

全局变量会占用更多的内存,并且容易导致命名冲突,从而降低程序的运行效率。我们应尽量减少全局变量的使用。

// 不好的写法
var name = 'John';
function greet() {
  console.log('Hello, ' + name);
}
// 好的写法
function greet(name) {
  console.log('Hello, ' + name);
}
greet('John');

使用事件委托减少事件处理器的数量

事件委托是将事件监听器添加到父元素,而不是每个子元素,以此来减少事件处理器的数量,并且提升性能。

document.getElementById('parent').addEventListener('click', function (event) {
  if (event.target.classList.contains('child')) {
    // 处理点击事件...
  }
});

好的,下面我会详细解释一下这些概念以及相关的示例:

async 和 defer

asyncdefer 是用于控制 JavaScript 脚本加载和执行的 HTML 属性。

  • async 使浏览器在下载脚本的同时,继续解析 HTML。一旦脚本下载完毕,浏览器将中断 HTML 解析,执行脚本,然后继续解析 HTML。

<script async src="script.js"></script>
  • defer 也使浏览器在下载脚本的同时,继续解析 HTML。但是,脚本的执行会等到 HTML 解析完毕后再进行。

<script defer src="script.js"></script>

在需要控制脚本加载和执行的时机以优化性能的场景中,这两个属性是非常有用的。

防抖和节流

throttle(节流)和 debounce(防抖)。

  • throttle 保证函数在一定时间内只被执行一次。例如,一个常见的使用场景是滚动事件的监听函数:

function throttle(func, delay) {
  let lastCall = 0;
  return function(...args) {
    const now = new Date().getTime();
    if (now - lastCall < delay) return;
    lastCall = now;
    return func(...args);
  };
}
window.addEventListener('scroll', throttle(() => console.log('Scrolling'), 100));
  • debounce 保证在一定时间内无新的触发后再执行函数。例如,实时搜索输入的监听函数:
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}
searchInput.addEventListener('input', debounce(() => console.log('Input'), 300));

利用虚拟DOM和Diff算法进行高效的DOM更新

当我们频繁地更新DOM时,可能会导致浏览器不断地进行重绘和回流,从而降低程序的性能。因此,我们可以使用虚拟DOM和Diff算法来进行高效的DOM更新。例如,React和Vue等框架就使用了这种技术。

// React示例
class Hello extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
ReactDOM.render(<Hello name="John" />, document.getElementById('root'));

避免长时间运行的任务

浏览器单线程的运行方式决定了JavaScript长时间运行的任务可能会阻塞UI渲染和用户交互,从而影响性能。对于这类任务,可以考虑将其分解为一系列较小的任务,并在空闲时执行,这就是“分片”或者“时间切片”的策略。

function chunk(taskList, iteration, context) {
  requestIdleCallback((deadline) => {
    while (deadline.timeRemaining() > 0 && taskList.length > 0) {
      iteration.call(context, taskList.shift());
    }
    if (taskList.length > 0) {
      chunk(taskList, iteration, context);
    }
  });
}
chunk(longTasks, (task) => {
  task.execute();
}, this);

虚拟列表(Virtual List)

当我们在页面上渲染大量的元素时,这可能会导致明显的性能问题。虚拟列表是一种技术,可以通过只渲染当前可见的元素,来优化这种情况。

虚拟列表的等高方式实现:

// 列表项高度
const ITEM_HEIGHT = 20;
class VirtualList {
  constructor(container, items, renderItem) {
    this.container = container;
    this.items = items;
    this.renderItem = renderItem;
    this.startIndex = 0;
    this.endIndex = 0;
    this.visibleItems = [];
    this.update();
    this.container.addEventListener('scroll', () => this.update());
  }
  update() {
    const viewportHeight = this.container.clientHeight;
    const scrollY = this.container.scrollTop;
    this.startIndex = Math.floor(scrollY / ITEM_HEIGHT);
    this.endIndex = Math.min(
      this.startIndex + Math.ceil(viewportHeight / ITEM_HEIGHT),
      this.items.length
    );
    this.render();
  }
  render() {
    // 移除所有的可见元素
    this.visibleItems.forEach((item) => this.container.removeChild(item));
    this.visibleItems = [];
    // 渲染新的可见元素
    for (let i = this.startIndex; i < this.endIndex; i++) {
      const item = this.renderItem(this.items[i]);
      item.style.position = 'absolute';
      item.style.top = `${i * ITEM_HEIGHT}px`;
      this.visibleItems.push(item);
      this.container.appendChild(item);
    }
  }
}
// 使用虚拟列表
new VirtualList(
  document.getElementById('list'),
  Array.from({ length: 10000 }, (_, i) => `Item ${i}`),
  (item) => {
    const div = document.createElement('div');
    div.textContent = item;
    return div;
  }
);// 不好的写法
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// 好的写法
let length = arr.length;
for (let i = 0; i < length; i++) {
  console.log(arr[i]);
}
// 更好的写法
arr.forEach(function (item) {
  console.log(item);
});
// 不好的写法
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// 好的写法
let length = arr.length;
for (let i = 0; i < length; i++) {
  console.log(arr[i]);
}
// 更好的写法
arr.forEach(function (item) {
  console.log(item);
});

优化循环

在处理大量数据时,循环的效率是非常重要的。我们可以通过一些方法来优化循环,例如:

// 不好的写法
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
// 好的写法
let length = arr.length;
for (let i = 0; i < length; i++) {
  console.log(arr[i]);
}
// 更好的写法
arr.forEach(function (item) {
  console.log(item);
});

避免阻塞UI

JavaScript的运行是阻塞UI的,当我们在进行一些耗时的操作时,应尽量使用setTimeout或Promise等异步方法,以避免阻塞UI。

setTimeout(function () {
  // 执行耗时的操作...
}, 0);

使用合适的数据结构和算法

使用合适的数据结构和算法是优化程序性能的基础。例如,当我们需要查找数据时,可以使用对象或Map,而不是数组;当我们需要频繁地添加或移除数据时,可以使用链表,而不是数组。

// 使用对象进行查找
var obj = { 'John': 1, 'Emma': 2, 'Tom': 3 };
console.log(obj['John']);
// 使用Map进行查找
var map = new Map();
map.set('John', 1);
map.set('Emma', 2);
map.set('Tom', 3);
console.log(map.get('John'));

避免不必要的闭包

虽然闭包在某些情况下很有用,但是它们也会增加额外的内存消耗,因此我们应该避免不必要的闭包。

// 不必要的闭包
function createFunction() {
  var name = 'John';
  return function () {
    return name;
  }
}
// 更好的方式
function createFunction() {
  var name = 'John';
  return name;
}

避免使用with语句

with语句会改变代码的作用域,这可能会导致性能问题,因此我们应该避免使用它。

// 不好的写法
with (document.getElementById('myDiv').style) {
  color = 'red';
  backgroundColor = 'black';
}
// 好的写法
var style = document.getElementById('myDiv').style;
style.color = 'red';
style.backgroundColor = 'black';

避免在for-in循环中使用hasOwnProperty

hasOwnProperty方法会查询对象的整个原型链,这可能会影响性能。在for-in循环中,我们应该直接访问对象的属性。

// 不好的写法
for (var key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key + ': ' + obj[key]);
  }
}
// 好的写法
for (var key in obj) {
  console.log(key + ': ' + obj[key]);
}

使用位操作进行整数运算

在进行整数运算时,我们可以使用位操作符,它比传统的算术运算符更快。

// 不好的写法
var half = n / 2;
// 好的写法
var half = n >> 1;

避免在循环中创建函数

在循环中创建函数会导致性能问题,因为每次迭代都会创建一个新的函数实例。我们应该在循环外部创建函数。

// 不好的写法
for (var i = 0; i < 10; i++) {
  arr[i] = function () {
    return i;
  }
}
// 好的写法
function createFunction(i) {
  return function () {
    return i;
  }
}
for (var i = 0; i < 10; i++) {
  arr[i] = createFunction(i);
}

使用Web Worker进行多线程处理

JavaScript默认是单线程运行的,但我们可以使用Web Worker来进行多线程处理,以提升程序的运行效率。

// 主线程
var worker = new Worker('worker.js');
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}
worker.postMessage('Hello Worker');
// worker.js
self.onmessage = function(event) {
  console.log('Received message ' + event.data);
  self.postMessage('You said: ' + event.data);
};

使用WebAssembly进行性能关键部分的开发

WebAssembly是一种新的编程语言,它的代码运行速度接近原生代码,非常适合于进行性能关键部分的开发。例如,我们可以用WebAssembly来开发图形渲染、物理模拟等复杂任务。

// 加载WebAssembly模块
WebAssembly.instantiateStreaming(fetch('module.wasm'))
  .then(result => {
    // 调用WebAssembly函数
    result.instance.exports.myFunction();
  });

使用内存池来管理对象

当我们频繁地创建和销毁对象时,可以使用内存池来管理这些对象,以避免频繁地进行内存分配和垃圾回收,从而提升性能。

class MemoryPool {
  constructor(createObject, resetObject) {
    this.createObject = createObject;
    this.resetObject = resetObject;
    this.pool = [];
  }
  acquire() {
    return this.pool.length > 0 ? this.resetObject(this.pool.pop()) : this.createObject();
  }
  release(obj) {
    this.pool.push(obj);
  }
}
var pool = new MemoryPool(
  () => { return {}; },
  obj => { for (var key in obj) { delete obj[key]; } return obj; }
);

使用双缓冲技术进行绘图

当我们需要进行频繁的绘图操作时,可以使用双缓冲技术,即先在离屏画布上进行绘图,然后一次性将离屏画布的内容复制到屏幕上,这样可以避免屏幕闪烁,并且提升绘图性能。

var offscreenCanvas = document.createElement('canvas');
var offscreenContext = offscreenCanvas.getContext('2d');
// 在离屏画布上进行绘图...
offscreenContext.fillRect(0, 0, 100, 100);
// 将离屏画布的内容复制到屏幕上
context.drawImage(offscreenCanvas, 0, 0);

目录
相关文章
|
7月前
|
消息中间件 缓存 NoSQL
如何做性能优化?
如何做性能优化?
|
1月前
|
缓存 负载均衡 算法
性能优化:提升系统效率的关键
性能优化:提升系统效率的关键
77 1
|
5月前
|
SQL 缓存 Java
系统性能优化总结
系统性能优化总结
86 10
|
6月前
|
存储 JSON 数据格式
如何提升写入效率?Schemaless 写入性能优化实践分享
TDengine 是一款时序数据库,其Schemaless模式适应物联网数据动态变化。通过分析火焰图,发现parser和insert操作是性能瓶颈。优化措施包括减少标签解析、排序和子表生成的重复执行,提前判断schema变更,改进数据插入方法,减少内存分配和拷贝。通过这些优化,如在3.0版本中,line协议性能提升了2.5倍,telnet提升2倍,json提升近5倍。使用工具如火焰图和perf进行性能分析,以识别和解决瓶颈,实现性能提升。
44 0
|
7月前
|
缓存 监控 NoSQL
一次性能优化实践
【5月更文挑战第21天】为解决在线教育平台在高并发下数据库查询响应时间增加的问题,开发者采用Redis缓存策略。通过数据分层、LRU淘汰策略、异步更新及监控调优,成功提升性能,缓存命中率超90%,页面加载时间从3秒降至1秒,改善了用户体验。此实践强调了合理缓存策略、监控调优以及考虑数据访问模式在系统设计中的重要性。
79 2
|
7月前
|
缓存 小程序 前端开发
小程序 如何做性能优化?
小程序 如何做性能优化?
|
前端开发
一次性能优化思考过程
最近业务上空闲了下来,也是把之前在开发时自身感受比较大的白屏时间放在了主线上去排查优化,这里记录一下笔者对于移动端vConsole脚本的引入问题全过程。
174 0
一次性能优化思考过程
|
存储 缓存 NoSQL
性能优化方案及思考
周末闲暇在家,朋友让我帮忙优化一个接口,这个接口之前每次加载都需要40s左右,经过优化将性能提了10倍左右;又加了缓存直接接口响应目前为300ms左右,于是将自己的优化思路整理总结一下
|
Web App开发 存储 缓存
我工作中用到的性能优化全面指南(2)
使用WebGL进行3D渲染 WebGL是一种用于进行3D渲染的Web标准,它提供了底层的图形API,并且能够利用GPU进行加速,非常适合于进行复杂的3D渲染。
111 0
|
消息中间件 缓存 弹性计算