深入理解前端数据处理:方法、技巧与应用场景

430 阅读11分钟

前言

在前端开发中,处理数据是非常常见的需求。JavaScript 提供了多种方法来遍历和操作数组,每种方法都有其独特的用途和优势。本文将深入探讨 forwhileforEachmapreducefiltersomeeveryfindfor...infor...of 等方法的最佳实践和应用场景,并结合一些常用的算法和高级应用场景。

1. for 循环

它提供了最大的灵活性,可以随时控制循环的开始、结束和步进。 continue 语句用于跳过当前循环的本次迭代,直接进入下一次迭代。 breakretrun 语句用于完全终止循环。

应用场景

for 循环是最基本的循环方式,是我们平常使用最多的方法,适用于几乎所有的遍历场景。

示例代码
     const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
     for (let i = 0; i < array.length; i++) {
           if (i % 2 === 0) { //打印偶数
               continue;//过当前循环的本次迭代,直接进入下一次迭代。
            }
           if (i >= 8) { //打印到第八个元素
              break; //跳出循环 也可以用 return 终止循环, 但是使用 return ‘到达循环结尾’ 不会被打印
           }
           console.log(array[i]); //输出 2 4 6 8
       }
     console.log('到达循环结尾');

2. for...in

for...in 用于遍历对象的可枚举属性(包括原型链上的属性)。 它不保证遍历属性的顺序。所以不推荐用于数组的遍历。

应用场景
  1. 当需要遍历对象的属性来执行某些操作时,例如将对象的属性值进行格式化输出。
  2. 在处理配置对象或包含多个相关属性的对象时,获取并处理每个属性。
示例代码
const car = {
            brand: "BMW",
            model: "X5",
            year: 2023
        };

        for (let property in car) {
            if (car.hasOwnProperty(property)) {
                console.log(`${property}: ${car[property]}`); //输出 brand: BMW model: X5 year: 2023
            }
        }

3. for...of

for...of 用于遍历数组、字符串、MapSet 等具有迭代器接口的数据结构。它返回的是值而不是索引或键。

  1. 它只能用于可迭代对象,不能直接用于普通对象(除非对象实现了 @@iterator 方法)。
  2. 相比于传统的 for 循环,for...of 语法更加简洁和直观。
应用场景
  1. 遍历数组并对每个元素进行操作,无需关注数组的索引。
  2. 读取字符串中的每个字符。
示例代码
//1.  遍历数组
const fruits = ['apple', 'banana', 'cherry'];

for (let fruit of fruits) {
  console.log(fruit); //输出 apple banana  cherry
}
//2.  遍历字符串
const str = 'Hello';

for (let char of str) {
  console.log(char); //输出 H e l l o
}
//3. 遍历 Set
const mySet = new Set([1, 2, 3, 3, 4, 4, 5]); 
for (let num of mySet) { 
 console.log(num); //输出 1 2 3 4 5
}

4. while 循环

while 循环用于需要基于某个条件持续执行的场景,特别是在循环次数不确定时。

  1. 只要条件为真,循环就会一直执行。
  2. 循环体中的代码至少会执行一次,如果初始条件就为假,那么循环体不会被执行。
应用场景
  1. 当不知道确切的循环次数,但知道循环结束的条件时,适合使用 while 循环。
  2. 例如,等待某个异步操作完成后再停止循环。
示例代码
let i = 0;
const array = [1, 2, 3, 4, 5];
while (i < 3) {
  console.log(array[i]); //输出 1 2 3
  i++;
}

do...while 循环至少执行一次,然后检查条件是否为真。

        let j = 0;
        const array = [1, 2, 3, 4, 5];
        do {
            console.log(array[j]); //输出  1
            j++;
        } while (j <= 0);

5. forEach

forEach 用于对数组的每个元素执行一次给定的函数,更加简洁和语义化。

  1. 简洁直观,无需手动管理索引和循环条件。
  2. 不能使用 break  或 continue 或者 return 来中断循环。
应用场景
  1. 当需要简单地对数组中的每个元素执行一个操作,而不需要关心索引或中途停止时,forEach 非常方便。
  2. 例如,对数组中的每个元素进行数据转换或输出。
示例代码
            const users = [
                { name: 'name1', age: 25 },
                { name: 'name2', age: 30 },
                { name: 'name3', age: 35 }
            ];

            users.forEach((user,index) => {
                console.log(`${index}-${user.name} is ${user.age} years old.`);
                //输出 0-name1 is 25 years old. 1-name2 is 30 years old. 2-name3 is 35 years old.
            });

6. map

map 用于创建一个新数组,其结果是对原数组每个元素执行一个提供的函数后的返回值。

  1. map 方法会返回一个新的数组,原数组不会被修改。
  2. 它会按照原始数组的顺序依次处理每个元素。
