编程一些较难理解的概念----匿名函数、回调函数、闭包 js、go php的区别

编程界崇尚以简洁优雅为美,
很多时候 如果你觉得一个概念很复杂,那么很可能是你理解错了。
上面这段话引用自 https://zhuanlan.zhihu.com/p/22486908 饥人谷前端学习指南

原理

匿名函数

我们之所以给一个东西取名字,是因为需要区分它以及为了多次使用,但是很多地方,函数只用一次,而且只有一个函数调用 无需区分 所以就使用匿名函数。不然代码量多的时候 你还要经常想着起名字 还有不能和已有的冲突就很麻烦

  • 匿名函数特别适合回调函数的情况 js中会具体讲
  • 匿名函数还可以用在成员方法,很多类定义后,内部方法是需要调用者自己传入的,这时候用匿名函数就比较方便
  • 在闭包中的应用

回调函数

定义:被当做参数使用的函数叫做 回调函数;传进去的目的仅仅是为了在某个时刻去执行它。
应用场景:

  • 用户需求千变万化,无法满足所有的情况。比如你要实现一个计算器功能,能够实现加减法。为了写在一个函数中,可能这样定义,function calculate(value1,value2,mode) 当mode为1时做加法,为2时做减法。但是客户需求是千变万化的,可能有一天,他需要加一个乘法,甚至要求value变成数组,把数组里的东西依次相加。所以,一旦我们需要做乘法和除法,就不得不修改函数体。而修改函数体 可能会出现意想不到的bug,可能影响其他使用这个函数的用户。 所以,我们无法满足所有的要求,为什么不让用户去写具体的实现呢?然后把参数传进去。比如php的array_map。而这时候由于函数只用一次,匿名函数就很适合用在这里。
    利用回调,可以在运行时将与组件的核心任务没有直接关系的功能插入到组件中。有了组件回调,你就赋予了他人在你不知道的上下文中扩展你的代码的权力。
    这里应用自java小白翻身

  • 避免写很多同名函数,多态服用。比如写了一个函数,判断xxx是否属于某个分组(要去数据库中查),后面需求增加了,要判断多个xxx,称之为xxxs,是否属于某个分组。这时候,可能判断逻辑很复杂,为了避免影响原来的逻辑,就又写了一个函数,但是两个函数其实功能是差不多的!这种情况多了之后,取得名字多的你都分不清应该用哪个了。比如java 不同类型转string?

  • 前端请求后台接口等场景,需要等待,为了不阻塞主线程,要分出一个分线程去网络请求,但是子线程有一个局限:一旦发射了以后就会与主线程失去同步,我们无法确定它的结束,如果结束之后需要处理一些事情,比如处理来自服务器的信息,我们是无法将它合并到主线程中去的。为了解决这个问题,JavaScript 中的异步操作函数往往通过回调函数(如success)来实现异步任务的结果处理(通知主线程完成是否也算回调函数的功能呢?)

A让B做一件事情,一种不停的去问B做完了没有。另一种就是A写一个接口,让B做完了去调用这个接口,即B做完了后去调用这个接口。然后A只需要实现这个接口,而这个接口的实现代码里就是B做完了的事情

闭包

闭包就是:一个函数,以及与它关联的环境。
闭包是一种封装技术,像类一样。类/对象是将n个函数和n个数据封装到一起,闭包是将1个函数和n个数据封装到一起。以JS为例,可以用闭包实现的功能,通常也可以用类/对象的方式等价的实现。

闭包是保存变量环境,闭包是指在创建时封装周围状态的函数,即便闭包所在的环境不存在了,闭包中封装的状态依然存在。
「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。

要理解闭包,你先需要知道JS是词法作用域,然后通常函数的作用域会在函数调用后销毁,那如果函数内部值被其他地方引用,这个函数执行完也不会被销毁。

通过引用变量从而阻止该变量被垃圾回收的机制

