likes
comments
collection
share

前端性能优化之JavaScript代码优化

作者站长头像
站长
· 阅读数 15

前端性能优化之JavaScript代码优化

1. 优化JavaScript代码执行

a. 使用事件委托来减少事件处理程序的数量

使用事件委托可以大大减少事件处理程序的数量,从而提高页面性能。这是因为事件委托将事件处理程序绑定到父元素上,而不是绑定到每个子元素上,从而避免了每个子元素都有一个事件处理程序的情况。

举个例子,如果您有一个包含多个按钮的列表,并且希望为每个按钮添加点击事件,您可以为每个按钮单独绑定一个事件处理程序,或者将事件处理程序绑定到列表的父元素上,然后使用事件委托来处理每个按钮的点击事件。如果您使用事件委托,您只需要一个事件处理程序,无论列表中有多少个按钮,这可以显着提高页面性能。

以下是一个使用事件委托的示例:

```
<ul id="myList">
  <li><button>Button 1</button></li>
  <li><button>Button 2</button></li>
  <li><button>Button 3</button></li>
</ul>

const list = document.querySelector('#myList');
list.addEventListener('click', function(event) {
  if (event.target.tagName === 'BUTTON') {
    // 处理按钮点击事件
    console.log('Button clicked!');
  }
});

```

在此示例中,我们使用了事件委托来避免为每个按钮单独绑定一个事件处理程序。这将减少代码量,并提高页面性能。

b. 避免使用过多的全局变量

在JavaScript中,全局变量可以被所有代码访问,而且一旦被定义,将一直存在于内存中,这可能会导致命名冲突和内存泄漏等问题。因此,避免过多的全局变量可以提高代码的可维护性和性能。

下面是一些可能有助于减少全局变量的建议:

使用函数作用域:将变量声明在函数内部,而不是在全局范围内声明,可以减少全局变量的数量。例如:

```
function myFunction() {
  var x = 1;
  // ...
}
```

使用模块化开发:使用模块化工具(如ES6模块、CommonJS、AMD等)将代码分成独立的模块,可以避免命名冲突和全局变量的污染。例如:

```
// module1.js
export function myFunction() {
  // ...
}

// module2.js
import { myFunction } from './module1.js';
```

避免使用全局变量作为对象属性:将全局变量作为对象属性可能会导致变量被污染或重写,从而导致不可预料的结果。因此,应该避免这种做法,或者使用封闭的作用域来限制访问。例如:

```
// 避免这种做法
var myObject = {
  myVariable: 1,
  // ...
};

// 建议这种做法
var myObject = (function() {
  var myVariable = 1;
  return {
    // ...
  };
})();
```
  • 避免使用eval()和with()

    eval()函数可以将字符串作为JavaScript代码执行,而with()语句可以将一个对象作为当前执行环境的上下文,这两种技术在某些情况下可能会导致性能下降和安全问题。

    • 性能问题: 使用eval()with()可能导致性能下降,因为它们在运行时需要解析和执行代码。解析和执行代码需要时间和计算资源,所以过多地使用这些功能可能会导致应用程序变慢。

    • 安全问题: eval()函数可以将字符串作为代码执行,如果应用程序接受用户输入并使用eval()执行该输入,则可能存在安全漏洞。因为用户可能会注入恶意代码并执行它,从而导致安全问题。 with()语句可以更改当前执行环境的上下文,从而可能导致变量名冲突或数据污染问题,这也可能导致安全漏洞。

    那么如何避免使用eval()with()呢?可以考虑使用其他技术,如:

    • 使用闭包: 闭包是一种在JavaScript中创建私有变量和函数的技术。它可以避免使用eval()with(),因为它可以让你在不污染全局命名空间的情况下创建和存储变量和函数。

    • 使用对象和数组: 使用对象和数组可以将数据和代码组织在一起,避免了使用eval()with()来访问动态生成的代码。例如,可以使用对象和数组来存储一组数据和对应的处理方法:

        const data = {
          name: 'John',
          age: 25,
          address: '123 Main St'
        };
    
        const handlers = {
          name: (value) => { data.name = value },
          age: (value) => { data.age = value },
          address: (value) => { data.address = value }
        };
    
        function updateData(property, value) {
          if (handlers.hasOwnProperty(property)) {
            handlers[property](value);
          } else {
            console.log(`Invalid property: ${property}`);
          }
        }
    
        updateData('name', 'Jane');
        console.log(data); // 输出 { name: 'Jane', age: 25, address: '123 Main St' }
    
    
    • 避免动态创建代码: 动态创建代码可以使用eval()with()来执行,因此应避免使用此功能。如果必须动态创建代码,则应使用安全的方式来执行它们。