应用场景
  1. 对数组中的元素进行统一的转换或计算,生成一个新的数组。
  2. 例如,将数值数组转换为字符串数组,或者对价格数组进行打折计算。
示例代码
          //示例1 处理价格
           const prices = [100, 200, 300, 400, 500];
           const discountedPrices = prices.map(price => price * 0.8); 
           console.log(discountedPrices) //[80, 160, 240, 320, 400]
           // 示例2 处理数组对象
           const users = [
              {name: 'name1', age: 25},
              {name: 'name2', age: 30},
              {name: 'name3', age: 35}
             ];
           const names = users.map(user => user.name);
           console.log(names); //输出 ['name1', 'name2', 'name3']

7. reduce

reduce 用于将数组中的所有元素通过一个累积函数合并为单一值,方法接受一个回调函数和一个初始值作为参数,并对数组中的每个元素依次执行回调函数,最终返回一个累积的结果。常用于求和、求积等操作。

  1. reduce 方法提供了强大的灵活性,可以根据具体的需求自定义累积的逻辑。
  2. 可以在回调函数中添加更多的逻辑判断和处理。
应用场景
  1. 求和:如上述示例,计算数组中所有数字的总和。
  2. 求积:计算数组元素的乘积。
  3. 也可以用来数据去重。
示例代码
//示例一
const numbers = [1, 2, 3, 4, 5];

// 计算数组元素的总和
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum);  // 输出: 15
// 示例二
const users = [
  {name: 'Alice', age: 25},
  {name: 'Bob', age: 30},
  {name: 'Charlie', age: 35}
];
const totalAge = users.reduce((accumulator, user) => accumulator + user.age, 0);
console.log(totalAge); //输出 90
// 示例三 去重
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((accumulator, currentValue) => {
  if (!accumulator.includes(currentValue)) {
    accumulator.push(currentValue);
  }
  return accumulator;
}, []);
console.log(uniqueArray); //输出 [1, 2, 3, 4, 5]


代码解析 reduce 方法通过一个累积函数迭代数组元素,并将其合并为单一值。accumulator 是累加器,currentValue 是当前元素。示例中,0 是初始值。

8. filter

filter 方法创建一个新数组,新数组中的元素是通过提供的函数实现的测试的原数组元素的子集。

  1. filter 方法不会修改原始数组,而是返回一个新的数组。
  2. 用于根据指定的条件筛选过滤出数组中的元素。
  3. 与 map 方法不同,filter 方法侧重于筛选元素,而 map 侧重于对元素进行转换。
  4. 与 reduce 方法相比,filter 主要用于产生一个符合条件的新数组,而 reduce 用于将数组元素累积为一个单一的值
应用场景

1.筛选出符合特定条件的元素,例如筛选出大于某个值的数字、包含特定字符串的对象等。 2.也可以用来数据去重

示例代码
// 示例一  过滤能整除2 的数据
const array = [1, 2, 3, 4, 5];
const evenArray = array.filter(element => element % 2 === 0);
console.log(evenArray); // [2, 4]
// 示例二  过滤年龄大于=30 的数据
const users = [
  {name: 'name1', age: 25},
  {name: 'name2', age: 30},
  {name: 'name3', age: 35}
];
const adults = users.filter(user => user.age >= 30);
console.log(adults); 
// 输出 [{name: 'name2', age: 30}, {name: 'name3', age: 35}]

//示例三 对象数组去重
const objects = [
  { id: 1, name: 'name1' },
  { id: 2, name: 'name2' },
  { id: 1, name: 'name3' },
  { id: 3, name: 'name4' },
  { id: 2, name: 'name5' }
];
const uniqueObjects = objects.filter((obj, index, self) => 
  index === self.findIndex(item => item.id === obj.id)
);

console.log(uniqueObjects); // 输出 [{"id":1,"name":"name1"},{"id":2,"name":"name2"},{"id":3,"name":"name4"}]

9. some

some 用于测试数组中的至少一个元素是否通过了提供的函数的测试。只要找到一个满足条件的元素,方法就会返回 true,否则返回 false

  1. 只要有一个元素满足条件,some 方法就返回 true ,否则返回 false 。
  2. 它会在找到第一个满足条件的元素后立即停止遍历数组。
  3. 与 every 方法相反,every 要求所有元素都满足条件才返回 true 。而 some 只要有一个满足即可。
应用场景
  1. 快速检查数组中是否存在满足特定条件的元素。
示例代码
const users = [
  {name: 'name1', age: 25},
  {name: 'name2', age: 30},
  {name: 'name3', age: 35}
];
const hasMinor = users.some(user => user.age < 18);
console.log(hasMinor); //输出 false

10. every

