Ela's Notes
So Be It


Hitokoto ·
PHP/JavaScript中的闭包,回调和立即执行
Elatis   PHP, JavaScript   802 | 文章字数: 5470 字

总算是理清这几个概念的区别了QAQ

匿名函数

使用闭包,回调,立即执行一般都需要用到匿名函数.所谓匿名函数就是在声明函数时没有声明名字的函数.
下面分别列出了php,js,c++中的普通函数和匿名函数,可对比一下它们的区别
PHP:

<?php
function foo(){
    // 一般函数
};
function(){
    // 匿名函数
};

JavaScript:

function foo(){
    // 一般函数
};
(function(){
    // 匿名函数
});

C++:

void foo(){
    // 一般函数
}
[]() -> void { 
    // 匿名函数
}

其中C++的匿名函数最前面的中括号的作用是将外部变量传入,当然这不是本篇的重点,就不讲解了.

如果只是像上面那样声明一个匿名函数,你就会发现一个问题.
那就是:没法调用啊!调用函数当然需要函数名,但是匿名函数并没有名字
当然闭包,回调和立即执行都是调用匿名函数的方法.

立即执行函数

一般函数就像一个保温杯,你可以重复使用它.而立即执行函数就像一个一次性纸杯,使用一次之后就可以丢了,因为没人想带着它跑来跑去.
而只使用一次的纸杯自然不需要给它起个名字
立即执行函数也一样,所以立即执行函数一般都是匿名函数(或者说只有定义并立即执行的匿名函数才叫立即执行函数?).
js/php的立即执行函数的写法如下
PHP:
在php7中,写法如下:

<?php
(function(){
    // do something
})();

在php7之前,只能这么写:

<?php
call_user_func(function(){
    // do something
});

JavaScript:
js中的立即执行函数也有两种写法:

(function(){ 
    // do something
})();

(function(){ 
    // do something
}());

回调函数

回调的英文是Callback,有回电话的意思.
但是回调这个翻译..我到底该说好还是不好呢
说它不好是因为咋一听感觉太高端了,让人感觉是个晦涩的概念
但为什么我又说好呢,因为当你知道了回调是什么意思后,你就知道这个翻译有多牛逼了
回调的意思就是.....回头再调用
没错,回调函数就是和立即函数相对的一种函数.

  • 一般定义回调函数并不要求立即执行,而是在程序需要的时候再执行.
  • 而且回调函数特指由其它函数调用的函数,由开发者调用的话就是普通函数了
  • 回调函数一般被当作参数传给另一个函数

首先来看一下回调函数的写法吧
不过就如上面所说的,回调函数被当作参数,所以需要首先定义一个需要函数作为参数的函数
PHP:

<?php
function foo($Callback){
    return $Callback();
};

JavaScript:

function foo(Callback){
    return Callback();
};

然后我们将一个函数传给它,在函数foo内,被传入的这个函数就被叫做回调函数了
PHP:

<?php
$callback = function(){
    echo 'WDNB!';
};
foo($callback);

JavaScript:

let callback = function(){
    console.log('WDNB!');
};
foo(callback);