假设你现在有一个函数 f (x) = a + x 这个函数是不完整的,比如 f (1) = a + 1 你还差一个问题: a 是多少?有两个方法回答这个问题

  • 第一种叫“动态作用域”,a的值决定于函数调用时上下文中a的值,比如a = 1;v=f(1) ; 这里v为2
    动态作用域的问题是,函数每一次调用相同的参数未必返回相同的值,其返回值还取决于上下文的某些值
  • 第二种是“词法作用域”,a的值取决于函数定义时上下文中的值g (a) = lambda (x) a + x;f = g(2)这里函数g返回一个和上面函数f形式一样函数,a在此处为2,那么执行a = 1;v=f(1) ;这里v为3因为f要“记住”自己定义时a的值为2,所以实现时 f (x) = a + x 和 a = 2 被打包在一块,被称为“闭包”,意思是它是完整独立的,仅仅依靠调用时参数求值,不再依赖调用时的上下文
function outer() {
  const a = 1;
  function inner() {
    console.log(a); // 访问 outer 的 a(定义时已确定作用域链)
  }
  inner();
}
outer(); // 输出 1
function outer() {
  const a = 1;
  inner(a);
}
function inner(a) {
    console.log(a); // 访问 outer 的 a(定义时已确定作用域链)
}
outer(); 

体会下两者的区别
第一个,没有形参,内部函数可以直接用外部的变量
而第二个,必须要将这个变量传到函数的形参中

在看闭包的形式:

function a() {
    var i = 0;//js函数内使用var定义的变量为函数私有变量,正常情况我们在函数外面是看不到它的
    return function () { //也可return function b(){
        console.log(i++);
    }
}
var b = a();
b();    //0
b();    //1
b();    //2

而第一种有什么特性呢?就在于 只要a传入了,后面执行第二次就和外面的a没有关系了!
而第二种,如果第二次执行,它还是要依赖外部的a,从头开始!

闭包是懒人的对象,对象是天然的闭包。

一个闭包包含三大块:

  • 外部函数
  • 内部函数
  • 引用内部函数的 return 语句

注意 核心是内部函数中要直接使用外部函数的变量而不能是形参的形式出现


function a() {
  	i=1
    return function (i) { //也可return function b(){
        console.log(i++);
    }
}
var b = a();
b(1);    //1
b(2);    //2
b(3);    //3

如果你变成这种形式,那就和直接调没太多区别了

关于内部函数不能执行
比如

i=1
return function () { 
	console.log(i++);
}
//和
return function () { 
	console.log(i++);
}()

这两者都是闭包,只不过下面这种 闭包之后执行一次
相当于

function a() {
    var i = 0;//js函数内使用var定义的变量为函数私有变量,正常情况我们在函数外面是看不到它的
    return function () { //也可return function b(){
        console.log(i++);
    }
}
var b = a();
b();    //0

相当于就只调用了一次
虽然无法体现闭包的作用 但它依然是个闭包

而且,闭包的变量是"指针传递"而非"值传递",所以,要注意变量的作用域,不是一定保护的

 var i = 0;
function a() {
    return function () { 
        console.log(i++);
    }
}
var b = a();
b();    //0
b();    //1

i=9;
b();//9

可以看到 这时候闭包就不能起到保护的作用了。
正常你写个int为参数类型的函数,普通函数是值传递。而闭包这种写法,它其实是"指针传递"
这个例子看golang教程 defer的奇怪之处那个地方体会会更深刻

历史渊源

