查看原文
其他

【第1029期】JavaScript 中的匿名递归

冉余 前端早读课 2019-06-01

前言

代码如果这么写,过一段时间之后自己还能明白?今日早读由 @ 冉余份以分享。

正文从这开始~

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

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

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

[ 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 返回最后一个表达式作为结果。

使用括号 () 包裹一个匿名函数表示其结果就是 匿名函数 本身。

(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, 2, 3] 传给最外层作用域

  • 整个函数作为参数传给上面函数

  • 这个函数接收下面函数作为参数 f 的值,然后自身调用

  • 2.将被调用被作为 3.的结果然后返回函数 4. ,该函数是满足最外层作用域的函数,因此接收输入数组作为 l 参数的值

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

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

注意,f 是一个闭包,所以我们只需要调用它就可以访问到操作输入数组的最里面的函数。

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

结论

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

话虽如此,你必须培养自己何时使用它们,何时不用的这样一种感觉。如果你的代码变得难以维护,那这可能会成为重构中一些好点子。

然而,理解这些基本技术对于创建清晰优雅的解决方案以及提升自我是至关重要的。

最后,@冉余曾分享过的文章:

【第969期】编写现代 JavaScript 代码

【第1002期】面向初学者的高阶组件教程

一件事可能跟你有关

第三届FEDAY 报名时间将在明天截止,有兴趣但还没报名的童鞋要抓紧了。

你不应该错过的前端技术会议

@ 早读君手上还有五张优惠券可抵 150 元,有兴趣的留言哈。

关于本文

译者:@ 冉余

译文:https://zhuanlan.zhihu.com/p/28421224

作者:@Simeon Velichkov

原文:https://dev.to/simov/anonymous-recursion-in-javascript

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存