全网最全的性能优化指南(建议收藏)

在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;
  }
);

优化循环

在处理大量数据时,循环的效率是非常重要的。我们可以通过一些方法来优化循环,例如:避免在循环中进行不必要的计算,使用倒序循环,使用forEach或map等函数。

// 不好的写法
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);

使用WebGL进行3D渲染

WebGL是一种用于进行3D渲染的Web标准,它提供了底层的图形API,并且能够利用GPU进行加速,非常适合于进行复杂的3D渲染。

var canvas = document.getElementById('myCanvas');
var gl = canvas.getContext('webgl');

// 设置清空颜色缓冲区的颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);

// 清空颜色缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);

使用Service Workers进行资源缓存

Service Workers可以让你控制网页的缓存策略,进一步减少HTTP请求,提升网页的加载速度。例如,你可以将一些不常变化的资源文件预先缓存起来。

// 注册一个service worker
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
  console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(error) {
  console.log('ServiceWorker registration failed: ', error);
});

// service-worker.js
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('my-cache').then(function(cache) {
      return cache.addAll([
        '/style.css',
        '/script.js',
        // 更多资源...
      ]);
    })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

使用内容分发网络(CDN)

你可以将静态资源(如JavaScript、CSS、图片等)上传到CDN,这样用户可以从离他们最近的服务器下载资源,从而提高下载速度。

<!-- 从CDN加载jQuery库 -->
<script src="https://cdn.example.com/jquery.min.js"></script>

使用HTTP/2进行资源加载

HTTP/2支持头部压缩和多路复用,可以更高效地加载资源。如果你的服务器和用户的浏览器都支持HTTP/2,那么你可以使用它来提高性能。

// 假设我们有一个HTTP/2库
var client = new Http2Client('https://example.com');

client.get('/resource1');
client.get('/resource2');

使用Web Socket进行数据通信

如果你需要频繁地与服务器进行数据交换,可以使用Web Socket,它比HTTP有更低的开销。

var socket = new WebSocket('ws://example.com/socket');

socket.addEventListener('open', function() {
  socket.send('Hello, server');
});

socket.addEventListener('message', function(event) {
  console.log('Received message from server: ' + event.data);
});

使用Progressive Web Apps(PWA)技术

PWA可以让你的网站在离线时仍然可用,并且可以被添加到用户的主屏幕,提供类似于原生应用的体验。PWA需要使用Service Workers和Manifest等技术。

// 注册Service Worker
navigator.serviceWorker.register('/service-worker.js');

// 检测是否支持Manifest
if ('manifest' in document.createElement('link')) {
  var link = document.createElement('link');
  link.rel = 'manifest';
  link.href = '/manifest.json';
  document.head.appendChild(link);
}

使用WebRTC进行实时通信

WebRTC是一种提供实时通信(RTC)能力的技术,允许数据直接在浏览器之间传输,对于需要实时交互的应用,如视频聊天、实时游戏等,可以使用WebRTC来提高性能。

var pc = new RTCPeerConnection();

// 发送offer
pc.createOffer().then(function(offer) {
  return pc.setLocalDescription(offer);
}).then(function() {
  // 发送offer给其他浏览器...
});

// 收到answer
pc.setRemoteDescription(answer);

使用IndexedDB存储大量数据

如果你需要在客户端存储大量数据,可以使用IndexedDB。与localStorage相比,IndexedDB可以存储更大量的数据,并且支持事务和索引。

var db;
var request = indexedDB.open('myDatabase', 1);
request.onupgradeneeded = function(event) {
  db = event.target.result;
  var store = db.createObjectStore('myStore', { keyPath: 'id' });
  store.createIndex('nameIndex', 'name');
};
request.onsuccess = function(event) {
  db = event.target.result;
};
request.onerror = function(event) {
  // 错误处理...
};

使用Web Push进行后台消息推送

Web Push允许服务器在后台向浏览器推送消息,即使网页已经关闭。这需要在Service Worker中使用Push API和Notification API。