许多编程语言(比如JavaScript)里的“闭包”,和代数里面的“对于运算封闭“,确确实实不太一样。话说当年我也困惑了好久,程序员社区怎么就选了闭包这样一个玄而又玄的术语呢?结果扎进故纸堆,一不小心搭进去了几天时间,才把来龙去脉疏离个七七八八。闭包(Closure)的含义在不同语言中也许会略有不同,但整体上大同小异。这个术语在编程领域的最早出现,大致可以追溯到Landin于1964年发表的The mechanical evaluation of expressions[1],里面有这么一句话:Closures are roughly the same as McCarthy’s “FUNARG” lists and Dijkstra’s PARD’s这句话解释了究竟什么是最早的闭包,(其实也约等于没解释,有兴趣的可以再去翻McCarthy和Dijkstra的文献,和今天理解的闭包已经基本一致了。)不过没说为什么这样一个概念要起名叫做“闭包”。反倒是Moses(1970)[2]代为效劳,“揣测”了一下Landin的意图:A useful metaphor for the difference between FUNCTION and QUOTE in LISP is to think of QUOTE as a porous or an open covering of the function since free variables escape to the current environment. FUNCTION acts as a closed or nonporous covering (hence the term “closure” used by Landin). Thus we talk of “open” Lambda expressions (functions in LISP are usually Lambda expressions) and “closed” Lambda expressions翻译过来,就是说闭包是相对于更早的“开包”(我胡诌的词,就是文中的open lambda expressions,早期Lisp的dynamically scoped函数)而言的,变量逃不出去,有点像是拿一个没有眼的盖子给严严实实地封闭住了,所以叫做闭包。是不是觉得有点啼笑皆非?闭包,Closure,这么高端的词汇,没想到起初竟然是个比喻?还有洞、没洞(non porous),还盖子、遮蔽(covering),真的是太土了!

应用场景

比如统计、游戏数据

比如实现统计某个按钮被点击次数,或者点赞次数,或者做一个游戏,命数。可以不被其他人恶意修改

function a() {
    var i = 0;//js函数内使用var定义的变量为函数私有变量,正常情况我们在函数外面是看不到它的
    return function () { //也可return function b(){
        console.log(i++);
    }
}
var b = a();
b();    //0
b();    //1
b();    //2

但要注意垃圾回收与内存泄露问题,比如我们用完后 要手动设置b=null

命数的例子

!function(){

  var lives = 50

  window.奖励一条命 = function(){
    lives += 1
  }

  window.死一条命 = function(){
    lives -= 1
  }

}()
工厂模式
function dabaoLiwu(liwu){
    console.log('装' +liwu+ '到打包盒');
    function jiadizhi(dizhi){
        console.log('打包盒加地址,地址为' +dizhi+'并且准备寄礼物,礼物为'+liwu);
    }
    return jiadizhi;
}

// 闺蜜礼物:口红
let guimiLiwu= dabaoLiwu('口红')
guimiLiwu('江苏省南京市XXXX王女士136XXXXX')

输出:

装口红到打包盒
打包盒加地址,地址为江苏省南京市XXXX王女士136XXXXX并且准备寄礼物,礼物为口红

类似工厂函数 返回却不调用,最终用的人再加参数

jiadizhi() 函数就是一个闭包,它可以在调用 dabaoLiwu 函数后的任何时间调用,另外它还可以访问最初调用 dabaoLiwu() 时的变量和参数,这样的话外部函数和内部函数都能访问礼物,但是地址只有内部函数可以访问。

更多 看golang的应用场景 写的比较好

分语言

JS----更经典的实现了这些东西

匿名函数:

匿名函数是因为很多地方不需要函数名字,因为函数只用一次 ;取名就很繁琐,同时取名多了后 会造成自己都分不清
匿名函数可以赋值给变量,,匿名函数特别适合作为函数或方法的回调。

首先我们声明一个普通函数:

//声明一个普通函数,函数的名字叫fn

function fn(){
    console.log("张培跃");
}

然后将函数的名字去掉即是匿名函数:

//匿名函数,咦,运行时,你会发现报错啦!
function (){
   console.log("张培跃");
}

到此,你会发现单独运行一个匿名函数,由于不符合语法要求,报错啦!解决方法只需要给匿名函数包裹一个括号即可:

//匿名函数在其它应用场景括号可以省略

(function (){
    //由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
    console.log("张培跃");
})

