51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在Web前端开发中,如何优化前端数组处理方法?

500.jpg

Web前端开发中,如何优化前端数组处理方法?也许你已经遇到过讨论,我们是否应该使用Array方法(filtermapreduce等)或循环(forfor...of,等)遍历集合。

关于数组的一些方法,我们分享过文章:

JS数组遍历之for循环方法应用解析

分享几种原生JS数组遍历的方法和应用

jquery数组封装使用方法分享(jquery数组遍历)

jquery进行数组遍历如何跳出当前的each循环

大家有兴趣的,可以去一一了解下。一段好的代码,会让你的程序性能更好且易理解,运行速度加快很多,相信大家都会知道这一点。

性能和可读性是选择一个而不是另一个的两个常见论点。让我们更好地看一下循环和Array方法之间的差异并进行比较,以便我们自己决定。


性能

在ECMAScript 5之前,我们所拥有的只是循环和jQuery $.each。哪种迭代最快的方法经常引起争议。从那时起,情况有了很大的改善。从那时起,我们智能手机中的CPU的性能就超过了计算机,我们现在可以在客户端做更多很棒的工作,而不会崩溃整个浏览器。尽管如此,Array仍然提高了方法的性能。

TIPs: 如果要遍历大量项目,则可能要避免使用Array方法。

在大多数情况下,它不应有太大的区别。对于大多数项目,很少要遍历一百多个项目的列表。您通常可以使用您认为更易读的内容。只要确保始终检查低端设备的性能即可。

可读性

我们可以同意可读性很重要。这也是主观的。例如,让我们使用带有隐式return((a, b) => a + b)的箭头函数。初学者可能会将其与逻辑表达式混淆。大于或等于运算符(>=)看起来确实相似。也许他们可能没有意识到函数返回值。毕竟,返回是隐式的。训练有素的眼睛会立即注意到箭头的功能。对于所有其他语法也可以这样说。由我们决定培训初学者开发人员或不使用功能,以便任何技能水平的开发人员都可以访问代码库。话虽如此,让我们比较一些循环和Array方法的可读性。

Array.prototype.forEach而且for...of都易于阅读。


posts.forEach((post) => console.log(post));
// or
for (const post of posts) {
  console.log(post);
}

等效的循环Array.prototype.filter有点显式。


const drafts = posts.filter((post) => post.isDraft);

// or
const drafts = [];

for (const post of posts) {
  if (post.isDraft) {
    drafts.push(post);
  }
}

也是一样Array.prototype.map


const publishDates = post.map((post) => post.date);

// or

const publishDates = new Array(posts.length);

for (let i = 0; i < posts.length; i++) {
  publishDates[i] = posts[i].date;
}

Array.prototype.reduce确实很棒,但是可能要花几秒钟来了解正在发生的事情。即使对于类似求和的运算,您可能也需要一秒钟才能理解。当reduce回调包含更多逻辑时,可读性迅速变差。


const totalWordCount = posts
  .reduce((result, post) => result + post.wordCount, 0);

// or

let totalWordCount = 0;

for (const post of posts) {
  totalWordCount += post.wordCount;
}

对于其他方法,例如Array.prototype.every,,Array.prototype.findArray.prototype.some,循环并不可怕,并且在具有早期返回功能的函数中效果更好。它们绝对比Array方法更明确。


const isEverythingPublished = posts.every((post) => post.isDraft);

// or

let isEverythingPublished = true;

for (const post of posts) {
  if (post.isDraft) {
    isEverythingPublished = false;
    break;
  }
}

// or in a function with early return

function getIsEverythingPublished(posts) {
  for (const post of posts) {
    if (post.isDraft) {
      return false;
    }
  }

  return true;
}

const isEverythingPublished = getIsEverythingPublished(posts);

如果您喜欢明确的话,循环可能会吸引您。对我来说,它们有点冗长,我个人更喜欢Array除之外的方法Array.prototype.reduce。但同样,可读性是主观的。这是私事。

特点与用途

