一、介绍:
1、前提:在 js 中,函数也是对象,可以赋值给变量,可以作为参数放在函数的参数列表中,如:
var doSomething =function(a,b){return a + b;}
console.log(doSomething(2,3));
2、概念:
callback 是一种特殊的函数,这个函数被作为参数传给另一个函数去调用,这样的函数就是回调函数。回调,顾名思义,回头再调。回调与同步、异步并没有直接的联系,回调只是一种实现方式,既可以有同步回调,也可以有异步回调,还可以有事件处理回调和延迟函数回调。
3、语法:
在大多数编程语言中,函数的形参总是从外向内传递参数,但在JS中,如果形参碰到“关键字” callback 则完全相反,它表示从内向外反向调用某个外部函数。
二、举例
1、举例介绍:
(1)第一步:
var fs =require("fs");var c
function f(param){
console.log(param)}
function writeFile(){
fs.writeFile('input.txt', 'fs.writeFile 写入文件的内容', function (err){if(!err){
console.log("文件写入完成")
c =1}});}
c =0writeFile()f(c)
打印结果为0,因为程序运行到writeFile()这一行的时候,是一个比较耗时的IO操作,JS碰到这种操作并不会停在原地一直等待直到函数执行完毕,而是直接运行下一条代码(即f©),而此时 c = 1这一行代码其实并没有被执行到,所以打印出来的结果还是0 !,如果希望打印1,可以使用第二步的代码:
(2)第二步:
var fs =require("fs");var c
function f(param){
console.log(param)}
function writeFile(){
fs.writeFile('input.txt', 'fs.writeFile 写入文件的内容', function (err){if(!err){
console.log("文件写入完成")
c =1f(c)}});}
c =0writeFile()
这样结果是对的,但是改成这样并不完美,因为这么做就相当于将f()写死在writeFile()里了,如果此处我想根据不同的场景调用不同的函数还要写几个不同的writeFile(),而他们之间的区别仅仅是最后调用的那个函数不同,这里就体现callback的作用了(准确地说callback并不真的是Javascript里的关键字,只是大家都约定成俗把callback这个单词作为回调函数的默认选择)。看下使用回调函数后的代码:
(3)第三步:回调函数写法
var fs =require("fs");
function f(param){
console.log(param)}
function writeFile(callback){//callback,表示这个参数不是一个普通变量,而是一个函数
fs.writeFile('input.txt', 'fs.writeFile 写入文件的内容', function (err){if(!err){
console.log("文件写入完成")
c =1callback(c)// 因为我们传进来的函数名是f(),所以此行相当于调用一次f(c)}});}var c =0writeFile(f)// 函数f作为一个参数传进writeFile函数
经过改造后的代码出现了两次callback,第一个callback出现在writeFile的形参里,起定义的作用,表示这个参数并不是一个普通变量,而是一个函数,即所谓的“以函数为参数”。 第二个callback出现在c = 1下面,表示此处“执行”从形参传递进来的那个函数。这样一来,writeFile()函数在执行完毕之后到底调用哪个函数就变“活”了,如果我们想writeFile()函数执行完之后并不是像第二个例子那样只能调用f(),而是还有别的函数比如说x() y() z(),那么只需要写成 writeFile(x),writeFile(y)… 就行了。PS: 此处并不一定非要写为“callback”,你可以任意写成a,b,c…callback只是一种约定俗成的写法,它明确地告诉代码阅读者:此处是一个回调函数。
但是这步写法不够简洁,一些函数的形参列表里直接嵌套一个函数的情况,其本质上仍然是回调函数,因为没有了函数名,所以也称匿名函数。看下最终的简化写法:
(4)第四步:匿名回调函数
var fs =require("fs");
function writeFile(callback){
fs.writeFile('input.txt', '我是通过fs.writeFile 写入文件的内容', function (err){if(!err){
console.log("文件写入完毕!")
c =1callback(c)}});}var c =0writeFile(function (param){
console.log(param)})
这是最简洁的写法,再举几个例子:
var doit =function(callback){var a =1,
b =2,
c =3;var t =callback(a,b,c);return t +10;};var d =doit(function(x,y,z){return(x+y+z);});
console.log(d);
export default{created(){this.testCallBack();},
methods:{testCallBack(){
let param ='测试'this.myCallback(param, function (arg1, arg2){alert('这是回调'+ arg1 +' '+ arg2)});},myCallback(param, callback){setTimeout(()=>{alert(param)callback(param,'222');},1000);}}}readImageFile(origin, quality, file){
let that =this;
let fileName = file.name;
let reader =newFileReader();
reader.onload =function(evt){
let base64File = evt.target.result;
that.imageCompress(
base64File,
origin,{
quality
},function(result){
let blobFile = that.dataURLtoBlob(result);
let compressFile =newwindow.File([blobFile], fileName,{ type: file.type });
that.uploadFile(compressFile);});};
reader.readAsDataURL(file);},// 压缩图片imageCompress(path,Orientation, obj, callback){
let img =newImage();
img.src = path;
img.onload =function(){
let that =this;// 默认按比例压缩
let imgWidth = that.width;
let imgHeight = that.height;
let scale = imgWidth / imgHeight;if(imgWidth >MAX_IMAGE_WIDTH){
imgWidth =MAX_IMAGE_WIDTH;
imgHeight = imgWidth / scale;}
let quality = obj.quality ||0.7;// 默认图片质量为0.7// 生成canvas
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let anw = document.createAttribute('width');
let anh = document.createAttribute('height');
anw.nodeValue = imgWidth;
anh.nodeValue = imgHeight;
canvas.setAttributeNode(anw);
canvas.setAttributeNode(anh);
ctx.drawImage(that,0,0, imgWidth, imgHeight);// quality值越小,所绘制出的图像越模糊
let base64 = canvas.toDataURL('image/jpeg', quality);// 回调函数返回base64的值callback(base64);};},
function doSomething(msg, callback){alert(msg);if(typeof callback =="function")callback();}doSomething("回调函数",function(){alert("匿名函数实现回调!");});
2、常见的回调函数例子:
三、回调函数中this的指向:
callback中的this指向window。可以使用Call和Apply函数来改变this指向,每个Javascript中的函数都有两个方法:Call 和 Apply。这些方法被用来设置函数内部的this对象以及给此函数传递变量。
举例:
var clientData ={
id:096545,
fullName:"Not Set",//setUsrName是一个在clientData对象中的方法
setUserName: function (firstName, lastName){this.fullName = firstName +" "+ lastName;}}
function getUserInput(firstName, lastName, callback){//code .....//调用回调函数存储callback(firstName, lastName);}getUserInput("Barack","Obama",clientData.setUserName);
console.log(clientData.fullName);//Not Set
console.log(window.fullName);//Barack Obama
在上面的代码中,当clientData.setUsername被执行时,this.fullName并没有设置clientData对象中的fullName属性,它将设置window对象中的fullName属性。
下面我们看下Apply函数实现,Call函数类似。(call接收的第一个参数为被用来在函数内部当做this的对象,传递给函数的参数被挨个传递。Apply函数的第一个参数也是在函数内部作为this的对象,然而最后一个参数确是传递给函数的值的数组。)
//注意到我们增加了新的参数作为回调对象,叫做“callbackObj”
function getUserInput(firstName, lastName, callback ,callbackObj){//code .....
callback.apply(callbackObj,[firstName, lastName]);}getUserInput("Barack","Obama", clientData.setUserName, clientData);
console.log(clientData.fullName);//Barack Obama
四、回调函数的使用场景:
1、回调函数经常使用于以下场景:
异步调用(例如读取文件,进行HTTP请求,动态加载js文件,加载iframe资源后,图片加载完成执行回调等等)
事件监听器/处理器
setTimeout和setInterval方法
一般情况:精简代码
2、以异步调用为例,回调函数与异步编程:回调函数是实现异步编程的利器,在程序运行中,当某些请求过程漫长,我们有时没必要选择等待请求完成继续处理下一个任务,这时使用回调函数进行异步处理可以大大提高程序执行效率。例如:AJAX请求。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用!下面有个使用AJAX加载XML文件的示例,并且使用了call()函数,在请求对象(requested object)上下文中调用回调函数。
function fn(url, callback){var httpRequest;//创建XHR
httpRequest =window.XMLHttpRequest?newXMLHttpRequest()://针对IE进行功能性检测window.ActiveXObject?newActiveXObject("Microsoft.XMLHTTP"): undefined;
httpRequest.onreadystatechange =function(){if(httpRequest.readystate ===4&& httpRequest.status ===200){//状态判断
callback.call(httpRequest.responseXML);}};
httpRequest.open("GET", url);
httpRequest.send();}fn("text.xml",function(){//调用函数
console.log(this);//此语句后输出
});
console.log("this will run before the above callback.");//此语句先输出
版权归原作者 zch981964 所有, 如有侵权,请联系我们删除。