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

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

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岁了!

3、函数表达式----结合2对象的看

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

var fn=function(){

return "我是一只小小小小留下,怎么飞也飞不高!"

}

//调用方式与调用普通函数一样

console.log(fn());//我是一只小小小小留下,怎么飞也飞不高!

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

回调函数

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

//比如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;
		}
	}
}

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

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

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

$(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中没有public,private等修饰词,里面的变量就分为globe和函数内部(用var声明,否则是globe的)。闭包可以类比java的private变量!但是……??php有private 为啥也要用闭包呢?因为普通函数不是类?--------可能因为php接口是函数的形式出现的,而函数内部定义不了private成员

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

一个简单的闭包如下:

function a(){
  var  i=10;//js函数内使用var定义的变量为函数私有变量,正常情况我们在函数外面是看不到它的
  return function b(){ //其实这里可以写成匿名函数 不需要b
    console.log(i)
  }
}

var c=a();//a带括号,运行此处返回的是b函数(a函数运行的结果c();//执行b函数

从此处可以看出,其实闭包一句话总结就是:函数嵌套函数,然后外部函数能够获取到函数内部的私有数据;(由于js没有其他语言private等方法修饰符 所以要这么去实现)

a()函数执行完就马上被销毁了,但是这个函数的变量对象仍然被c所引用,所以它的变量对象不会被垃圾回收机制处理掉,而是会留在内存中。这就形成了一个闭包。最后执行c()依然能读取到已经被销毁的a函数的定义的i变量。
我们换个更清晰的例子:比如实现统计某个按钮被点击次数,或者点赞次数,或者做一个游戏,命数。可以不被其他人恶意修改

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

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

优缺点

好处:
其实,在函数外部我们想获取函数内部的变量,数据这些,是无法获取到的,而闭包正好满足了这一点。所以,闭包最大的好处就是,1、能够在函数外部获取到函数内部的私有数据;2、这些变量和数据会一直存在函数中,而且不会在调用函数之后被垃圾回收机制回收。
好处就是隐藏变量,然后搭起一条沟通的桥,闭包可以间接操作这个隐藏变量;并且让这个变量留在内存中而不会污染到全局变量。

坏处:

而由于函数的私有数据会一直存在内存中,这也将导致内存过多,内存消耗大。造成内存泄漏

所以,我们不应该滥用闭包,而为了解决这一缺点,我们可以在退出函数之前,将局部变量删除掉。(第一个例子设置c=null;第二个设置b=null)

ps 命数的例子

!function(){

  var lives = 50

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

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

}()

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

闭包的应用场景

闭包应用场景有:1、采用函数引用方式的setTimeout调用;2、小范围代替全局变量;3、有权访问私有变量和私有函数的公有方法。

67e0d19ece7fbd95f23f98e0bc3b1018.png

函数外部无法访问函数内部的局部变量,但函数内部的函数可以访问本函数内的局部变量,故通过闭包实现函数外部访问函数内部局部变量。但容易造成内存泄漏,应当谨慎使用。

闭包的使用场景:

1】、采用函数引用方式的setTimeout调用

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

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

a5026189bf12613146f28c45a783a913.png

2】、小范围代替全局变量

8c887f68e57729ea23b513b61a80aeec.png

3】、访问私有变量的特权方法?

特权方法:有权访问私有变量和私有函数的公有方法

私有变量包括:

1)、局部变量

2)、函数的参数

3)、函数内部定义的其他函数(闭包)

07d88f336deee492aed0ce803d5144f6.png

以上代码的构造函数中定义了两个特权方法:getName()、setName(),这两个方法可以通过对象访问,而且都有权访问私有变量name,但是在Person构造函数外部,没有任何方法可以访问name。由于这两个方法是在函数内部定义的,因此作为闭包能够通过作用域链访问到name

异常

异常是阻断代码运行

给匿名函数命名–函数声明与函数表达式

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

function functionName(){
}
这样的方式来声明一个函数,而函数表达式则是类似表达式那样来声明一个函数,如:

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

事实上,js的解析器对函数声明与函数表达式并不是一视同仁地对待的。对于函数声明,js解析器会优先读取,确保在所有代码执行之前声明已经被解析,而函数表达式,如同定义其它基本类型的变量一样,只在执行到某一句时也会对其进行解析,所以在实际中,它们还是会有差异的,具体表现在,当使用函数声明的形式来定义函数时,可将调用语句写在函数声明之前,而后者,这样做的话会报错。
后者还能实现闭包(因为需要让一个变量等于一个函数)
和变量赋值 使用后者使得方法更像是一个属性(虽然不是必须 java就直接函数 声明的形式 重点应该还是上面的)

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

php

php不像js
1、php偏向一个一个的函数,而js更为“整体”,所以php中
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和直接调用的区别。

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

这样需要定义一个函数的名字,但这个只用一次 没必要

$numbersPlusOne = array_map(function ($number) {
return $number + 1;
}, [1, 2, 3]);
print_r($numbersPlusOne);
2、给函数命名

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

例如:

funciton a(){}
$f='a';//此时没有加括号 代表这里不是要运行它。但是为啥要这么写呢-----------是为了下面这个闭包的例子
$f();//会抛出错误
//但这样是可以的,就用到了匿名函数
$f=function(){
};
$f();
通过use 实现工厂模式 访问上层变量---------------php真正的闭包在此处

??php有private 为啥也要用闭包呢?因为普通函数不是类?

下面这个例子,使得name变量被“保护”了起来 只能通过

//此处不用函数  $name=1  似乎更容易被理解。
function enclosePerson ($name) {
	//对象并没有什么父作用域可言,所以需要使用 use 来手动声明使用的变量,
	return function ($doCommand) use ($name) {//此处的name本来是上层函数的参数,在下面这个函数中是不可见的,通过use就可以使用了
		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
$numberPlusOne = array_map(function($number) {
    return $number + 1;
}, [1, 2, 3]);
 
print_r($numberPlusOne);PHP闭包之前, 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 )

随便想到的 以后补充进去

之所以要给匿名函数命名 也许是为了return 函数的时候 能给函数赋值?把它当成一个属性 不然不方便使用

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值