// 请求推送通知的权限
Notification.requestPermission().then(function(permission) {
  if (permission === 'granted') {
    console.log('Push notification permission granted');
  }
});

// 订阅推送服务
navigator.serviceWorker.ready.then(function(registration) {
  registration.pushManager.subscribe({ userVisibleOnly: true }).then(function(subscription) {
    console.log('Push subscription: ', subscription);
  });
});

// 在Service Worker中接收和显示推送通知
self.addEventListener('push', function(event) {
  var data = event.data.json();
  self.registration.showNotification(data.title, data);
});

通过服务器端渲染(SSR)改善首次页面加载性能

服务器端渲染意味着在服务器上生成HTML,然后将其发送到客户端。这可以加快首次页面加载速度,因为用户可以直接看到渲染好的页面,而不必等待JavaScript下载并执行。这对于性能要求很高的应用来说,是一种有效的优化手段。

// 服务器端
app.get('/', function(req, res) {
  const html = ReactDOMServer.renderToString(<MyApp />);
  res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});

利用HTTP3/QUIC协议进行资源传输

HTTP3/QUIC协议是HTTP/2的后续版本,采用了全新的底层传输协议(即QUIC),以解决HTTP/2中存在的队头阻塞(Head-of-line Blocking)问题,从而进一步提高传输性能。如果你的服务器和用户的浏览器都支持HTTP3/QUIC,那么可以考虑使用它进行资源传输。

使用Service Worker与Background Sync实现离线体验

通过Service Worker,我们可以将网络请求与页面渲染解耦,从而实现离线体验。并且,结合Background Sync,我们可以在用户离线时提交表单或同步数据,并在用户重新联网时自动重试。

// 注册Service Worker
navigator.serviceWorker.register('/sw.js');

// 提交表单
fetch('/api/submit', {
  method: 'POST',
  body: new FormData(form)
}).catch(() => {
  // 如果请求失败,使用Background Sync重试
  navigator.serviceWorker.ready.then(reg => {
    return reg.sync.register('sync-submit');
  });
});

// 在Service Worker中监听sync事件
self.addEventListener('sync', event => {
  if (event.tag === 'sync-submit') {
    event.waitUntil(submitForm());
  }
});

使用PostMessage进行跨文档通信

如果你的应用涉及到多个窗口或者iframe,你可能需要在他们之间进行通信。使用postMessage方法可以进行跨文档通信,而不用担心同源策略的问题。

// 父窗口向子iframe发送消息
iframeElement.contentWindow.postMessage('Hello, child', 'https://child.example.com');

// 子iframe接收消息
window.addEventListener('message', function(event) {
  if (event.origin !== 'https://parent.example.com') return;
  console.log('Received message: ' + event.data);
});

使用Intersection Observer进行懒加载

Intersection Observer API可以让你知道一个元素何时进入或离开视口,这对于实现图片或者其他资源的懒加载来说非常有用。

var images = document.querySelectorAll('img.lazy');

var observer = new IntersectionObserver(function(entries, observer) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      var img = entry.target;
      img.src = img.dataset.src;
      observer.unobserve(img);
    }
  });
});

images.forEach(img => {
  observer.observe(img);
});

利用OffscreenCanvas进行后台渲染

OffscreenCanvas API使得开发者可以在Web Worker线程中进行Canvas渲染,这可以提高渲染性能,尤其是在进行大量或者复杂的Canvas操作时。

var offscreen = new OffscreenCanvas(256, 256);
var ctx = offscreen.getContext('2d');

// 在后台线程中进行渲染...

利用Broadcast Channel进行跨标签页通信

Broadcast Channel API提供了一种在同源的不同浏览器上下文之间进行通信的方法,这对于需要在多个标签页之间同步数据的应用来说非常有用。

var channel = new BroadcastChannel('my_channel');

// 发送消息
channel.postMessage('Hello, other tabs');

// 接收消息
channel.onmessage = function(event) {
  console.log('Received message: ' + event.data);
};

使用Web Cryptography API进行安全操作