如果需要执行匿名函数,在匿名函数后面加上一个括号即可立即执行!

(function (){
    //此时会输出张培跃
    console.log("张培跃");
})()

倘若需要传值,直接将参数写到括号内即可:

(function (str){
    //此时会输出张培跃好帅!
    console.log("张培跃"+str);
})("好帅!")
匿名函数的应用场景

1、事件—可以不用给函数起名字 无需想着还要命名它

<input type="button" value="点我啊!" id="sub">

<script>
    //获得按钮元素
    var sub=document.querySelector("#sub");
    //给按钮增加点击事件。
    sub.onclick=function(){
        alert("当点击按钮时会执行到我哦!");
    }
</script>

2、对象
如1的例子,虽然οnclick=后面的函数不需要名字,但是作为对象的时候,要给对象一个属性的名字,不然怎么调用这个方法呢?但是这里的写法依然是匿名函数

var obj={
    name:"张培跃",
    age:18,
    fn:function(){  
        return "我叫"+this.name+"今年"+this.age+"岁了!";
    }
};

console.log(obj.fn());//我叫张培跃今年18岁了!

fn是对象中的名字 是必须有的 因为这个对象其他地方可能使用很多次 需要obj.fn的形式调用,但是具体的函数名是不重要的 ,如果我们传统方式写 就得写成 fn:tmp_fn_name ,然后再写tmp_fn_name的定义 很麻烦==

3、函数表达式:函数变量化

//将匿名函数赋值给变量fn。

var fn=function(){
    return "我是一只小小小小留下,怎么飞也飞不高!"
}

//调用方式与调用普通函数一样
console.log(fn());//我是一只小小小小留下,怎么飞也飞不高!

再比如golang的例子

var bytePool = sync.Pool{
	// 为 sync.Pool 的 New 字段赋值一个函数
    New: func() interface{} { //一个无参数、返回 interface{} 的函数
        return make([]byte, 1024)
    },
}

1、如果我们还要再给匿名函数赋个名字,那就有new和函数名两个了,但是后者显然没必要
2、new字段是有必要的 首先它是一个标识符,其次 我们需要一个字段承接这个函数,否则怎么传参呢?所以这就是函数变量化的意义

为什么要这么写 还有一个原因 看php匿名函数为什么要有名字那里

4、回调函数----和下一节回调函数的结合

setInterval(function(){
    console.log("我其实是一个回调函数,每次1秒钟会被执行一次");
},1000);

5、将匿名函数作为返回值

function fn(){
    //返回匿名函数
    return function(){
        return "张培跃";
    }
}

//调用匿名函数

console.log(fn()());//张培跃

//或
var box=fn();
console.log(box());//张培跃

这里不理解具体作用

6、模仿块级作用域-----似乎let实现了

块级作用域,有的地方称为私有作用域。JavaScript中是没有块级作用域的,例如:

if(1==1){//条件成立,执行if代码块语句。

    var a=12;//a为全局变量

}

console.log(a);//12

 

for(var i=0;i<3;i++){

    console.log(i);

}

console.log(i);//4

if(){}for(){}等没有自己的作用域。如果有,出了自己的作用域,声明的变量就会立即被销毁了。但是咱们可以通过匿名函数来模拟块级作用域:

 

(function(){

    //这里是我们的块级作用域(私有作用域)

})();

 

尝试块级作用域:

function fn(){

    (function(){

        var la="啦啦啦!";

    })();

    console.log(la);//报错---la is not defined

}

fn();
匿名函数的作用:

1、通过匿名函数可以实现闭包,关于闭包在后面的文章中会重点讲解。在这里简单介绍一下:闭包是可以访问在函数作用域内定义的变量的函数。若要创建一个闭包,往往都需要用到匿名函数。

2、模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。再者,在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。自此开发者再也不必担心搞乱全局作用域了。

给匿名函数命名–js函数声明与函数表达式的区别

Js中的函数声明是指下面的形式:

function functionName(){ 
} 

函数表达式则是类似表达式那样来声明一个函数,如:

var functionName = function(){ 
} 

可能很多朋友在看到这两一种写法时会产生疑惑,这两种写法差不多,在应用中貌似也都是可行的,那他们有什么差别呢?

事实上,js的解析器对函数声明与函数表达式并不是一视同仁地对待的。

  • 对于函数声明,js解析器会优先读取,确保在所有代码执行之前声明已经被解析,
  • 而函数表达式,如同定义其它基本类型的变量一样,只在执行到某一句时也会对其进行解析

所以在实际中,它们还是会有差异的,具体表现在,

  • 当使用函数声明的形式来定义函数时,可将调用语句写在函数声明之前,而后者,这样做的话会报错。
  • 后者还能实现闭包(因为需要让一个变量等于一个函数)
  • 和变量赋值 使用后者使得方法更像是一个属性(虽然不是必须 java就直接函数 声明的形式 重点应该还是上面的)

本质原因还是:js和php将匿名函数当成了一个对象

回调函数

//比如jaquery的这个例子,on里就用了回调函数!因为点击按钮后,谁知道你具体要干啥呢,只需要指定这个点击后运行你要的功能就行了。
$("#box").on('click',function(){  
	alert();
});
//ps1 有的会在function中加一个e形参 如$("#box").on('click',function(e) 其实e是jquery在定义方法的时候指明的 比如
function on(event,callback){
	if(event == 'click'){
		var e = "Hello World";
		callback(e);
	}
}
//ps2 jquert $函数是怎么实现的
var $ = function(id){ return document.getElementById(id); } 
这样就行了,可是有个问题,这个函数返回的是一个dom对象,而标准的dom元素是没有绑定事件的方法的。所以,我们不能直接返回dom,而应该返回一个json。(json虽然是后面的内容,这里先提前用一下吧)
我返回一个 json ,json的用处就大了,它是一个实实在在的对象,既有属性也有方法。在json中,属性和方法之间都是用逗号分隔的。

var $ = function(id){
	return {
		element : document.getElementById(id) ,
		on : function(event,callback){
			this.element['on' + event] = callback;
		}
	}
}

异步任务的结果处理 特别再请求网络的时候

$(document).ajaxSuccess(function(){
    alert("AJAX 请求完成");
});
function say (value) {
    alert(value);
}
alert(say);//不带括号,返回的是这个方法本身
alert(say('hi js.'));//带括号返回的是方法运行的结果
var a = 100;
1
是不是这样做的,那么定义一个函数不就是:

var fun = function(){   
   alert('你好!');
}

不是一个意思吗,不知道我这样写你是不是好理解一点呢?
我们在运用jQuery的时候,是不是总是写这样的代码:

$(function(){

});

很显然,这个就是回调函数,$本身就是一个函数的名字,没有道理不相信,我就问你,它是不是打了括号?我们把里面的 function(){} 去掉:

$();
1
是不是就变成这样了?那好,我就想请问一下了,你见过除了函数之外的什么东西要打括号吗?有没有,就问你一句话,有还是没有?只有函数才能打括号啊,你写一个var a = 10; 能打括号吗?所以,对jQuery来说,它本身就是一个函数,这一点要明确。
//带参数的例子 如果是强类型语言 里面还得写那啥
const request = function(url,method,callback){
		url:url,
		method: method,
		callback(response)
}

闭包

除了上面原理部分,js和其他语言有个很大的区别就是,

  • 首先js没有private关键字定义私有变量 里面的变量就分为globe和函数内部(用var声明,否则是globe的)所以可以用闭包模拟private
  • 而且js的面向对象比较模糊,能够直接通过function创建对象(详细看js教程-面向对象 那篇博客)

但是……??php有private 为啥也要用闭包呢?因为普通函数不是类?--------可能因为php接口是函数的形式出现的,而函数内部定义不了private成员?

