Performance analysis of try catch statement in JavaScript

Performance analysis of try catch statement in JavaScript

Because of my last blog Array.prototype.forEach method detailed explanation proposed two ways to jump out of the forEach loop.
But the blogs on the Internet almost only mention the first method (try catch + throw method), and the second method (splice + return method) is thought of after understanding the principle of the forEach method.
So I want to compare the performance of these two methods, so I need to compare the performance of the splice method and the try catch statement.

//方法一:try catch + throw 方法
const arr = [1, 2, 3, 4];
try {
    
    
  arr.forEach((item) => {
    
    
    if (item === 2) {
    
    
      throw new Error(`值为${
      
      item}时跳出forEac循环`);
    }
    console.log(item); //只打印 1
  });
} catch (e) {
    
    
  console.log(e); //Error: 值为2时跳出forEac循环
}
console.log(arr); // [1, 2, 3, 4]
//方法二:splice + return 方法
//在迭代时 使用splice方法 删除数组中的元素
const arr = [1, 2, 3, 4];
let spliceArr = null;
arr.forEach((item, index) => {
    
    
  if (item === 2) {
    
    
    spliceArr = arr.splice(index); // 将 2 以后的元素全部删除 并赋值给spliceArr
    return;
  }
  console.log(item); // 1
});
arr.splice(arr.length, spliceArr.length, ...spliceArr); //将删除的元素拼接回去
console.log(arr); // [1, 2, 3, 4]

1. Basic introduction and principle of splice method

For the splice method, you can refer to the following two blogs.

After looking at the source code of the splice method, I found that a loop is called inside the splice method.
So if we simply use big O notation to analyze:

  • Method 1: The performance of try catch + throw is O(n).
  • Method 2: The performance of splice + return is O(n²).

However, after referring to the relevant information, we found that the try catch statement is also a very performance-consuming statement, so we need to analyze the try catch statement.

2. Performance analysis of try catch statement

Friends who are not familiar with the try catch statement can refer to the following three articles.

The performance analysis method I use mainly refers to the method in the blog of using try catch in JS to analyze the performance impact of code operation . There are five situations for discussion.

  • Blank group 1. Without try catch, it takes time to take data modulo 10 million times
  • Reference group 2: directly execute time-consuming code inside try
  • Reference group 3: Calling functions with time-consuming code inside try
  • Reference group 2: Execute time-consuming code directly inside the catch
  • Reference group 2: Calling a function with time-consuming code inside the catch

The results obtained in this blog are: when the operating environment is Chrome51, respectively 结果为 98.2ms;1026.9ms;107.7; 1028.5ms; 105.9ms.

But it is different from the results of my own experiments. It may be a problem of version and environment, because modern browsers will optimize our code. Please see the following content for my test results.

2.1 Operating environment: Node.js v16.16.0

The test code for Node.js is as follows:

//空白组1. 无 try catch 的情况下,对数据取模1千万次耗时
!(function () {
    
    
  var t = new Date(); //耗时代码开始
  for (var i = 0; i < 100000000; i++) {
    
    
    var p = i % 2;
  }
  console.log("1:", new Date() - t); //耗时代码结束
})();

//参照组2:在 try 内部直接执行耗时代码
!(function () {
    
    
  var t = new Date(); //耗时代码开始
  try {
    
    
    for (var i = 0; i < 100000000; i++) {
    
    
      var p = i % 2;
    }
    throw new Error();
  } catch (e) {
    
    }
  console.log("2:", new Date() - t); //耗时代码结束
})();

//参照组3:在 try 内部调用拥有耗时代码的函数
!(function () {
    
    
  var t = new Date(); //耗时代码开始
  function run() {
    
    
    for (var i = 0; i < 100000000; i++) {
    
    
      var p = i % 2;
    }
  }
  try {
    
    
    run();
    throw new Error();
  } catch (e) {
    
    }
  console.log("3:", new Date() - t); //耗时代码结束
})();

//参照组4:在 catch 内部直接执行耗时代码
!(function () {
    
    
  var t = new Date(); //耗时代码开始
  try {
    
    
    throw new Error();
  } catch (e) {
    
    
    for (var i = 0; i < 100000000; i++) {
    
    
      var p = i % 2;
    }
  }
  console.log("4:", new Date() - t); //耗时代码结束
})();

//参照组5:在 catch 内部调用拥有耗时代码的函数
!(function () {
    
    
  function run() {
    
    
    for (var i = 0; i < 100000000; i++) {
    
    
      var p = i % 2;
    }
  }
  var t = new Date(); //耗时代码开始
  try {
    
    
    throw new Error();
  } catch (e) {
    
    
    run();
  }
  console.log("5:", new Date() - t); //耗时代码结束
})();

The results of the five methods: all within 60-80ms , so I infer that it should be caused by modern browsers optimizing our code. You can run it in your local environment to see for yourself.

2.2 Operating environment: Chrome version (105.0.5195.127) or Microsoft Edge version (105.0.1343.42)

The test code is as follows:

  1. Without try catch, it takes time to take modulo data 10 million times **[blank group]**
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>1. 无 try catch 的情况下,对数据取模1千万次耗时[空白组]</title>
  </head>
  <body>
    <h1 id="h1"></h1>
    <script>
      //空白组1:[无 try catch 的情况下对数据取模1千万次耗时]
      !(function () {
      
      
        const h1 = document.querySelector("#h1");
        var t = new Date(); //耗时代码开始
        for (var i = 0; i < 100000000; i++) {
      
      
          var p = i % 2;
        }
        h1.innerText = `情况1:无 try catch 消耗性能为${ 
        new Date() - t}ms`; //耗时代码结束
      })();
    </script>
  </body>