尽管看起来循环和Array方法是做同一件事的两种方式,但是它们在显着方式上是不同的。

首先,Array方法是同步的。如果将异步函数传递给Array方法,它将不会等待异步函数完成。循环不依赖于回调,因此,如果您已经处于异步上下文中,则可以await在循环块中或使用for await...of循环。

考虑下面的示例。这是一劳永逸的操作,传递给异步函数的Array.prototype.forEach调用被调用,但forEach不等待该函数完成。

const urls = [
  'https://www.jiangweishan.com',
  'https://www.web176.com',
];

urls.forEach(async () => {
  const response = await fetch(url);

  if (!response.ok) {
    console.warn(`Unable to fetch ${url}`);
  }
});

在下面的示例中,我们将异步函数传递给Array.prototype.map调用。因为一个异步函数总是返回Promise时,返回值map调用的列表Promise。我们可传递给Promise.allPromise.allSettledPromise.race。这样,我们可以并行运行各种异步任务并等待结果。


async function assertPageIsOk(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`"GET ${url}" responded with ${response.statusCode}`);
  }

  return true;
}

const urls = [
  'https://timseverien.com',
  'https://timseverien.com/posts',
];

try {
  await Promise.all(urls.map(assertPageIsOk));
} catch (error) {
  console.error(error);
}

让我们使前面的示例稍微复杂一点。让我们依次调用每个URL,而不是同时触发所有请求(可能导致拒绝服务)。如果其中之一返回非2xx状态代码,我们可以停止序列。


function assertPagesAreOk(urls) {
  return urls.reduce(async (chain, url) => {
    await chain;

    return assertPageIsOk(url);
  }, Promise.resolve());
}

assertPagesAreOk(urls)
  .then(() => ...)
  .catch(() => ...);

这是上面的循环等效项:


async function assertPagesAreOk(urls) {
  for (const url of urls) {
    await assertPageIsOk(url);
  }

  return true;
}

assertPagesAreOk(urls)
  .then(() => ...)
  .catch(() => ...);

在以上所有示例中,我发现循环均等或更具可读性。

循环和Array方法之间的第二个重要区别是数组不能无限大。通常,我们认为无限循环是一件坏事,但它们可能非常有用。

想象一下,我们正在开发一个现代散点图库,该库中我们想在画布上绘制大量点。由于数组大小有限,因此我们可以通过允许用户传递生成器函数来利用迭代器。

import createScatterPlot from 'scatman';

createScatterPlot({
  data: *function() {
    while (true) {
      yield {
        x: Math.random(),
        y: Math.random(),
      };
    }
  },
});

生成器函数返回生成器对象,它是迭代器的超集。它们可以转换为数组(通过[...iterator]Array.from(iterator)),也可以循环使用。如前所述,数组的最大大小是一个限制。另一方面,在循环中,当循环需要下一个值时,会将各个值从迭代器中拉出。因为我们在示例中生成了无限点,所以将其转换为数组将冻结浏览器。


for (const { x, y } of data) {
  draw(x, y);

  await waitForNextFrame();
}

总结

我们已经知道循环速度更快,但是Array当方法内部处理某些逻辑(例如创建新数组或滤除特定值)时,方法可以提高可读性。我们还看到,在处理异步任务时,循环更具可读性,并了解到循环对于迭代未知数量的项目很有用,这在迭代器中很常见。

在大多数情况下,我们将使用它们中的任何一个来遍历少于一千个项目的数组,在这种情况下,我们应该强烈倾向于更具可读性的选项。我觉得Array除以外的方法更具可读性Array.prototype.reduce。我经常看到前者在使用这种方法时遇到了麻烦,而没有完全了解它的行为。

无论如何,没有人比您更了解您,您的团队或您的项目。也许您是在一个全栈的团队中工作的,他们对循环比对Array方法更熟悉。也许这些Array方法更符合您的函数式编程原理。您(和您的团队)将不得不弄清楚这一点。

赞(0)
未经允许不得转载:工具盒子 » 在Web前端开发中,如何优化前端数组处理方法?