函数可以认为是一个”类“,。闭包可以类比java的private变量!

一个简单的闭包如下:

function a() {
    var i = 0;//js函数内使用var定义的变量为函数私有变量,正常情况我们在函数外面是看不到它的
    return function () { //也可return function b(){
        console.log(i++);
    }
}
var b = a();
b();    //0
b();    //1
b();    //2

这个例子三次调用b()会分别输出0、 1、 2,是因为a()的变量对象被return的闭包函数引用着,所以i会一直留在内存中,并只能被闭包函数所访问。这个闭包函数被赋值给了b,所以b()能对i变量进行自增计算。

闭包是 JS 函数作用域的副产品。
换句话说,正是由于 JS 的函数内部可以使用函数外部的变量,所以这段代码正好符合了闭包的定义。而不是 JS 故意要使用闭包。

面向对象 构造函数的形式 实现类似private

function ShoppingCart() {
  let items = []; // 定义购物车中的商品列表

  // 添加商品到购物车
  this.addItem = function(item) {
    items.push(item);
  };

  // 从购物车中删除商品
  this.removeItem = function(index) {
    items.splice(index, 1);
  };

  // 查看购物车中的商品列表
  this.getItems = function() {
    return items;
  };
}
const cart = new ShoppingCart();

cart.addItem('苹果');
cart.addItem('香蕉');
console.log(cart.getItems()); // 输出 ["苹果", "香蕉"]

cart.removeItem(0);
console.log(cart.getItems()); // 输出 ["香蕉"]

以上就是使用闭包实现模块化开发的一个例子,通过使用闭包,我们将购物车模块的实现细节封装了起来,同时保护了数据的安全性,提高了代码的可维护性。

此外 还有一个场景是——采用函数引用方式的setTimeout调用

setTimeout的第一个参数一般是一个即将要执行的函数,第二个参数是一个延迟时间。

如果一段代码想要通过setTimeout来调用,那么它需要传递一个函数对象的引用来作为第一个参数,但这个函数对象的引用无法为将要被延迟执行的对象提供参数。此时可以调用另一个函数来返回一个内部函数的调用,将那个内部函数对象的引用传递给setTimeout函数,内部函数执行时需要的参数,在调用外部函数时传递给它,setTimeout在执行内部函数时无需传递参数,因为内部函数仍然能够防伪外部函数调用时提供的参数。

小心内存泄露

闭包可能导致的问题我们在本文开头的时候提到,如果不理解闭包,可能会写一些导致性能、内存泄露的代码,以下将写一个闭包导致内存泄漏的示例代码:

function createTimer() {
  let count = 0;
  let timer = setInterval(function() {
    count++;
    console.log(count);
  }, 1000);
  return timer;
}

let myTimer = createTimer();

这个代码中,createTimer() 函数创建了一个计时器,并返回计时器对象。这个计时器对象是一个闭包,它引用了 setInterval 函数中的 count 变量,导致 count 变量无法被垃圾回收。如果我们想停止计时器,可以调用 clearInterval(myTimer),但是这并不能清除 count 变量,因为它仍然被闭包引用着,无法被释放。如果创建很多这样的计时器对象,就会导致内存泄漏,使程序变得缓慢或不稳定。为了避免内存泄漏,我们应该注意清除不再使用的变量和函数。在这个例子中,可以使用如下代码来清除计时器和 count 变量:

function createTimer() {
  let count = 0;
  let timer = setInterval(function() {
    count++;
    console.log(count);
    if (count >= 10) {
      clearInterval(timer);
      count = null;
    }
  }, 1000);
}

createTimer();

这个代码中,我们添加了一个计数器,当计数器达到一定值时,就清除计时器和 count 变量,从而避免了内存泄漏问题。

php

php不像js
1、php偏向一个一个的函数,而js更为“整体”
2、php有die函数,js只能异常
3、php有private方法

php的讲解都以匿名函数为切入

本质----是对象

