什么是明确decalred' let'在一个带递归的for-loop变量上?

时间:2018-02-19 15:32:04

标签: javascript node.js for-loop recursion

所以我有一个函数的示例代码,它通过一个数组和它所有的子数组通过递归,并计算匹配找到的字符串的数量。

示例数组:

const array = [
    'something',
    'something',
    [
        'something',
        'something'
    ],
    'something',
    [
        'something',
        [
            'something',
            'something'
        ],
        'something',
        [
            [
                'something',
                'something',
                [
                    'anything'
                ]
            ],
            'something',
        ],
        [
            'something',
            [
                'something',
                'something',
                'something',
                [
                    'anything',
                    'something',
                    [
                        'something',
                        'anything'
                    ]
                ]
            ]
        ]
    ],
    'anything'
];

我的功能可以通过这个数组来计算其中的#34;"的数量。

此代码效果很好:

let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
    for(let i=0;i<array.length;i++){
        const item = array[i];
        if((typeof item) === 'string'){
            (item === stringMatch) ? somethingsFound++ : undefined;
        } else if ((typeof item) === 'object'){
            searchArrays(item, stringMatch);
        }
    }
}

searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);

控制台输出:

>Found 18 somethings

然而 这是我不理解的部分,需要一些解释。如果我删除let变量forloop上的i声明,只是隐式声明它i=0;<array.length;i++,那么我的函数会进入无限递归。我通过放置一个console.log("running search)语句来查看它。

在这种情况下,让我们做什么?我已经尝试过阅读它,但不能完全理解发生了什么,以及递归和forloop究竟是如何相互作用的。

这里是代码的失败代码以防万一,所有不同的是让我们声明:

let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
    for(i=0;i<array.length;i++){
        const item = array[i];
        if((typeof item) === 'string'){
            (item === stringMatch) ? somethingsFound++ : undefined;
        } else if ((typeof item) === 'object'){
            searchArrays(item, stringMatch);
        }
    }
}

searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);

谢谢! CodeAt30

3 个答案:

答案 0 :(得分:4)

  

...只是隐含地声明它i=0;

这不是声明,只是创建implicit global *。由于它是全局,所以对searchArrays的所有调用都会共享,因此您的外部调用过早结束(因为i已经被内部调用增加了。)< / p>

示例:

function withDeclaration(recurse) {
  for (let i = 0; i < 3; ++i) {
    console.log((recurse ? "Outer " : "Inner ") + i);
    if (recurse) {
      withDeclaration(false);
    }
  }
}
function withoutDeclaration(recurse) {
  for (i = 0; i < 3; ++i) {
    console.log((recurse ? "Outer " : "Inner ") + i);
    if (recurse) {
      withoutDeclaration(false);
    }
  }
}

console.log("With declaration:");
withDeclaration(true);
console.log("Without declaration:");
withoutDeclaration(true);
.as-console-wrapper {
  max-height: 100% !important;
}

故事的寓意:从不依赖隐式全局变量。在您需要它们的最里面的范围内声明您的变量。使用"use strict"使隐式全局创建成为错误。

* (这是我贫血的小博客上的帖子)

答案 1 :(得分:2)

当您使用“隐式声明”变量时,您将只有一个这样的变量,与您在递归树中的位置无关。它将是一个全局变量。

这将有效地破坏算法的逻辑,因为最深的递归级别会将i的值移到数组长度之外,然后当你在前一个递归级别回溯循环时会突然跳转到值i,可能会跳过应该处理的几个有效数组条目。

始终声明变量。

答案 2 :(得分:2)

TJ和Trincot在修复你的计划方面做得很好 - 我会尝试修复你的想法......

递归是一种功能性传承

递归是一种来自功能风格的概念。将它与命令式风格混合是新程序员的痛苦和困惑的源泉。

To design a recursive function,我们确定基础归纳案例

  • 基本情况 - 输入的first值为Empty - 如果输入为空,则显然没有匹配项,因此返回0 < / LI>
  • 归纳案例1 - first 为空,但 是一个数组 - 重复first plus 重复了rest的值
  • 归纳案例2 - first 为空且数组,因此它是一个普通值 - 如果{ {1}}匹配first,为匹配项返回1 加上在值match上重复出现的结果
  • 归纳案例3 - rest 为空,数组,也不匹配first - 重复match