Web Cryptography API 提供了一组底层的加密API,使得开发者可以在Web环境中进行安全的密码学操作,例如哈希、签名、加密、解密等。

window.crypto.subtle.digest('SHA-256', new TextEncoder().encode('Hello, world')).then(function(hash) {
  console.log(new Uint8Array(hash));
});

使用Blob对象进行大型数据操作

Blob对象代表了一段二进制数据,可以用来处理大量的数据,比如文件。它们可以直接从服务端获取,或者由客户端生成,这对于处理大型数据或者二进制数据很有用。

var fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', function(event) {
  var file = event.target.files[0];
  var reader = new FileReader();
  reader.onload = function(event) {
    var contents = event.target.result;
    processContents(contents);
  };
  reader.readAsArrayBuffer(file);
});

使用Page Visibility API进行页面可见性调整

Page Visibility API提供了一种方式来判断页面是否对用户可见。利用这个API,你可以在页面不可见时停止或减慢某些操作,例如动画或视频,从而节省CPU和电池使用。

document.addEventListener('visibilitychange', function() {
  if (document.hidden) {
    pauseAnimation();
  } else {
    resumeAnimation();
  }
});

使用WeakMap和WeakSet进行高效的内存管理

在处理大量数据时,如果不小心可能会产生内存泄漏。WeakMap和WeakSet可以用来保存对对象的引用,而不会阻止这些对象被垃圾回收。这在一些特定的应用场景中,例如缓存、记录对象状态等,可能非常有用。

let cache = new WeakMap();

function process(obj) {
  if (!cache.has(obj)) {
    let result = /* 对obj进行一些复杂的处理... */
    cache.set(obj, result);
  }
  
  return cache.get(obj);
}

使用requestAnimationFrame进行动画处理

requestAnimationFrame能够让浏览器在下一次重绘之前调用指定的函数进行更新动画,这样可以保证动画的流畅性,并且减少CPU的使用。

function animate() {
 

 // 更新动画...
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

使用CSS3动画替代JavaScript动画

CSS3动画不仅可以提供更好的性能,还可以在主线程之外运行,从而避免阻塞UI。因此,我们应该尽可能地使用CSS3动画替代JavaScript动画。

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.myDiv {
  animation: fadeIn 2s ease

-in-out;
}

避免回流和重绘

回流和重绘是浏览器渲染过程中的两个步骤,它们对性能影响很大。优化的关键在于尽可能减少触发回流和重绘的操作,例如一次性修改样式,避免布局抖动等。

var el = document.getElementById('my-el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
// 尽量避免上面的写法,以下为优化后的写法
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';

使用CSS3硬件加速提高渲染性能

使用 CSS3 的 transform 属性做动画效果,可以触发硬件加速,从而提高渲染性能。

element.style.transform = 'translate3d(0, 0, 0)';

避免使用同步布局

同步布局(或强制布局)是指浏览器强制在 DOM 修改和计算样式之后,立即进行布局。这会中断浏览器的优化过程,导致性能下降。一般出现在连续的样式修改和读取操作之间。

let div = document.querySelector('div');

// 写样式
div.style.width = '100px';
// 读样式,导致同步布局
let width = div.offsetWidth;
// 再写样式
div.style.height = width + 'px';  // 强制布局

为避免这个问题,可以将读操作移到所有写操作之后:

let div = document.querySelector('div');

// 写样式
div.style.width = '100px';
// 写样式
div.style.height = '100px';

// 读样式
let width = div.offsetWidth;

使用ArrayBuffer处理二进制数据

ArrayBuffer 提供了一种处理二进制数据的高效方式,例如图像,声音等。

var buffer = new ArrayBuffer(16);
var int32View = new Int32Array(buffer);
for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

利用ImageBitmap提高图像处理性能

ImageBitmap对象提供了一种在图像处理中避免内存拷贝的方法,可以提高图像处理的性能。

var img = new Image();
img.src = 'image.jpg';
img.onload = function() {
  createImageBitmap(img).then(function(imageBitmap) {
    // 在这里使用 imageBitmap
  });
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linwu-hi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值