匿名函数的本质是对象,因此跟对象一样可将匿名函数赋值给某一变量
php中将匿名函数、闭包的概念混合到一起了。但实际上 它们有一些区别。
php闭包和匿名函数使用的句法和普通函数相同,不过别被这一点迷惑了,闭包和匿名函数其实是伪装成函数的对象,它的用法,当然只能被当作变量来使用了。

$greet = function(string $name){
    echo "hello {$name}";
}

$greet("jack") // hello jack
所有的匿名函数都是 Closure 对象的实例

$greet instanceof Closure // true



之所以能调用$closure变量,是因为这个变量的值是一个闭包,而且闭包对象实现了 __invoke()魔术方法,只要
变量名后面有 (),php就会查并调用__invoke() 方法。

匿名函数的作用

1 用 回调函数

PHP中有许“需求参数为函数”的函数,像array_map,usort,call_user_func_array之类,他们执行传入的函数,然后直接将结果返回主函数。好处是函数作为值使用起来方便,而且代码简洁,可读性强。
其中,https://blog.csdn.net/S_ZaiJiangHu/article/details/121567268我写的这个例子 讲解了call_user_fun和直接调用的区别。

$numberPlusOne = array_map(function($number) {
    return $number + 1;
}, [1, 2, 3]);
 
print_r($numberPlusOne);

在闭包之前, php开发者无法选择(因为这要求函数参数化传入另一个函数,而既然你实现了函数参数化 闭包也就自然而然了),只能单独创建具名函数,然后引用那个函数,这么做,代码执行的稍微慢一点, 而且把回调的实现和使用场所隔离开了

传统的php代码:这样需要定义一个函数的名字,但这个只用一次 没必要

function incrementNumber($number)
{
    return $number + 1;
}
 
$numberPlusOne = array_map('incrementNumber', [1, 2, 3]);
print_r($numberPlusOne);
//以上两个例子输出:Array ( [0] => 2 [1] => 3 [2] => 4 )
2、给函数命名

php不知道哪个版本起,不允许变量作为调用方法.

例如:

funciton a(){}
$f='a';//此时没有加括号 代表这里不是要运行它。但是为啥要这么写呢-----------是为了下面这个闭包的例子
$f();//会抛出错误
//但这样是可以的,就用到了匿名函数
$f=function(){
};
$f();
php的闭包 ——通过use 实现工厂模式 访问上层变量
//此处不用函数  $name=1  似乎更容易被理解。
function enclosePerson ($name) {
	//对象并没有什么父作用域可言,所以需要使用 use 来手动声明使用的变量,
	//此处的name本来是上层函数的参数,在下面这个函数中是不可见的,通过use就可以使用了
	return function ($doCommand) use ($name) {
		return $name . ', ' . $doCommand;
	}
}
$clay = enclosePerson('Clay');
echo $clay('get me sweet tea!');
$num = 1;
$func = function() use($num){
    $num = $num + 1;
    echo $num;
}
$func();  // 2
echo $num;  // 还是 1

如果要让匿名函数中的变量生效,需要使用引用传值

$num = 1;
$func = function() use(&$num){
    $num = $num + 1;
    echo $num;
}
$func();  // 2
echo $num;  // 2

Golang版

闭包

匿名函数可以访问其定义作用域内的变量,即使在外部函数已经返回后,这些变量也不会被销毁,因为它们被匿名函数“捕获”了。这种特性称为闭包(Closure)。

package main
 
import "fmt"
 
func main() {
    x := 100 // x是一个外部变量,在匿名函数中被“捕获”并使用。
    increment := func() int { // 定义一个匿名函数,它捕获了外部的x变量。
        x++ // 在这个匿名函数中修改x的值。
        return x // 返回x的当前值。
    }
    fmt.Println(increment()) // 输出:101,x被增加1。
    fmt.Println(increment()) // 输出:102,x再次被增加1。
}

上面这种不是返回一个函数的也算闭包?

