學習一下閉包函式 - Closures

Littlesqx發表於2018-01-12

什麼是閉包?

PHP 文件這樣介紹:

匿名函式(Anonymous functions),也叫閉包函式(closures),允許 臨時建立一個沒有指定名稱的函式。最經常用作回撥函式(callback)引數的值。當然,也有其它應用的情況。

真的很含糊,什麼叫還有其它應用的情況。。。

找了 JavaScript 的文件:

Closures (閉包)是使用被作用域封閉的變數,函式,閉包等執行的一個函式的作用域。通常我們用和其相應的函式來指代這些作用域。(可以訪問獨立資料的函式)。

閉包是一個函式和宣告該函式的詞法環境的組合。從理論角度來說,所有函式都是閉包。

關鍵在於“作用域”、“環境”。

栗子:

<?php
$closure = function($name) {
    printf("Hello %s\r\n", $name);
};

$closure('World');
// Hello World

為什麼要用閉包?

閉包有一個特點,內部函式可以引用外部函式的引數和變數,引數和變數就不會被收回。

環境被儲存下來。

栗子:

<?php
$add = function() {
    $sum = 0;
    return function() use (&$sum): int {
        $sum += 1;
        return $sum;
    };
};

$test = $add();

echo $test(), "\n"; // 1
echo $test(), "\n"; // 2

一般函式區域性變數無法長久地儲存,而全域性變數可能造成變數汙染,所以我們希望有一種機制既可以長久地儲存變數又不會造成全域性汙染。

PHP 閉包的實現原理

目前是透過 Closure 類來實現,呼叫閉包函式的過程與 __invoke 魔術方法無關。

這是文件說的,但是很奇怪,下面的程式碼兩個輸出是一樣的。

<?php
$closure = function($name) {
    printf("Hello %s\r\n", $name);
};

$closure('World'); // Hello World
$closure->__invoke('World'); // Hello World

原理網上找了下,比較少相關文章。

與 JavaScript 閉包區別

最大的區別在於作用域。

可以看到前面加一的例子,使用了 use 關鍵字,PHP 裡叫做變數“繼承”,而且逐層傳遞(只能傳遞父級作用域的變數)。

這跟 PHP 語言特性有關, PHP 只有函式作用域、類作用域,而沒有塊級作用域。

<?php

$i = 1;
while($i--) {
    $j = 0;
}

echo $j, "\n"; // 0

奇葩有木有。。。

函式里訪問不了函式外部的變數,需要訪問的話可以透過傳參和 use 傳遞。

下面的例子可以更好看出區別。

舉栗子

使用閉包列印斐波那契數列。我們知道斐波那契數列有下面的規律:

# f(n) 表示數列中第 n 個數的值
f(n) = 0; (n = 0)
f(n) = 1; (n = 1)
f(n) = f(n-1)+f(n-2); (n >= 2)

使用遞迴的方法

<?php
function fibonacci(int $n): int {
    if ($n < 2) {
        return $n;
    }
    return fibonacci($n-1) + fibonacci($n-2);
}

echo fibonacci(10), "\n"; // 55

如果列印數列,那每一次都需要重複計算前面已經計算過的資料(只需要前兩個就好)。

可以使用閉包儲存上一次的執行環境。

PHP 版本

<?php
$fibonacci = function (): callable {
    $x = 0;
    $y = 1;
    return function () use (&$x, &$y): int {
        list($x, $y) = [$y, $x+$y];
        return $x;
    };
};

$f = $fibonacci();

for ($i = 0; $i < 10; $i++) {
    echo $f() , "\n";
}

JavaScript 版本

let fibonacci = _ => {
    let x = 0, y = 1;
    return _ => {
        [x, y] = [y, x+y];
        return x;
    };
};

let f = fibonacci();

for (let i = 0; i <= 10; i++) {
    console.log(f());
}

對,說了那麼多我就是想證明 JavaScript ES6 好簡潔。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
癩蛤蟆想吃燉大鵝

相關文章