JavaScript 中的匿名递归

原文:Anonymous Recursion in JavaScriptjavascript

做者:Simeon Velichkovjava

(
  (
    (f) => f(f)
  )
  (
    (f) =>
      (l) => {
        console.log(l)
        if (l.length) f(f)(l.slice(1))
        console.log(l)
      }
  )
)
(
  [1, 2, 3]
)

是的,这就是想要分享给你们的一个有趣的示例。这个例子包含如下特性:闭包),自执行函数箭头函数函数式编程匿名递归express

你能够复制粘贴上述代码到浏览器控制台,会看到打印结果以下:编程

[ 1, 2, 3 ]
[ 2, 3 ]
[ 3 ]
[]
[]
[ 3 ]
[ 2, 3 ]
[ 1, 2, 3 ]

说到函数式编程,这里有一个使用 Scheme) (JavaScript 借鉴过的其中一门语言)编写的相似例子:数组

(
  (
    (lambda (f) (f f))
    (lambda (f)
      (lambda (l)
        (print l)
        (if (not (null? l)) ((f f) (cdr l)))
        (print l)
      )
    )
  )
  '(1 2 3)
)

Unwind

像其余不少编程语言同样,函数调用是经过在函数名称后添加括号 () 来完成的:浏览器

function foo () { return 'hey' }
foo()

在 JavaScript 中咱们可使用括号包裹任意数量的表达式:闭包

('hey', 2+5, 'dev.to')

上面代码返回结果是 'dev.to',缘由是 JavaScript 返回最后一个表达式做为结果。app

使用括号 () 包裹一个匿名函数表示其结果就是 匿名函数 自己。编程语言

(function () { return 'hey' })

这自己并无用处,由于匿名函数没有命名,没法被引用,除非在初始化的时候当即调用它。函数式编程

就像是普通函数同样,咱们能够在其后面添加括号 () 来进行调用。

(function () { return 'hey' })()

也可使用箭头函数:

(() => 'hey')()

一样地,在匿名函数后添加括号 () 来执行函数,这被称为 自执行函数

闭包

闭包) 指的是函数和该函数声明词法环境的组合。结合 箭头功能,咱们能够定义以下:

var foo = (hi) => (dev) => hi + ' ' + dev

在控制台调用上述函数会打印 hey dev.to:

foo('hey')('dev.to')

注意,咱们能够在内部函数做用域访问外部函数的参数 hi

如下代码跟上述代码同样:

function foo (hi) {
  return function (dev) { return hi + ' ' + dev }
}

自执行的版本以下:

(
  (hi) =>
    (
      (dev) => `${hi} ${dev}`
    )
    ('dev.to')
)
('hey')

首先,将 hey 做为参数 hi 的值传给最外层做用域的函数,而后这个函数返回另外一个自执行函数。dev.to 做为参数 dev 的值传给内部函数,最后这个函数返回最终值:'hey dev.to'

再深刻一点

这个一个上述自执行函数的修改版本:

(
  (
    (dev) =>
      (hi) => `${hi} ${dev}`
  )
  ('dev.to')
)
('hey')

须要注意的是,自执行函数闭包) 用做初始化和封装状态,接下来咱们来看另一个例子。

匿名递归

回到咱们最初的例子,此次加点注释:

(
  (
    (f) => f(f) // 3.
  )
  (
    (f) => // 2.
      (l) => { // 4.
        console.log(l)
        if (l.length) f(f)(l.slice(1))
        console.log(l)
      }
  )
)
(
  [1, 2, 3] // 1.
)
  1. 输入函数 [1, 2, 3] 传给最外层做用域
  2. 整个函数做为参数传给上面函数
  3. 这个函数接收下面函数做为参数 f 的值,而后自身调用
  4. 2.将被调用被做为 3.的结果真后返回函数 4. ,该函数是知足最外层做用域的函数,所以接收输入数组做为 l 参数的值

至于结果为何是那样子,缘由是在递归内部有一个对函数 f 的引用来接收输入数组 l。因此能那样调用:

f(f)(l.slice(1))

注意,f 是一个闭包,因此咱们只须要调用它就能够访问到操做输入数组的最里面的函数。

为了说明目的,第一个 console.log(l) 语句表示递归自上而下,第二个语句表示递归自下而上。

结论

但愿你喜欢这篇文章,并从中学到了新的东西。闭包、自执行函数、函数式编程模式不是黑魔法。它们遵循一套易于理解和玩乐的简单原则。

话虽如此,你必须培养本身什么时候使用它们,什么时候不用的这样一种感受。若是你的代码变得难以维护,那这可能会成为重构中一些好点子。

然而,理解这些基本技术对于建立清晰优雅的解决方案以及提高自我是相当重要的。

Happy Coding!

相关文章
相关标签/搜索