优化JavaScript代码以提高性能 | 性能优化与调试技巧

90 阅读4分钟

课程链接:JavaScript 编码原则之各司其责 - 掘金 (共三节课)

在编写 JavaScript 代码时,遵循一些基本的编程原则可以帮助你编写更加清晰、易于维护、可扩展的代码。

  • 各司其责:确保每个函数和模块的职责清晰。一个函数应该只做一件事情,并且做好它。这样能够增强代码的可维护性和可读性。例如,一个负责获取数据的函数不应该直接修改页面 DOM。

  • 组件封装:将页面元素和功能拆分成可复用的组件,提升代码的结构性和可维护性。每个组件应该有清晰的职责,包含模板(HTML)、功能(JavaScript)和样式(CSS),可以独立使用和修改。

  • 过程抽象:避免重复代码,通过抽象化和模块化来提高代码的复用性。常见的方式是将重复的逻辑提取为函数、模块或类,使得代码更加简洁、易于管理。

课程中的例子

1. 减少重绘和重排:控制网页浅色和深色模式的实现

版本一
const btn=document.getElementById('modebtn');
btn.addEventListener('click',(e)=>{
    const body = document.body;
    if(e.target.innerHTML === 'Dark Mode'){
        body.style.backgroundColor = 'black';
        body.style.color = 'white';
        e.target.innerHTML = 'Light Mode';
    }else
    {
        body.style.backgroundColor = 'white';
        body.style.color = 'black';
        e.target.innerHTML = 'Dark Mode';
    }
});
代码存在的问题:

未初始化页面主题

假如用户首次访问页面时,页面的初始主题状态是Dark Mode,那么点击按钮后才会切换到白色主题Light Mode。为了确保用户每次访问页面时根据其最后一次选择的主题进行初始化,应在页面加载时就检测并设置初始状态。

使用内联 style 修改颜色

  • 代码可维护性差:如果以后有多个地方都需要修改背景色和文字色,直接修改 style 会导致代码重复,难以管理。

  • 修改复杂的样式时不方便:不仅需要修改背景色,还需要修改按钮的颜色、文字大小等,如果每次都要直接操作 style,代码会变得越来越复杂。

版本二
const btn=document.getElementById('modebtn');
btn.addEventListener('click',(e)=>{
    const body = document.body;
    if(body.className!== 'dark-mode'){
        body.className='dark-mode';
    }else{
        body.className='';
    }
});

通过 className 控制样式,可以将样式集中在 CSS 中,JavaScript 只负责逻辑,易于扩展和修改。

代码存在的问题:

直接使用 className 覆盖所有类

通过 body.className = 'dark-mode' 直接设置 bodyclassName 属性为 'dark-mode',这会替换掉 body 上所有已有的类名

版本三
<body>
    <input id="modeCheckBox" type="checkbox">
    <div class="content">
        <header>
            <label id="modeBtn" for="modeCheckBox"></label>
            <h1>深夜食堂</h1>
        </header>
    </div>
</body>

<style>
#modeCheckBox:checked+.content{
    background-color: black;
    color:white;
    transition: all 1s;
}
</style>

用户可以通过勾选 checkbox(复选框)来切换页面的背景颜色和文字颜色。

#modeCheckBox:checked 伪类选择器来检测复选框是否被选中,然后通过相邻选择器 + 来更改相邻元素的样式。

优点

简洁且无JS依赖

仅通过 HTML 和 CSS 就实现了模式切换功能,不依赖任何 JavaScript。减少复杂性,也提高了页面加载性能和响应速度。

伪类选择器的应用

使用 :checked 伪类选择器来检测复选框是否被选中,通过 + 相邻选择器,将夜间模式样式应用到紧邻的 .content 元素。

总结

  1. 减少重绘和重排: 只在切换复选框状态时,通过 CSS 改变背景和文字颜色。这种方式避免了对 DOM 的频繁操作,从而减少了页面的重绘和重排。

  2. CSS 过渡动画的优化transition: all 1s; 使得颜色变化在切换时有一个平滑的过渡,而不是立即变化,降低了页面的渲染压力。


2. 组件封装:电商网站的轮播图

整体思路:
  1. HTML 结构:
  • 使用 <ul><li> 标签来创建图片列表。

  • 用按钮 <button> 控制轮播图的切换。

  1. CSS 样式:
  • 使用 position: relativeoverflow: hidden 来隐藏超出的图片部分。

  • 使用 CSS 的 transitionopacity 来实现图片的平滑切换效果。 只影响元素的透明度,不会触发浏览器的重排和重绘。

  1. JavaScript 逻辑:
  • 使用 classList.addclassList.remove 方法来控制哪个图片显示,哪个图片隐藏。

使用 Chrome DevTools 分析性能

GPT给出的性能较差的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>性能优化示例 - 交互</title>
    <style>
        #container {
            width: 100px;
            height: 100px;
            background-color: lightblue;
            transition: width 0.5s;
        }
        #button {
            margin-top: 20px;
            padding: 10px 20px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <button id="button">Increase Width</button>

    <script>
        document.getElementById('button').addEventListener('click', () => {
            const container = document.getElementById('container');
            let currentWidth = 100;

            // 不佳的代码:频繁更新宽度
            const interval = setInterval(() => {
                currentWidth += 1;
                container.style.width = currentWidth + 'px';

                if (currentWidth >= 300) {
                    clearInterval(interval);
                }
            }, 10); // 每隔10毫秒更新一次
        });
    </script>
</body>
</html>


image.png

点击按钮后,setInterval 会每隔 10 毫秒将 container 宽度增加 1px,直到宽度达到 300px。这种频繁操作会导致页面的重排和重绘,降低性能。

image.png

d8428072df229b01a01fd6261af0ebf.png

优化后的代码:

    <script>
        document.getElementById('button').addEventListener('click', () => {
            const container = document.getElementById('container');
            let currentWidth = 100;
            const targetWidth = 300;

            function increaseWidth() {
                if (currentWidth < targetWidth) {
                    currentWidth += 2;  // 增加宽度
                    container.style.width = currentWidth + 'px';
                    requestAnimationFrame(increaseWidth);  // 请求下一帧
                }
            }

            requestAnimationFrame(increaseWidth);  // 初次调用
        });
    </script>

requestAnimationFrame 将所有更新操作尽可能合并在一起,避免每 10 毫秒触发一次重排,减少了浏览器的工作量。

d0490075f64f0dd6e89b157e94e0769.png