</html>
  1. Execute time-consuming code directly inside try
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>2.将耗时代码用 try 包围,内联耗时代码</title>
  </head>
  <body>
    <h1 id="h1"></h1>
    <script>
      //参照组2:在 try 内部直接执行耗时代码
      !(function () {
      
      
        const h1 = document.querySelector("#h1");
        var t = new Date(); //耗时代码开始
        try {
      
      
          for (var i = 0; i < 100000000; i++) {
      
      
            var p = i % 2;
          }
          throw new Error();
        } catch (e) {
      
      }
        h1.innerText = `情况2:try 内部执行消耗性能为${ 
        new Date() - t}ms`; //耗时代码结束
      })();
    </script>
  </body>
</html>
  1. Calling a function with time-consuming code inside a try
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>3.将耗时代码用 try 包围,try调用耗时代码的函数</title>
  </head>
  <body>
    <h1 id="h1"></h1>
    <script>
      //参照组3:在 try 内部调用拥有耗时代码的函数
      !(function () {
      
      
        const h1 = document.querySelector("#h1");
        var t = new Date(); //耗时代码开始
        function run() {
      
      
          for (var i = 0; i < 100000000; i++) {
      
      
            var p = i % 2;
          }
        }
        try {
      
      
          run();
          throw new Error();
        } catch (e) {
      
      }
        h1.innerText = `情况3:try 内部调用消耗性能为${ 
        new Date() - t}ms`; //耗时代码结束
      })();
    </script>
  </body>
</html>
  1. Execute time-consuming code directly inside the catch
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>4.在 catch 内部直接执行耗时代码</title>
  </head>
  <body>
    <h1 id="h1"></h1>
    <script>
      //参照组4:在 catch 内部直接执行耗时代码
      !(function () {
      
      
        const h1 = document.querySelector("#h1");
        var t = new Date(); //耗时代码开始
        try {
      
      
          throw new Error();
        } catch (e) {
      
      
          for (var i = 0; i < 100000000; i++) {
      
      
            var p = i % 2;
          }
        }
        h1.innerText = `情况4:catch 内部执行消耗性能为${ 
        new Date() - t}ms`; //耗时代码结束
      })();
    </script>
  </body>
</html>
  1. Call the function with time-consuming code inside the catch
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>5.在 catch 内部调用耗时代码的函数</title>
  </head>
  <body>
    <h1 id="h1"></h1>
    <script>
      //参照组5:在 catch 内部调用拥有耗时代码的函数
      !(function () {
      
      
        const h1 = document.querySelector("#h1");
        var t = new Date(); //耗时代码开始
        function run() {
      
      
          for (var i = 0; i < 100000000; i++) {
      
      
            var p = i % 2;
          }
        }
        try {
      
      
          throw new Error();
        } catch (e) {
      
      
          run();
        }
        h1.innerText = `情况5:catch 内部调用消耗性能为${ 
        new Date() - t}ms`; //耗时代码结束
      })();
    </script>
  </body>
</html>

Results : The results of these five methods are similar. The time spent on the first run is more than 100ms, but the time spent on refreshing the page is about 60-80ms, which is similar to the result of the node.js environment.

3. Performance comparison of the two methods of jumping out of the forEach loop

The code for performance analysis is as follows:

  1. Method 1: try catch + throw method
//方法一:try catch + throw 方法
!(function () {
    
    
  const arr = new Array(100000).fill(0).map((item, index) => index + 1);
  console.time("testTime1"); //开始计时
  // const start = Date.now();
  try {
    
    
    arr.forEach((item) => {
    
    
      if (item === 50000) {
    
    
        throw 1;
      }
    });
  } catch (e) {
    
    }
  console.timeEnd("testTime1"); //结束计时
  // console.log("runtime:", Date.now() - start);
})();

2. Method 2: splice + return method

//方法二:splice + return 方法
!(function () {
    
    
  const arr = new Array(100000).fill(0).map((item, index) => index + 1);
  console.time("testTime2"); //开始计时
  // const start = Date.now();
  //当数组元素过多时  会报错 RangeError: Maximum call stack size exceeded
  let spliceArr = null;
  arr.forEach((item, index) => {
    
    
    if (item === 50000) {
    
    
      spliceArr = arr.splice(index); // 将 2 以后的元素全部删除 并赋值给spliceArr
      return;
    }
  });
  arr.splice(arr.length, spliceArr.length, ...spliceArr); //将删除的元素拼接回去
  console.timeEnd("testTime2"); //结束计时
  // console.log("runtime:", Date.now() - start);
})();

The results are as follows : the first method is obviously better than the second method, but the size of the gap is affected by the condition of jumping out of the loop and the size of the array , but the first method is generally better than the second method.

So when you develop: It is recommended to use method 1 to jump out of the forEac loop, but in fact we can not use the forEach method at all, because there are many traversal methods, and most of them perform better than the forEach method, such as: for loop. But everyone still has to choose the most suitable method according to the actual application scenario. Talking about performance regardless of the scenario is tantamount to playing hooligans .

But if it is asked during an interview: how to break out of the forEac loop?

If you can give a general answer, I believe it will definitely impress the interviewer.

epilogue

This is the best answer I know so far, and of course there may be some misunderstandings.

So if you have doubts about this article, you can leave a message in the comment area, and everyone is welcome to point out the wrong views in the article.

Code words are not easy, friends who find it helpful will like it, and follow it.

Guess you like

Origin blog.csdn.net/forward_xx/article/details/126943278