【问题标题】:Iterating through a function call遍历函数调用
【发布时间】:2018-03-19 11:39:13
【问题描述】:

在 C++ 中以乏味的方式编写循环多年后

for(int i=0; i<N; ++i) {
    ...
}

使用迭代器变得非常好

for(it i=v.begin(); i<v.end(); ++i) {
    ...
}

并最终转向范围迭代器

for(auto i:v) {
    ...
}

在 JavaScript 中也可以使用for,其风格几乎相同 (减去类型声明和前/后增量运算符)到 上面的第一个。

不过,在所有这些中,for 仍然存在。 D3.js 库演示了另一种选择。可以通过编写来遍历数组

d3.select("body")
  .selectAll("p")
  .data([4, 8, 15, 16, 23, 42])
  .enter().append("p")
  .text(function(d) { return "I’m number " + d + "!"; });

这里enter 变异为for 循环。 documentation 很好地解释了连接的客户端视图。我缺少的是一个 (函数式编程?)风格的独立示例 将函数调用转换为迭代。

毫无疑问,这不是 D3.js 独有的。这正是我遇到这个成语的地方。

你能推荐几行独立的 JavaScript 代码吗? 通过函数调用演示迭代?

【问题讨论】:

    标签: javascript c++ d3.js functional-programming


    【解决方案1】:

    这是Array.prototype.forEach的示例实现:

    function foreach(array, cb) {
        for (var i = 0; i < array.length; ++i)
            cb(array[i], i, array);
    }
    
    foreach([2,8,739,9,0], (n, i) =>
        console.log("number: %s\nindex: %s\n", n, i));
    

    我肯定不用用勺子喂你吧?

    function array_iterator(array) {
        var i = 0;
    
        function next() {
            return array[i++];
        }
    
        function head() {
            return array[i];
        }
    
        function tail() {
            return array[array.length-1];
        }
    
        function more() {
            return i < array.length;
        }
    
        function done() {
            return !more();
        }
    
        function reset() {
            i = 0;
        }
    
        return { next, head, tail, done, more, reset };
    }
    
    var nums = [3,34,4];
    
    var iter = array_iterator(nums);
    
    while (iter.more()) {
        console.log(iter.next());
    }
    

    【讨论】:

    • 就这样?没有车/cdr(头/尾)?
    • @Sebastian car/cdr 方法仅适用于单链表结构。这些在函数式语言中确实很常见,但是当您感兴趣的只是迭代(概念上)数组中的所有元素时,它们实际上效率很低,因此在 Lisp、ML 和 Haskell 之外您不会发现它们太多。但是一定要读一些关于这些语言的书,这是非常值得的。
    【解决方案2】:

    我想到了至少几个内置函数。

    地图()

    这个很明显。

    [1, 2, 3]
      .map(someNumber => someNumber * someNumber)
      .map((powered, index) => index + "::" + powered);
    
      // --> [ "1::1", "2::4", "3::9" ]
    

    链很好,对吧?接受一些输入并生成由元素组成的结果,这些元素通过逐元素应用函数计算得出。

    建议:尽可能尝试使用纯函数(对相同的输入产生相同的结果,尽可能不要改变原始集合,也不要产生任何副作用)。

    forEach()

    这个函数也遍历数组的所有元素,并应用一个函数,没有返回任何东西。因此,它只能结束一个调用链,而不能用于进一步的链接。

    [1, 2, 3, 4]
      .forEach(number => console.info(number));
    

    建议:forEach() 非常有用,当我们想要编写一些代码会导致被迭代的集合中的每个条目产生副作用时。

    过滤器()

    过滤器函数使用一个谓词,用于从谷壳中筛选小麦。谓词为您要在下一个“阶段”处理的项目定义标准。

    [null, undefined, 0, 1, 2, 3, NaN, "", "You get the idea"]
      .filter(Boolean)
      .map(filteredElement => filteredElement + "!")
    
      // --> [ "1!", "2!", "3!", "You get the idea!" ]
    

    建议:尽可能使用纯函数。 IE。除了与过滤逻辑本身直接相关的事情之外,不要在 filter 中做任何其他事情。

    Object.keys() 和 Object.entries()

    当我们需要迭代对象的键或键值对而不是数组的元素时,这两个函数很有帮助。

    const targetObject = { a: 1, b: 2, c: 3 };
    Object
      .keys(targetObject)
      .map(key => key + "=" + targetObject[key])
    
      // --> [ "a=1", "b=2", "c=3" ]
    

    这样也可以达到同样的效果

    Object
      .entries({ a: 1, b: 2, c: 3 })
      .map((key, value) => key + "=" + value)
    
      // --> [ "a=1", "b=2", "c=3" ]
    

    建议:在使用Object.keys(...) 时,您可能需要使用Object.hasOwnProperty(...)。详情请见documentation

    查找()

    这几乎是微不足道的。让我们搜索与谓词匹配的项目。搜索是“从左到右”的,只要找到第一个“匹配”就停止。

    [1, 5, 10, 15]
      .find(number >= 7)
    
      // --> 10
    

    findIndex() 函数可以在我们寻找与谓词匹配的元素位置时使用。

    some() 和every()

    这些函数检查是否

    a) 至少有一个元素与谓词匹配;要么 b) 每个元素都匹配一个谓词。

    const arrayOfNumbers = [2, 4, 6, 8, 10];
    
    arrayOfNumbers.every(number => number % 2 === 0); // --> true
    arrayOfNumbers.every(number => number % 2 === 1); // --> false
    
    arrayOfNumbers.some(number => number > 1);        // --> true
    arrayOfNumbers.some(number => number <= 1);       // --> false
    

    reduce() 和`reduceRight()`

    本快速回顾中要提到的最后一个功能是获取事物列表并将其聚合为单个结果的函数。

    [-1, 0, 1, 2, 3]
      .filter(value => value >= 0) // [0, 1, 2, 3]
      .map(value => value + 1)     // [1, 2, 3, 4]
      .reduce((subTotal, currentValue) => subTotal + currentValue, 5);
    
      // --> 15
    

    建议:尽可能使用纯函数。


    普遍适用的性能说明。在我的基准测试中(手头没有它们),手写的for 循环总是比forEachmap 和其他迭代函数快。除非性能受到严重影响,否则我仍然更喜欢这些功能。这有两个主要原因:1)更容易避免一次错误; 2) 代码更具可读性,因为每个单独的函数在数据处理流程中定义了一个独立的步骤,从而使代码更简单,更易于维护。


    我希望,这是对一些内置链式 JavaScript 函数的概述。更多的是described here。看看concat()sort()fill()join()slice()reverse()——我也经常使用它们。

    如果您需要first()last() 之类的东西,您将无法在本机函数中找到它们。要么自己写,要么使用第三方库(例如lodashrambda.js)。

    【讨论】:

      猜你喜欢
      • 2020-10-11
      • 1970-01-01
      • 2014-09-11
      • 1970-01-01
      • 2011-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-13
      相关资源
      最近更新 更多