• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

在JavaScript中使用迭代器和生成器来优化代码

在JavaScript中使用迭代器和生成器来优化代码  第1张

介绍

您是否曾经需要遍历列表,但该操作需要花费大量时间才能完成?您是否曾经因为操作占用过多内存而导致程序崩溃?当我尝试实现一个生成素数的函数时,这发生在我身上。 

生成多达 100 万个素数所花费的时间比我希望的要多得多。但是产生高达 1000 万的数字是不可能的。我的程序会崩溃或挂起。我已经在使用 Eratosthenes 的筛子,它在生成素数方面应该比蛮力方法更有效。

如果您发现自己处于类似情况,您可以尝试使用不同的算法。有些搜索和排序算法在较大的输入上效果更好。缺点是这些算法可能更难立即掌握。另一种选择是使用不同的编程语言。 

编译语言可能能够更快地处理代码。但是使用另一种语言可能不切实际。您也可以尝试使用多个线程。再一次,它可能不实用,因为您的编程语言必须支持这一点。

幸运的是,有了javascript,还有另一种选择。如果您有一项计算密集型任务,您可以使用迭代器和生成器来获得一些效率。迭代器是某些 JavaScript 集合的属性。 

迭代器通过让您一次一个地使用列表中的项目来提高效率,就好像它们是一个流一样。生成器是一种可以暂停执行的特殊函数。调用生成器允许您一次生成一个数据块,而无需先将其存储在列表中。

迭代器

首先,让咱们回顾一下在 javaScript 中循环遍历集合的不同方式。表单的循环for (initial; condition; step) { ... }将执行其主体中的命令特定次数。同样,只要条件为真,while 循环就会执行其主体中的命令。 

您可以使用这些循环通过在每次迭代时递增索引变量来遍历列表。迭代是循环体的执行。这些循环不知道列表的结构。它们充当计数器。

for/in循环和for/of循环旨在迭代特定的数据结构遍历数据结构意味着您正在逐步遍历其每个元素。for/in循环遍历纯 JavaScript 对象中的键循环遍历可for/of迭代对象的值。什么是可迭代的?简单地说,可迭代对象是具有迭代器的对象。可迭代的示例是数组和集合。迭代器是对象的一个属性,它提供了一种遍历对象的机制。

迭代器的特别之处在于它如何遍历集合。其他循环需要预先加载整个集合以便对其进行迭代,而迭代器只需要知道集合中的当前位置。 

您可以通过调用迭代器的 next 方法访问当前项。下一个方法将返回当前项目的值和一个布尔值,以指示您何时到达集合的末尾。以下是从数组创建迭代器的示例。

const alpha = ['a','b','c'];
const it = alpha[Symbol.iterator]();

it.next();  //{ value: 'a', done: false }
it.next();  //{ value: 'b', done: false }
it.next();  //{ value: 'c', done: false }
it.next();  //{ value: undefined, done: true }

您还可以使用循环迭代迭代器的值for/of当您知道要访问对象中的所有项目时,请使用此方法。这就是您将如何使用循环遍历上一个列表的方式:

for (const elem of it){
    console.log(elem);
}

为什么要使用迭代器?当处理列表的计算成本很高时,使用迭代器是有益的。如果您有一个非常大的数据源,如果您尝试对其进行迭代,可能会导致程序出现问题,因为必须加载整个集合。 

使用迭代器,您可以分块加载数据。这更有效,因为您只操作您需要的列表部分,而不会产生处理整个列表的额外成本。 

例如,您已经从文件或数据库中加载了数据,并且您希望逐步在屏幕上显示信息。您可以从数据创建一个迭代器,并设置一个事件处理程序以在每次事件发生时抓取一些项目。这是一个这样的实现可能看起来像的例子:

let posts = load(url);
let it = posts[Symbol.iterator]();

function loadPosts(iterable, count) {
    for (let i = 0; i < count; i++) {
        display(iterable.next().value);
    }
}

document.getElementById('btnNext').onclick = loadPosts(it, 5);

发电机

如果你想建立一个集合,你可以用一个生成器来做。生成器函数可以通过在每次迭代时暂停执行来一次返回一个值。当您创建生成器的实例时,可以使用迭代器访问这些项目。这是创建生成器函数的一般语法:

function *genFunc() {
    ...
    yield value;
}

*表示这是一个生成器函数关键字暂停咱们的yield函数并提供生成器在特定时刻的状态。为什么要使用发电机?当您想通过算法在集合中生成一个值时,您将使用生成器。如果您有一个非常大或无限的集合,它特别有用。让咱们看一个例子来了解这对咱们有什么帮助。 

假设您已经构建了一个在线台球游戏,并且您希望将玩家与游戏室进行匹配。您的目标是生成可以从 2,000 名玩家列表中选择两个不同玩家的所有方式。从列表中生成的两人组合['a', 'b', 'c', 'd'] 将是ab, ac, ad, bc, bd, cd这是使用嵌套循环的解决方案:

function combos(list) {
    const n = list.length;
    let result = [];
  
    for (let i = 0; i < n - 1; i++) {
        for (let j = i + 1; j < n; j++) {
            result.push([list[i], list[j]]);
        }
    }
    
    return result;
}

console.log(combos(['a', 'b', 'c', 'd']));

现在尝试使用包含 2,000 个元素的列表执行该函数。(您可以使用将数字 1 到 2,000 添加到数组中的 for 循环来初始化列表)。现在运行代码时会发生什么? 

当我在在线编辑器中运行代码时,网页崩溃。当我在 chrome控制台中尝试时,我可以看到输出缓慢打印出来。但是,我的计算机的 CPU 开始超速运行,我必须强制退出 Chrome。这是使用生成器函数的修改后的代码:

function *combos(list) {
    const n = list.length;
    for (let i = 0; i < n - 1; i++) {
        for (let j = i + 1; j < n; j++) {
            yield [list[i], list[j]];
        }
    }
}

let it = combos(['a', 'b', 'c', 'd']);
it.next();

另一个例子是,如果咱们想在斐波那契数列中生成无穷大的数字。这是一种实现:

function *fibGen() {
    let current = 0;
    let next = 1;
    
    while(true) {
        yield current;
        let nextNum = current + next;
        current = next;
        next = nextNum;
    }
}

let it = fibGen();

it.next().value;    //0
it.next().value;    //1
it.next().value;    //1
it.next().value;    //2

通常,无限循环会使您的程序崩溃。fibGen函数能够永远运行,因为没有停止条件。但是由于它是一个生成器,因此您可以控制执行每个步骤的时间。

审查

当您想要增量处理集合时,迭代器和生成器会派上用场。您可以通过跟踪集合的状态而不是集合中的所有项目来提高效率。集合中的项目一次评估一个,集合其余部分的评估延迟到以后。 

迭代器提供了一种有效的方式来遍历和操作大型列表。生成器提供了一种有效的方式来构建列表。当您要使用复杂算法或实施并行编程来优化代码时,您应该尝试这些技术。


文章目录
  • 介绍
  • 迭代器
  • 发电机
  • 审查