JavaScript中回调

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014465934/article/details/89173951

1.先看一个回调函数的基本实现:

回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调.

// 声明一个函数,它的参数是一个函数fn
function example(fn)  {
    // 回调前,可以do something
    alert('我是回调前执行的代码');
    // 存在fn则直接用,不存fn在则用函数表达式的方式声明一个
    fn = fn || function() {};
    fn(); //  调用传进来的fn
}
// 调用传进来的fn
function callback() {
    alert('I am callback!')
}
example(callback); // 调用函数a

这个例子展示回调函数基本实现方法,实现回调的关键是把一个函数当成另一个参数。当然实践中很少用上面方式去使用回调函数,一般把一个匿名函数传入当成回调函数,这种方法在Javascript中使用非常广泛,下面是一个基本的例子。

// 声明一个函数,它的参数是一个函数fn
function example(fn) {
// 回调前,可以do something
    alert('我是回调前执行的代码');
    // 存在fn则直接用,不存fn在则用函数表达式的方式声明一个
    fn = fn || function() {};
    fn(); // 调用传进来的fn
}
// 把匿名函数当做参数
example(function()  {
    alert('I am callback!')
});

使用匿名函数作为参数,不仅可以极少代码,也让代码更好管理,更加灵活。

任何函数都是可以传入参数的,回调函数一样:

// 声明一个函数,它的参数是一个函数fn
function example(a, fn)  {
    // 回调前,可以do something
    alert(a);
    // 存在fn则直接用,不存fn在则用函数表达式的方式声明一个
    fn = fn || function() {};
    fn(m); // 调用传进来的fn
}
// 把匿名函数当做参数
var m = 'I am callback!';
example('我是回调前执行的代码',  function(m)  {
    alert(m)
});

2.Node.js中的回调

Node.js异步编程的直接体现就是回调。异步编程依托于回调来完成,但不能说使用了回调后程序就是异步化了。

回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

阻塞和非阻塞代码实例:

//阻塞代码
var fs = require("fs");

var data = fs.readFileSync('input.txt');

console.log(data.toString());
console.log("程序执行结束!");

//非阻塞代码
var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
    if (err) return console.error(err);
    console.log(data.toString());
});

console.log("程序执行结束!");

以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。

因此,阻塞是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。

3.回调函数的使用场合

资源加载:动态加载 js 文件后执行回调,加载 iframe 后执行回调,ajax 操作回调,图片加载完成执行回调,AJAX 等等。

DOM 事件及 Node.js 事件基于回调机制(Node.js 回调可能会出现多层回调嵌套的问题)。

setTimeout 的延迟时间为 0,这个 hack 经常被用到,settimeout 调用的函数其实就是一个 callback 的体现

链式调用:链式调用的时候,在赋值器(setter) 法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是 this 指针,如果要实现链式方法,可以用回调函数来实现。

setTimeout、setInterval 的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了 setTimeout 及 setInterval 的意义了,所以用 return 已经没有意义,只能使用 callback。callback 的意义在于将 timer 执行的结果通知给代理函数进行及时处理。

4.回调与同步/异步

回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。

回调不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。

/****************** 同步回调 ************************/
var syncFun = function(callback) {
    var start = new Date();
    while(new Date() - start < 1000) { // delay 1 sec
        ;
    }
    callback();
    console.log('同步方法返回'); // 2
};

syncFun(function() {
    console.log('这是同步回调'); // 1
});
console.log('同步方法会阻塞当前逻辑'); // 3

//输出结果(同步执行)
这是同步回调
同步方法返回
同步方法会阻塞当前逻辑

/****************** 异步回调 ************************/
var asyncFun = function(callback) {
    setTimeout(callback, 1000); // delay 1 sec
    console.log('异步方法返回'); // 4
};
asyncFun(function() {
    console.log('这是异步回调'); // 6
});
console.log('异步方法不会阻塞当前逻辑'); // 5

//输出结果(执行完同步,再执行异步)
异步方法返回
异步方法不会阻塞当前逻辑
这是异步回调

分析:
第一个同步的例子中,传入的回调函数在同步方法(syncFun)运行完成后才被调用,调用后同步方法结束并返回。
而在异步的例子中,异步方法调用后立即返回,异步事件完成后(这里用setTimeout超时事件模拟,实际还可以是Ajax、用户行为事件等异步事件),传入的回调函数才会被调用,在此期间,代码执行逻辑不会被阻塞。
请注意同步方法执行顺序(1~3)和异步方法执行顺序(4~6)的差别。

5.同步/异步、阻塞/非阻塞

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。

所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。

而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

总结:阻塞和非阻塞,应该描述的是一种状态,同步与非同步描述的是行为方式。

6.轮循与回调

你有事去隔壁寝室找同学,发现人不在,你怎么办呢?
方法 1,每隔几分钟再去趟隔壁寝室,看人在不
方法 2,拜托与他同寝室的人,看到他回来时叫一下你

前者是轮询,后者是回调。

那你说,我直接在隔壁寝室等到同学回来可以吗?
可以啊,只不过这样原本你可以省下时间做其他事,现在必须浪费在等待上了。把原来的非阻塞的异步调用变成了阻塞的同步调用。

JavaScript 的回调是在异步调用场景下使用的,使用回调性能好于轮询。

参考文章:
https://www.cnblogs.com/ys-wuhan/p/7027945.html
http://www.runoob.com/nodejs/nodejs-callback.html
http://wiki.jikexueyuan.com/project/brief-talk-js/callback-function.html
https://juejin.im/post/594b3607128fe100650355c7

猜你喜欢

转载自blog.csdn.net/u014465934/article/details/89173951