every 用于测试数组中的所有元素是否都通过了提供的函数的测试。如果所有元素都满足条件,则返回 true,否则返回 false

应用场景
  1. 验证数组中的所有元素是否都符合某种严格的规则
示例代码
// 示例一 检查所有元素都满足 被2 整除
const array = [2, 4, 6, 8];
const allEven = array.every(element => element % 2 === 0);
console.log(allEven); // true
// 示例2
const users = [
  {name: 'name1', age: 25},
  {name: 'name2', age: 30},
  {name: 'name3', age: 35}
];
const allAdults = users.every(user => user.age >= 18);
console.log(allAdults); // 输出 true


11. find

find 方法返回数组中满足提供的测试函数的第一个元素的值。如果没有找到匹配的元素,则返回 undefined

  1. 只返回第一个满足条件的元素,而不是所有满足条件的元素。
  2. 如果没有找到满足条件的元素,则返回 undefined 。
  3. 与 filter 方法不同,find 方法只返回第一个匹配的元素,而 filter 方法返回所有匹配的元素组成的新数组。
应用场景
  1. 在数组中查找具有特定属性值的对象。
示例代码
// 示例一
const array = [1, 2, 3, 4, 5];
const firstEven = array.find(element => element % 2 === 0);
console.log(firstEven); // 2

// 示例二
const users = [ 
 { id: 1, name: 'name1'}, 
 { id: 2, name: 'name2'},
 { id: 3, name: 'name3'} 
]; 

const foundUser = users.find(user => user.id === 2);
console.log(foundUser) // 输出 {id: 2, name: 'name2'}

12. findIndex

findIndex 方法返回数组中满足提供的测试函数的第一个元素的索引。如果没有找到匹配的元素,则返回 -1 。

应用场景
  1. 确定满足特定条件的元素在数组中的位置,以便进行后续的操作。
示例代码
const users = [
  { id: 1, name: 'name1' },
  { id: 2, name: 'name2' },
  { id: 3, name: 'name3' }
];

const foundUserIndex = users.findIndex(user => user.id === 2);

if (foundUserIndex!== -1) {
 console.log(users[foundUserIndex]) //输出 {id: 2, name: 'name2'}
}

一些常见高级应用场景

1.使用 filterincludes 计算两个数组的交集
const array1 = [1, 2, 3, 4, 5];
const array2 = [3, 4, 5, 6, 7];
const intersection = array1.filter(element => array2.includes(element));
console.log(intersection); //输出 [3, 4, 5]

2. 利用双端比较算法用于判断一个数组是否为回文(即从前往后和从后往前读都一样)
const isPalindrome = str => {
  let left = 0;
  let right = str.length - 1;
  while (left < right) {
    if (str[left] !== str[right]) {
      return false;
    }
    left++;
    right--;
  }
  return true;
};

console.log(isPalindrome('racecar')); //输出 true
console.log(isPalindrome('hello')); //输出 false

3.冒泡排序算法
const bubbleSort = array => {
  let n = array.length;
  let swapped;
  do {
    swapped = false;
    for (let i = 0; i < n - 1; i++) {
      if (array[i] > array[i + 1]) {
        [array[i], array[i + 1]] = [array[i + 1], array[i]];
        swapped = true;
      }
    }
    n--;
  } while (swapped);
  return array;
};

const array = [5, 3, 8, 4, 2];
console.log(bubbleSort(array)); //输出 [2, 3, 4, 5, 8]

4.快速排序算法
const quickSort = array => {
  if (array.length <= 1) return array;
  const pivot = array[Math.floor(array.length / 2)];
  const left = array.filter(x => x < pivot);
  const right = array.filter(x => x > pivot);
  return [...quickSort(left), pivot, ...quickSort(right)];
};

const array = [5, 3, 8, 4, 2];
console.log(quickSort(array)); //输出 [2, 3, 4, 5, 8]

5.洗牌算法随机排序
const shuffle = array => {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
};

const array = [1, 2, 3, 4, 5];
console.log(shuffle(array)); // 随机顺序

6.优化查询-二分查找
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const target = 7;

// 二分查找示例
const binarySearch = (arr, target) => {
  let left = 0, right = arr.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return true;
    else if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return false;
};

const exists = binarySearch(array, target);
console.log(exists); // true

总结

在我们前端日常开发中,合理选择数组遍历和处理方法可以大大提高代码的可读性和效率。forwhile 循环提供了最大的灵活性,forEach 更加简洁和语义化,mapreducefiltersomeeveryfind 等则提供了高层次的数据处理能力。for...infor...of 则适用于对象属性和迭代器对象的遍历。了解每种方法的特点和应用场景,有助于编写更加高效和优雅的代码。

希望通过本篇文章能再次巩固你的基础知识,提升开发效率,如果觉得本篇文章还不错,恳请来个收藏点赞。