c. 避免频繁的重绘和回流

当DOM结构发生变化时,浏览器会进行重绘和回流,重绘是指页面中某些元素的样式发生变化,需要重新渲染。而回流是指页面布局发生变化,需要重新计算元素的位置和大小,这两个操作都会占用大量的CPU资源,导致页面卡顿和响应变慢。 以下是一些可能有助于避免频繁的重绘和回流的建议:

  • 使用CSS动画代替JavaScript动画: CSS动画可以在浏览器的GPU中处理,比使用JavaScript编写动画要高效得多,因此尽可能使用CSS动画来减少重绘和回流。

  • 批量操作DOM元素: 在JavaScript中执行DOM操作可以使页面变慢,因为DOM操作会涉及到页面的重绘和回流。为了避免这种情况,可以尽量避免在JavaScript中执行DOM操作,而是通过修改CSS类名或属性值来达到同样的效果。 例如,如果需要隐藏一个元素,可以通过在该元素上添加一个类名来实现,而不是使用style.display = 'none',这样可以避免重绘和回流。 另外,可以使用文档片段(DocumentFragment)来减少DOM操作的次数。文档片段是一种轻量级的文档对象,它可以容纳一组DOM节点,并在插入到文档树中时一次性渲染,从而避免频繁的DOM操作。

    下面是一个示例,演示了如何使用文档片段来优化DOM操作:

        // 创建一个文档片段
        var fragment = document.createDocumentFragment();
    
        // 循环创建多个列表项,并添加到文档片段中
        for (var i = 0; i < 10; i++) {
          var li = document.createElement('li');
          li.textContent = '列表项 ' + i;
          fragment.appendChild(li);
        }
    
        // 将文档片段一次性添加到文档树中
        document.getElementById('list').appendChild(fragment);
    
    
  • 避免强制同步布局: 当使用一些会触发页面回流的属性(如offsetTop、offsetHeight、clientTop等)时,浏览器会强制同步布局以计算元素的尺寸和位置等信息。因此,应尽可能避免使用这些属性,并考虑使用缓存来避免重复计算。

  • 使用定位代替浮动: 当元素使用浮动时,浏览器需要重新计算页面中其他元素的位置。因此,可以考虑使用定位代替浮动,以减少页面回流。

d. 避免不必要的函数调用

函数调用会产生一定的开销,包括创建函数对象、压入函数调用栈、执行函数代码等等。

如果在代码中存在大量不必要的函数调用,会显著影响页面的性能和响应速度。在JavaScript中,每次函数调用都会产生一定的性能开销,因此在写代码时,我们应该尽可能减少函数的调用次数,从而提高代码的执行速度。

例如,当我们需要对一个数组进行排序时,我们可以使用原生的sort()函数,而不是自己写一个排序函数,这样可以减少不必要的函数调用。

或者避免在循环中多次调用同一个函数。如果函数体内部不会发生变化,我们可以将函数的返回值缓存起来,然后在循环中重复使用。这样可以减少函数的调用次数,从而提高代码执行效率。

2. 使用高效的JavaScript代码

a. 避免使用for-in循环

避免使用for-in循环可以提高代码的性能和可读性。因为for-in循环会枚举对象的所有可枚举属性,包括继承自原型链的属性,而且枚举的顺序是不确定的。在大多数情况下,我们只需要枚举对象自身的属性,此时使用for-in循环就不太合适了。

下面是一些避免使用for-in循环的方法:

  1. 使用Object.keys()方法获取对象的所有可枚举属性,然后使用普通的for循环遍历这些属性。例如:

    ```
        const obj = {a: 1, b: 2, c: 3};
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
          console.log(keys[i], obj[keys[i]]);
        }
    
    ```
    
  2. 使用Object.getOwnPropertyNames()方法获取对象的所有属性,然后使用普通的for循环遍历这些属性。例如:

        // `Object.getOwnPropertyNames()`方法只能获取对象的自身属性,不能获取继承自原型链的属性。
        const obj = {a: 1, b: 2, c: 3};
        const props = Object.getOwnPropertyNames(obj);
        for (let i = 0; i < props.length; i++) {
          console.log(props[i], obj[props[i]]);
        }
    
    

b. 尽量避免在循环中使用过多的条件判断