package main
import "fmt"

func getSequence() func() int {  //后面的func() int 当成一体的 就是一个返回值!
   i:=0
   return func() int {   //实际上就是把这里原封不动抄上去了而已
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())//1
   fmt.Println(nextNumber())//2
   fmt.Println(nextNumber())//3
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())//1
   fmt.Println(nextNumber1())//2
}

应用场景

封装私有变量‌

闭包可以创建类似“私有变量”的效果,避免全局变量污染。

func counter() func() int {
    count := 0 // 私有变量
    return func() int {
        count++
        return count
    }
}

func main() {
    c := counter()
    fmt.Println(c()) // 1
    fmt.Println(c()) // 2
}
函数工厂(Function Factory)‌如不同等级的报错

根据参数生成不同行为的函数。

func makeLogger(prefix string) func(string) {
    return func(msg string) {
        log.Printf("[%s] %s", prefix, msg)
    }
}

func main() {
    infoLog := makeLogger("INFO")
    errorLog := makeLogger("ERROR")
    infoLog("程序启动")  // 输出: [INFO] 程序启动
    errorLog("发生错误") // 输出: [ERROR] 发生错误
}
‌回调函数(Callback)与高阶函数‌

闭包可以作为参数传递给高阶函数,例如自定义排序、遍历操作等。

//函数参数支持这种再传函数的方式
func processNumbers(numbers []int, callback func(int)) {
    for _, n := range numbers {
        callback(n)
    }
}

func main() {
    sum := 0
    // 闭包捕获 sum 变量
    addToSum := func(n int) {
        sum += n
    }
    processNumbers([]int{1, 2, 3}, addToSum)
    fmt.Println(sum) // 6
}

当你实现了函数作为参数传递给另一个函数 闭包也就自然实现了

‌延迟执行(Deferred Execution)‌

闭包常与 defer 结合使用,实现资源释放、错误处理等逻辑的延迟执行。

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    // 闭包延迟关闭文件
    defer func() {
        if err := file.Close(); err != nil {
            log.Printf("文件关闭失败: %v", err)
        }
    }()
    // 处理文件内容...
    return nil
}
中间件(Middleware)与装饰器模式‌

在 Web 框架中,闭包常用于中间件逻辑,例如日志记录、权限验证、性能监控等。

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next(w, r) // 调用原始处理函数
        log.Printf("请求 %s 耗时 %v", r.URL.Path, time.Since(start))
    }
}

// 使用中间件
http.HandleFunc("/", loggingMiddleware(handler))
‌生成器(Generator)与迭代器(Iterator)‌

闭包可以记住状态,用于生成序列或遍历集合。

// 斐波那契数列生成器
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        result := a
        a, b = b, a+b
        return result
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f()) // 0, 1, 1, 2, 3, 5...
    }
}
异步编程与并发控制‌

在 goroutine 中使用闭包时,需注意变量捕获问题。例如,循环中启动多个协程时,通过参数传递变量:

func main() {
    for i := 0; i < 5; i++ {
        go func(n int) { // 通过参数传递 i 的值
            fmt.Println(n)
        }(i)
    }
    time.Sleep(time.Second)
}
测试中的模拟(Mocking)‌

在单元测试中,闭包可以模拟外部依赖的行为。

func TestCalculate(t *testing.T) {
    mockDB := func(query string) int {
        return 42 // 模拟数据库返回固定值
    }
    result := Calculate(mockDB)
    if result != 42 {
        t.Errorf("预期 42,实际得到 %d", result)
    }
}

注意事项
‌变量捕获陷阱‌:在循环或并发中使用闭包时,需确保捕获的变量是预期的值(例如通过参数传递)。
‌内存泄漏‌:闭包可能导致变量无法被垃圾回收,需注意生命周期管理。
闭包的灵活性和强大功能使其成为 Go 开发中的重要工具,合理使用能显著提升代码的简洁性和可维护性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值