深入了解回调函数和回调地狱问题

认识JS中的函数对象

在JS中万物皆为对象,由于函数的特殊性,函数在JS解析和执行时,会被维护成一个对象,这就是要介绍的函数对象

  • 我们可以使用function关键字定义一个函数,并为每个函数指定一个函数名,通过函数名来进行调用。
//函数声明的三种方式

//第一种:普通方式声明(常用)
function f1(num1,num2){
    return num1 + num2
}

//第二种:使用变量初始化的方式声明
var f2 = function(num1,num2){
    return num1 + num2
}

//第三种:使用Function构造函数的方式(微信小程序不允许使用这种方法,new Function('return this')除外)
var f3 = new Function('num1','num2','return num1 + num2')
  • 函数是用Function()构造函数创建的Function对象。函数对象(Function)与日期对象(Date)、数组对象(Array)、字符串对象(String)等都称为内部对象,我们可以通过typeof( )函数查看一个对象的类型。
console.log(typeof (Function));  //function
console.log(typeof (Array));  //function
console.log(typeof (Object));  //function
console.log(typeof (new Array()));  //object
console.log(typeof (new Date()));  //object
console.log(typeof (new Object()));  //object
console.log(typeof (new Function()));  //function //小程序不能运行这行代码
console.log(new Function() instanceof Object);    //true

//由此可见,对象是通过函数创建的,而函数本身又是一个类型为Function的特殊对象。
//Function是所有函数对象的基础,而Object则是所有对象(包括函数对象)的基础。

回调函数

经过上一节的了解,在JS中函数也是一类对象,这意味着函数能够像对象一样被使用。既然函数实际上是对象:它们能被“存储”在变量中,能作为函数参数被传递,能在函数中被创建,能从函数中返回。

回调函数是从一个叫函数式编程的编程范式中衍生出来的概念。简单来说,函数式编程就是使用函数作为变量。函数式编程中的一个主要技巧就是回调函数。

简单来说回调函数是一个被作为参数传递给另一个函数的函数。

举个例子:你到一个商店去购物,刚好你想要的东西没有货,于是你在店员那里留下了你的电话,叫店员有货了就通知你,过了几天店里有货了,店员于是打电话联系你,然后你接到电话后就去店里取了货。在这个例子里面,你将号码留下并让店员在有货时通知你的这个行为就叫回调函数,店里后来有货了叫做触发了回调关联的事件,店员打电话通知你叫做调用回调函数,你到店里去取货叫做响应回调事件

1、常见的回调函数

(1)DOM事件回调

document.getElementById('btn').onclick = function(){
    alert('hello world!')
}

(2)定时器回调

setTimeout(function(){
    alert('hello world!')
},1000)

2、回调函数的特点

(1)将回调函数作为变量传递给触发函数,体现了变量的灵活性。可将通用的逻辑抽象,将回调函数作为专职的函数进行分离,提高代码的可维护性和可读性。

//方式一
function a(){
    console.log('a函数原本执行的内容');
    b() //直接在a函数内调用b函数
}
//这样子也能到达由a函数控制b函数执行的目的,但是将b函数写在a函数内就会被限制住了,失去了灵活性

//方式二
function a(callback){
    console.log('a函数原本执行的内容');
    callback();
}
function test(){ //这样子可以灵活地去在a函数执行时触发不同的回调函数
    a(b);
    a(c);
}

//两个回调函数
function b(){
	alert("我是回调函数b");
}
function c(){
    alert("我是回调函数c");
}

(2)回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。因此,回调本质上是一种设计模式。

(3)不会立即执行,回调就是一个函数被另一个函数调用的过程。例如:函数a有一个参数,这个参数是函数b,函数a会决定函数b在什么时候被执行。那么这个过程就叫回调,即函数b在某个特定的时间点被函数a回调(通常这个特定的时间点是在函数a中的异步任务执行完后)。

(4)回调函数的闭包,我们将一个回调函数作为变量传递给另一个函数时,这个回调函数在包含它的函数内的某一特定时间点执行,就好像这个回调函数是在包含它的函数中定义的一样。这意味着回调函数本质上是一个闭包。正如我们所知,闭包能够进入包含它的函数的作用域,因此回调函数能获取包含它的函数中的变量,以及全局作用域中的变量。

3、回调函数的使用

(1)使用命名或匿名的函数作为回调

function a(callback){
    console.log('a函数执行了')
    callback()
}

function b(){
    console.log('回调函数b执行了')
}

function test(){
    a(b) //使用命名的回调函数
    a(function(){ //使用匿名的回调函数
        console.log('匿名回调函数函数执行了')
    })
}

(2)传递参数给回调函数

var name = 'dj'

function a(name,callback){
    console.log('a函数执行了')
    callback(name)
}

function b(name){ //回调函数b需要一个参数
    console.log('回调函数b执行了')
    console.log('hello '+name)
}

function test(){
    a(name,b) //这里的第一个参数name就是第一行代码定义的name变量,在此时传入给到回调函数b使用;第二个参数就是回调函数b
}

(3)在执行前确保回调函数是一个函数,如果不确定传入的callback是否为一个函数,推荐用这种写法,先判定是否为函数再进行调用,以免出现不必要的报错。

function a(callback){
    console.log('a函数执行了')
    //确保callback是一个函数   
    if(typeof callback === "function"){
        callback();
    }
}

function b(){
    console.log('回调函数b执行了')
}

function test(){
    a(b)
}

回调地狱

1、同步任务与异步任务

同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。

//同步
console.log('1')
console.log('2')
console.log('3')
//输出结果为 1 2 3

//异步(以定时器为例)
setTimeout(function(){
	console.log('1');
},1000)
console.log('2')
//如果按照代码的编写顺序,应该先输出1再输出2,但实际输出为2 1

像定时器这种不阻塞后面代码执行的任务就叫异步任务。

2、回调地狱是什么?

根据上文我们可以得出一个结论:存在异步任务的代码,不能够确保按照顺序执行。

如果我们想要在异步任务里按顺序执行一段代码,必须要如下操作才能保证顺序正确:

//假设我们想要按顺序分开输出一段文字:面向对象面向君,不负Java不负卿
setTimeout(function () { //第一层
	console.log('面向对象');
	setTimeout(function () { //第二程
		console.log('面向君');
		setTimeout(function () { //第三层
			console.log('不负Java');
             setTimeout(function () { //第四层
                 console.log('不负卿');
             }, 500)
		}, 1000)
	}, 2000)
}, 3000)

//假设业务开发中有三个接口,每个接口都依赖于前一个接口的返回值,就会出现以下情况
request({ //第一次请求
  url: 'url1',
  data: 'data1',
  success(res1) {
    request({ //第二次请求
      url: 'url2',
      data: res1, //依赖于第一个接口的返回值res1
      success(res2) {
        request({ //第三次请求
          url: 'url3',
          data: res2, //依赖于第二个接口的返回值res2
          success(res3) {
            console.log(res3)
          }
        })
      }
    })
  }
})

//可以看到代码中的回调函数嵌套回调函数,这种情况就称为回调地狱

通俗易懂的说就是在异步JS里面过多的使用了回调函数,回调函数层层嵌套,使得代码可读性差,且后期不易于维护。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值