在循环体中每次执行条件判断会消耗一定的性能,如果循环次数很多,那么条件判断的性能开销也会很大,影响程序的运行效率。 一些优化的技巧包括:

  1. 避免重复的条件判断:如果条件判断的结果不会在循环体内发生变化,可以将其提前到循环外,避免在循环中重复计算。

  2. 使用更高效的循环方式:在 JavaScript 中,使用 for 循环比 for-in 循环更高效,因为 for-in 循环需要检查每一个可枚举属性是否符合要求,而 for 循环则不需要。

  3. 使用缓存变量:如果在循环中使用的变量是一个对象的属性或方法,可以将其缓存到一个局部变量中,避免每次循环都要重新计算一遍。

  4. 使用数组操作方法:在处理数组时,可以使用数组操作方法(例如 map、filter、reduce 等)来减少循环次数和条件判断的次数,从而提高代码效率。

  5. 采用二分查找算法:如果要在有序数组中查找特定的值,可以使用二分查找算法,而不是遍历整个数组查找,这样可以大大减少查找次数,提高效率。

    下面是一个使用缓存变量的例子:

        const obj = {
          a: 1,
          b: 2,
          c: 3
        };
    
        // 不使用缓存变量
        for (let i = 0; i < Object.keys(obj).length; i++) {
          console.log(obj[Object.keys(obj)[i]]);
        }
    
        // 使用缓存变量
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
          console.log(obj[keys[i]]);
        }
    
    

    在这个示例中,我们使用了一个对象 obj,并想要在循环中打印它的所有属性值。第一个循环中,我们在每次迭代中都要使用 Object.keys(obj) 来获取对象的所有属性名,并使用 obj[Object.keys(obj)[i]] 来访问属性值,导致重复计算。而在第二个循环中,我们在循环之前就缓存了对象的属性名到变量 keys 中,然后在循环中直接使用 obj[keys[i]] 来访问属性值,避免了重复计算,提高了效率。

3. 使用异步编程模式来提高性能

异步编程模式是一种在 JavaScript 中处理并发任务的方式,它可以提高性能并提高用户体验。在同步编程模式中,任务是按照顺序依次执行的,直到一个任务完成后才能进行下一个任务,这样会阻塞程序的执行,降低程序的性能。而在异步编程模式中,任务是通过回调函数或 Promise 进行管理的,每个任务可以在后台执行,不会阻塞程序的执行。

a. 使用回调函数、Promise或async/await等方式来处理异步操作

使用异步编程模式是提高性能的重要手段之一,其中回调函数、Promise和async/await是常用的处理异步操作的方式。

回调函数是异步编程中最基本的模式之一,通过在异步操作完成后执行的回调函数中处理结果。 例如:

function fetchData(callback) {
  // 模拟异步操作
  setTimeout(() => {
    const data = { name: 'John', age: 30 };
    callback(data);
  }, 1000);
}

fetchData((data) => {
  console.log(data);
});

Promise是ES6中引入的一种处理异步操作的方式,通过链式调用then方法来处理结果。 例如:

    function fetchData() {
      return new Promise((resolve, reject) => {
        // 模拟异步操作
        setTimeout(() => {
          const data = { name: 'John', age: 30 };
          resolve(data);
        }, 1000);
      });
    }

    fetchData()
      .then((data) => {
        console.log(data);
      });

async/await是ES8中引入的异步编程方式,通过在异步函数中使用await关键字等待异步操作完成并返回结果。 例如:

    async function fetchData() {
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 1000));
      const data = { name: 'John', age: 30 };
      return data;
    }

    (async () => {
      const data = await fetchData();
      console.log(data);
    })();

这些异步编程方式可以避免JavaScript中的阻塞操作,提高代码的性能和可维护性。

b. 使用setTimeout或setInterval来延迟执行某些操作

使用setTimeout和setInterval可以延迟执行某些操作,从而避免阻塞主线程。setTimeout是在一定时间后执行一次指定的函数,而setInterval会每隔一定时间重复执行指定的函数。

下面是使用setTimeout和setInterval的示例:

// 使用setTimeout延迟执行某些操作
setTimeout(function() {
  // 执行需要延迟的操作
}, 1000); // 延迟1秒执行

// 使用setInterval重复执行某些操作
let timerId = setInterval(function() {
  // 执行需要重复执行的操作
}, 1000); // 每隔1秒重复执行

// 可以使用clearInterval清除setInterval的定时器
clearInterval(timerId);

需要注意的是,如果setTimeout或setInterval的回调函数执行时间过长,会导致阻塞主线程,因此需要谨慎使用。此外,由于定时器的精度不是非常高,因此在处理关键任务时,建议使用更精确的方式来处理异步操作。

转载自:https://juejin.cn/post/7208466455879598117
评论
请登录