当然我们还可以直接传入一个匿名函数(js类似我就只举php的例子啦

<?php
foo(function(){
    echo 'WDNB!';
});

那么回调函数到底有什么用呢?
最重要的就是灵活.
回调函数除了返回值类型一般不改变(一般在头注释中说明),内部的处理方式可以是任意的.
比如我们在使用sort(这里讨论的是C++中的sort),要自定义排序方式,可以将一个用于比较的函数传给它,sort就会严格按照比较函数中的比较方式来进行排序了

还有一点就是降低函数与函数间的耦合性
我们扔给函数一个回调函数,由函数来调用它,并获取返回值进行进一步处理.
而回调函数内部是如何处理的,是相对独立的
我们写程序时调用系统自带的函数,一般不会去考虑它是怎么实现的(调用个sort还需要会快排归并梳排堆排希尔之类的吗?)
而回调函数是由函数调用的,也就是由机器调用的,机器对回调函数的看法就如同我们对普通函数的看法:只关心执行的结果(也就是返回值),不关心执行的过程
也就是说,使用回调函数的函数可以专注于它自己的事情,而回调函数是怎么处理它给的数据的,或是怎么给它所需要的数据的,使用回调函数的函数都不需要关心.

回调函数大量使用于各种API

还是在这里放上一个我自己写的样例吧,是用C++写的归并排序

/** 
 * 归并排序的递归部分
*/
template <typename T>
void mergeSort_Rec(T arr[], T temp[], int begin, int end, bool comparator(T, T))
{
    if (!(begin < end))
        return;

    int mid = ((end - begin) >> 1) + begin;
    mergeSort_Rec(arr, temp, begin, mid, comparator);
    mergeSort_Rec(arr, temp, mid + 1, end, comparator);

    int k = begin;
    int firstPart = begin;
    int secondPart = mid + 1;

    while (firstPart <= mid && secondPart <= end)
        temp[k++] = comparator(arr[firstPart], arr[secondPart]) ? arr[firstPart++] : arr[secondPart++];

    while (firstPart <= mid)
        temp[k++] = arr[firstPart++];
    while (secondPart <= end)
        temp[k++] = arr[secondPart++];

    for (k = begin; k <= end; ++k)
        arr[k] = temp[k];
}

/** 
 * 必须要定义小于号运算符才能使用
 * @param {array} a 任意类型数组
 * @param {int} len 要排序的长度
 * @param {function} comparator 用于比较大小的回调函数,缺省使用小于号比较
 * @return void
*/
template <typename T>
void mergeSort(T arr[], int len, bool comparator(T a, T b) = [](T a, T b) -> bool { return a < b; })
{
    T *temp = new T[len];
    mergeSort_Rec(arr, temp, 0, len - 1, comparator);
    delete[] temp;
}

闭包函数

  • 闭包函数和回调函数有点类似,只不过闭包函数的作用和回调函数不同
  • 闭包函数的写法和匿名函数完全相同,因为闭包函数就是被赋予了特殊使命的回调函数
  • 闭包函数可以理解为使用了它的上层函数的一些成员变量的函数
  • 这里的使用并不是作为参数调用,而是实实在在地使用了,类似c++中的引用传参

那么闭包函数到底有什么作用呢?
大家应该都知道,函数在被调用完成后,其内部的变量都会被回收
但是如果在某些情形,在接下来的运行中我们还需要使用之前的一些变量来达到某些目的呢?
这时候就需要闭包函数了
闭包函数一般作为一个函数的返回值,注意是函数作为返回值:
PHP:

<?php
function foo(){
    return function(){
        echo "WDNB!";
    };
};

JavaScript:

function foo(){
    return function(){
        console.log("WDNB!");
    };
};

当然可以先把这个函数赋值给一个对象:

<?php
function foo(){
    $Closure = function(){
        echo "WDNB!";
    };
    return $Closure;
};

这时候只是调用这个函数的话并不会输出任何东西.

<?php
foo();

不过这样写就会正常输出:

<?php
foo()();

输出:

WDNB!

为什么会这样呢?
别忘了之前我们是把一个函数当作了返回值
也就是说,foo();这种写法,得到的是一个函数
输出语句是写在闭包函数里的,然而得到函数并不等于调用函数,所以闭包函数并没有被调用.
而我们再在后面加一个括号,就相当于获取了一个函数,接着又调用它了.别忘了()写在函数后面是函数被调用的标志

然而我们并没有让闭包函数使用任何变量,自然无法让它尽到给局部变量续命的职责
那么我们现在就让它使用它外层的函数的变量
先看看php中是怎么写的

<?php
function foo()
{
    $i = 1;
    return function () use(&$i) {
            return $i++;
        };
}

注意function ()后面那个use(&$i),那个&是不是很像C++中的引用传参?

然后来看看js

function foo(){
    let i = 1;
    return function(){
        return i++;
    };
};

??? 并没有给闭包函数传值啊?
那是因为js中变量的作用域实在太广(var似乎都是全局的,let就比var小一点),就算不给闭包函数传入它也能正常使用
然后就来体验一下闭包函数的作用吧!(js类似,就举PHP的例子了)

<?php
function foo()
{
    $a = 1;
    return function () use(&$a) {
            return $a++;
        };
}
$Closure = foo();
for($i = 0;$i < 10;++$i){
    $ret = $Closure();
    echo strval($ret);
}

运行结果:

12345678910

很明显$Closure的返回值是一直自增的,说明里面的变量一直没有被销毁
也就是说原本要销毁的$a的生命得到了延续(没有任何其它意思!!!!)
这就是闭包函数的一个应用了

总结

  • 闭包,回调,立即执行函数的本质都是函数,或者说是匿名函数,只不过是根据它们使用的场景,它们的职责定义了三个概念
  • 立即执行函数就是定义后马上调用的函数
  • 回调函数就是由函数调用的,并且不是定义之后立即调用的函数
  • 闭包函数就是用于保存一些之后还要用到的局部变量,并且在使用的时候还能对其进行处理的函数

评论

发送失败 可能是您的发言太频繁或联系方式有误

提交评论

Theme LightWhite Made by Archeb With
自豪地使用Typecho
© 2017 - 2020 elatis.cn 版权所有 ICP证: 冀ICP备18008017号-1
全站共 19.11 W 字
博客已经运行了