Vincent Ko

VK's Blog

Performance Improvement Tips: Optimize JavaScript Code Using Set and Array Methods

In the process of writing and maintaining front-end code, we often encounter scenarios where performance optimization is needed. Especially when dealing with arrays and objects, using the appropriate methods provided by JavaScript can not only improve the execution efficiency of the code, but also make the code more concise and understandable. In this article, we will explore how to optimize the code by using JavaScript's array and collection methods through a review of actual business code.

Business Scenario#

Let's start with a common scenario where we need to verify if the user has assigned the corresponding permissions for each selected permission scope. This requirement sounds straightforward, but in the actual implementation, it may involve a series of array operations. Here is the source code we want to optimize:

// Initial permission validation logic
const selectedObjItem = [];
this.selectedPermissions.forEach((item) => {
  !selectedObjItem.includes(item.action_id) && selectedObjItem.push(item.action_id);
});

const result = this.selectedScopes.every(scope => {
  if (scope.has_instance) {
    return selectedObjItem.includes(scope.action_id);
  }
  return true;
})

if (!result) {
  showMsg(this.$t('AUTH:请选择对象范围'), 'warning');
  return false;
}

The purpose of this code is to validate whether each selected scenario (selectedScopes) has selected a permission scope, and these permission scopes need to be found in selectedPermissions (each permission object has a unique identifier action_id).

Although the code seems to achieve the desired effect, upon careful analysis, we will find that there is room for optimization. In fact, we can improve the execution efficiency and make the code clearer by making a few simple changes. To do this, consider the following questions:

  1. Is there a more efficient way to achieve array recording and deduplication?
  2. Is every the best method for iterating and validating selections?
  3. Does the final result judgment have to wait until all iterations are completed?

Optimization#

1. Deduplication and Recording#

The first step of recording actually includes deduplication, mainly determined by the evidence judgment before push: !selectedObjItem.includes(item.action_id). Whenever deduplication is involved, we can think of using the efficient data type Set provided by ES6, because Set itself has the property of deduplication. Therefore, the code can be optimized as follows:

const selectedObjItem = new Set();
this.selectedPermissions.forEach((item) => {
  selectedObjItem.add(item.action_id);
});

Using Set for deduplication and checking the existence of elements with the has method is usually more efficient than using an array for deduplication (using push and includes to check). The reasons are as follows:

  1. Time Complexity:
    • The time complexity of the add and has methods of Set is usually constant time complexity O(1). This is because Set is implemented using a hash table internally, which allows for fast lookup and insertion of data.
    • The includes method of an array has a time complexity of O(n) because in the worst case, it needs to traverse the entire array to determine if an element exists.
  2. Deduplication:
    • The Set data structure is designed to be a collection of unique elements, and it automatically manages the uniqueness of elements. When you try to add an element that already exists in the Set, it will not perform any operations, so there is no need to manually write deduplication logic.
    • In the case of deduplication in an array, additional logic must be written (usually using includes to check and then push) to ensure that the added elements are unique, which increases code complexity and the number of operations performed.
  3. Code Simplicity:
    • Using Set makes the code more concise and easier to understand. Because the interface of Set is clear and represents the data structure of a set, it makes the code's intention clearer and more semantically meaningful.
    • Using array deduplication involves the use of includes and push methods, although they are both array methods and easy to understand, they need to be used together to achieve deduplication, which makes the code less intuitive.

The use and application of Set will be introduced in the following section.

2. Optimization of Validation#

Using the every method in the source code is acceptable, but it is also inefficient because every needs to iterate through all items. However, the purpose of this validation is to ensure that all items are selected, so if one item is not selected, we can return the error result early without checking the remaining items.

With this idea in mind, it is easy to think of the some method. The optimized code is as follows:

const result = this.selectedScopes.some(scope => {
  if (scope.has_instance) {
    return !selectedObjItem.has(scope.action_id);
  }
  return false;
});

The code logic and structure look the same as before, but the efficiency is improved because some stops iterating after returning true.

3. Complete Code#

The complete optimized code is as follows:

// Deduplicate and record action_id
const selectedObjItem = new Set();
this.selectedPermissions.forEach((item) => {
  selectedObjItem.add(item.action_id);
});

// Check if all options are selected
const result = this.selectedScopes.some(scope => {
  if (scope.has_instance) {
    return !selectedObjItem.has(scope.action_id);
  }
  return false;
});