由于这种实施,所有的痛苦都会从计划中消除。我们不关心本地状态变量,变量重新赋值,数组迭代器,递增迭代器或其他副作用,如rest

为了简洁起见,我分别用for'something'替换了数据中的'anything''A'

'B'

具有功能风格的

或将const Empty = Symbol () const searchArrays = (match, [ first = Empty, ...rest ]) => { /* no value */ if (first === Empty) return 0 /* value is NOT empty */ else if (Array.isArray (first)) return searchArrays (match, first) + searchArrays (match, rest) /* value is NOT array */ else if (first === match) return 1 + searchArrays (match, rest) /* value is NOT match */ else return searchArrays (match, rest) } const data = ['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B'] console.log (searchArrays ('A', data)) // 18 console.log (searchArrays ('B', data)) // 4 console.log (searchArrays ('C', data)) // 0编码为纯函数表达式 - 此程序相同但交换命令式searchArrays / if / else ifelse三元表达式的语句语法

return

没有魔法

上面,我们使用rest parameterdestructure输入数组。如果这让您感到困惑,那么在简化示例中查看它会有所帮助。注意const Empty = Symbol () const searchArrays = (match, [ first = Empty, ...rest ]) => first === Empty ? 0 : Array.isArray (first) ? searchArrays (match, first) + searchArrays (match, rest) : first === match ? 1 + searchArrays (match, rest) : searchArrays (match, rest) const data = ['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B'] console.log (searchArrays ('A', data)) // 18 console.log (searchArrays ('B', data)) // 4 console.log (searchArrays ('C', data)) // 0用于使我们的函数可以识别何时停止。

Empty

这是较新版本的JavaScript中包含的高级功能,但如果它让我们感到不舒服,我们就不必使用它。下面,我们重写const Empty = Symbol () const sum = ([ first = Empty, ...rest]) => first === Empty ? 0 : first + sum (rest) console.log (sum ([ 1, 2, 3, 4 ])) // 10 console.log (sum ([])) // 0而没有幻想的解构语法

sum

我们现在可以执行const isEmpty = (xs = []) => xs.length === 0 const first = (xs = []) => xs [0] const rest = (xs = []) => xs.slice (1) const sum = (values = []) => isEmpty (values) ? 0 : first (values) + sum (rest (values)) console.log (sum ([ 1, 2, 3, 4 ])) // 10 console.log (sum ([])) // 0isEmptyfirst函数并重新实现rest - 注意相似之处; 粗体

的变化
searchArrays

展开代码段,看它是否有相同的效果

const searchArrays = (match, values = []) =>
  isEmpty (values)
    ? 0
    : Array.isArray (first (values))
      ? searchArrays (match, first (values)) + searchArrays (match, rest (values))
      : first (values) === match
        ? 1 + searchArrays (match, rest (values))
        : searchArrays (match, rest (values))

具有强大的抽象功能

作为程序员,“遍历数据结构并对每个元素执行操作”是我们需要做的常见事情。识别这些模式并将它们抽象为通用的,可重用的函数是高级思维的核心,这使得编写更高级别的程序成为可能。

const isEmpty = (xs = []) =>
  xs.length === 0
  
const first = (xs = []) =>
  xs [0]
  
const rest = (xs = []) =>
  xs.slice (1)
  
const searchArrays = (match, values = []) =>
  isEmpty (values)
    ? 0
    : Array.isArray (first (values))
      ? searchArrays (match, first (values)) + searchArrays (match, rest (values))
      : first (values) === match
        ? 1 + searchArrays (match, rest (values))
        : searchArrays (match, rest (values))

const data =
  ['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']

console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0

这项技能并非自动生效,但有一些技巧可以帮助您实现更高层次的思考。在this answer我的目标是给读者一些洞察力。如果这种事情让你感兴趣,我建议你看看^ _ ^

递归警告

JavaScript还不支持尾调用消除,这意味着在编写递归函数时需要extra precaution。有关密切关注程序的代码示例,请参阅this answer