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!

相關文章
相關標籤/搜索