if (result) {
  showMsg(this.$t('AUTH:请选择对象范围'), 'warning');
  return false;
}

Scenario Training#

Similarly, consider the following example:

Given batchAddSource is a two-dimensional array with values: [['a','b'], ['c','d'], ...]; selectedList is a subset of batchAddSource, representing the selected content. Now we need a method getUnselectedList that takes the selected content as input and returns the unselected content.

Approach#

The core of this problem is still deduplication. Combining the optimization ideas mentioned earlier, we can have the following thoughts:

  1. For comparing two-dimensional arrays, we can convert the inner arrays into a more suitable string type for comparison (join method).
  2. Use the has method of the Set data structure to check if an element exists.
const getUnselectedList = (selectedList, batchAddSource) => {
  const selectedSource = new Set(selectedList.map(item => item.join('.')))
  return batchAddSource.filter(item => !selectedSource.has(item.join('.')))
}

Conclusion#

JavaScript is a constantly evolving language. The introduction of new syntax and features in ES6 greatly improves the efficiency and readability of code writing. Here is a summary of the methods and applications mentioned:

  1. Using the new data structure Set: It is very convenient to achieve array deduplication and efficient recording of unique values using Set. Compared to traditional traversal and checking methods, the native mechanism of Set ensures the uniqueness of elements in the set, which plays a significant role in performance optimization, especially when dealing with large amounts of data.
  2. Using array methods wisely: ES6 provides array methods such as forEach, every, and some. Using these methods reasonably can make the code clearer and choose the most suitable iteration method according to different business logic requirements. For example, using some to return results early to avoid unnecessary iteration.
  3. Optimizing logical judgments: By optimizing the logic, such as early exit from loops, unnecessary calculations can be avoided, saving resources and time. When implementing validation operations on arrays or collections, considering boundary conditions and short-circuit logic can achieve more efficient code execution.
  4. Utilizing the concise syntax of ES6: New features such as arrow functions and template strings can make the code more concise, avoid redundant code, and improve code readability.

In practical programming practice, although each optimization point may only bring a small performance improvement, overall, these improvements can significantly improve the performance and user experience of the application. By combining the above optimization methods, we can write JavaScript code that is both efficient and highly readable.

Appendix: Explanation and Examples of Set#

Set is a special type of collection - a "set of values" (without keys), where each value can only occur once.

The previous section has introduced an application scenario of Set. Here, let's review the characteristics of this data type. Its main methods are as follows:

  • new Set(iterable) - Creates a set, and if an iterable object (usually an array) is provided, it will copy the values from the array into the set.
  • set.add(value) - Adds a value and returns the set itself.
  • set.delete(value) - Deletes a value. If the value exists when this method is called, it returns true; otherwise, it returns false.
  • set.has(value) - Returns true if the value exists in the set, otherwise returns false.
  • set.clear() - Clears the set.
  • set.size - Returns the number of elements.

The main feature of Set is that using the same value to call set.add(value) multiple times will not cause any changes. This is why each value in the Set can only occur once.

Counting (Statistics)#

Let's take a look at the following code. Can you understand what it does without comments, and the underlying principle?

const uid = () =>
  String(
    Date.now().toString(32) +
      Math.random().toString(16)
  ).replace(/\./g, '')

const size = 1000000
const set = new Set(new Array(size)
  .fill(0)
  .map(() => uid()))

console.log(
  size === set.size ? 'all ids are unique' : `not unique records ${size - set.size}`
)

Yes, it is actually checking the uid function to see if the generated ids are unique. Interestingly, this is how Set is used to check uniqueness. Why can we judge whether the created uuids are unique by comparing set.size with size?

It is because the values in the Set collection can only occur once. Therefore, when using map and uid() to generate values, if there are duplicate values, they will not be added to the set. By utilizing this feature, we can count whether there are duplicate values in the initial example and the number of duplicates:

  • By calling set.size, if all values are unique, the length of the set after mapping should be the same as the initially defined size.
  • Otherwise, the difference between size and set.size is the number of duplicates.

By using Set properly, not only can the code be more efficient, but it can also be more concise and easier to understand. Therefore, when encountering scenarios such as deduplication, statistics, and counting, if you previously hesitated and considered using arrays, you can pay more attention to whether Set can improve code efficiency.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.