JS函数的回调

平常的前端开发工作中,编写js时会有很多地方用到函数的回调。

最简单的例子就是:

<script language="javascript" type="text/javascript">

function doSomething(callback) {

if(typeof callback == "function") {

callback();

}

}

function foo() {

alert("我是回调后执行的函数");

}

doSomething(foo); /*正确*/

doSomething(function(){

alert("我是回调后执行的函数");

}); /*正确*/

doSomething("foo"); /* 这样是不行的,传入的是一个字符串,不是一个函数名 */

</script>

以上只能回调没有参数的(除法你事先知道回调的函数的参数),如果函数有未知的函数,就不能如此简单的调用了。


高级方法:

1、使用javascript的call方法

function doSomething(callback,arg1,arg2) {

callback.call(this,arg1,arg2);

}

function foo(arg1,arg2) {

alert(arg1+":"+arg2);

}

doSomething(foo,1,2); /* 弹出了1:2 */


2、使用javascript 的 apply方法


function doSomething(callback,args) {

callback.apply(window,args);

}

function foo(arg1,arg2) {

alert(arg1+":"+arg2);

}

doSomething(foo,[1,2,3]); /* 弹出了1:2 */

可以看成call和apply基本一样,区别就是call只能一个个传参数,apply只能把参数放数组里传进来。

他们的第一个参数都是作用域,比如上面传了this,表示就是和doSomething这个函数一样的作用域,当然你也可以传window,表示整个window的作用域。


3、apply的巧妙用法

apply也可以看作是函数的执行函数,就是用来执行某个函数的函数。所以你会发现,有时候用好apply,有很多原本繁杂的事情会变得如此简单。

比如数组的push方法使用apply来调用:

var arr1=[1,3,4];

var arr2=[3,4,5];

如果我们要把 arr2展开,然后一个一个追加到arr1中去,最后让arr1=[1,3,4,3,4,5]

arr1.push(arr2)显然是不行的。 因为这样做会得到[1,3,4,[3,4,5]]

我们只能用一个循环去一个一个的push(当然也可以用arr1.concat(arr2),但是concat方法并不改变arr1本身)

var arrLen=arr2.length

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

    arr1.push(arr2[i]);

}

自从有了Apply,事情就变得如此简单

Array.prototype.push.apply(arr1,arr2)

一行代码就解决了,原理能看的出来,Array.prototype.push是指数组的push函数,apply(arr1,arr2)说明arr1是作用域,就等同于是arr1调用了数组的push函数,

而且arr1的确就是个数组,所以可以调用,arr2表示入参的数组。所以,以上语句等同于:arr1.push(3,4,5)。(push函数支持传递多个入参,这也是这里可以使用apply的前提条件)

以上语句也可以写成:arr1.push.apply(arr1,arr2); 两者完全等效,因为arr1.push表示arr1的push函数,也就是数组的push函数。

如果使用call就是这样Array.prototype.push.call(arr1,arr2[0],arr2[1]...),显然还是apply合适。

要是你还问,那直接用arr1.push(3,4,5)不就行了,那已经暴露了你的智商,arr2又不是不可以变,下次不是[3,4,5]了呢。


还有获取数组中,最大的那个数字,也可以使用apply调用Math.max函数

var arr1=[1,3,4];

alert(Math.max.apply(window,arr1)); /* 作用域可以不是window,就算是null都行,Math.max.apply(this,arr1),Math.max.apply(null,arr1) */


4、工作中函数回调的实际例子

有了上面的基础,就能看的懂工作中封装好的js的回调函数了

背景:页面A需要使用页面B来选择某个项目,然后带回这个项目的信息给页面A,页面A根据这些信息丰富自己。

页面A:

noticeInfo = {

selectProject: function () {

var win = newsee.ui.window

win.show('项目列表', '../Project/ProjectSelectList.html?callback=noticeInfo.setProjectInfo', { size: win.winSizeType.big })

//在当前页面弹出框,框里面是另一个页面,地址后面带上需要回调的函数名

//注意这两个页面其实都是在一个页面里面的,并不是像window.open()那样出现了新窗口,所以两个页面的js都是可见的

},

setProjectInfo: function (obj) {

//回调函数,将选择好的项目对象传进来,然后丰富自己的页面

$('#projectName').val(obj.name)

$('#projectID').val(obj.id)

}

页面B:

function SelectBack() {

var callback = newsee.util.url.getQuery('callback'); //获取页面参数callback,这里获取到的是"noticeInfo.setProjectInfo",是个字符串

var arr = newsee.ui.grid.getSelectedBack('datagrid') //获取选择的项目,这个不用深究

if (!arr.length) {

return newsee.ui.window.alert('请选择项目!')

}

newsee.util.url.back(callback, arr[0]) //重点来了,这里执行回调,将需要回调的函数名和入参传进来,arr[0]就是选择的项目的对象的数组了(它也是个数组,里面就一个对象)

}

newsee.util.url.back函数如下:

back : function (funcName) {

// / <param name="funcName" type="String">返回时执行的方法,一般为重新绑定</param>

var isWindow = typeof $$winClose === 'function',// 是否为弹窗

args // 弹窗返回方法参数

if (isWindow) {// 弹窗的返回方法

$$winClose()

args = [].slice.call(arguments) //arguments大家应该都知道的吧,它可以用来获取函数的实参,它类似数组又不是数组,这句代码就是把它转换成数组,因为apply的入参需要是个数组才行

//args现在里面有两个元素,args[0]=callback,就是之前传进来的回调函数名,args[1]=arr[0],就是回调函数的入参

newsee.callFunc.apply(newsee, args) //执行 newsee.callFunc 函数,作用域就是newsee自己(等同于newsee自己调用callFunc函数),参数是args

}

}

newsee.callFunc函数如下:

callFunc: function(funcName, arg) {

var func = typeof funcName === 'function' ? funcName : this.findItem(window, funcName) //上面我有提到过,doSomething("foo"); 传入的是一个字符串,不是一个函数名,所以无法执行

//同样的道理,现在funcName=args[0]=callback="noticeInfo.setProjectInfo",是个字符串,不能直接调用apply,需要变成函数

//这句话就是用来判断funcName是不是一个函数,如果不是,就在window作用域里根据funcName找到这个函数,然后赋给func

if (typeof func === 'function') {

//此时func已经是个函数了,就是页面A里定义的noticeInfo.setProjectInfo()

try {

return func.apply(window, arg) //执行需回调的函数,作用域依然是window,反正这个函数在window里肯定能找到,参数就是arg=args[1]=arr[0],即之前在页面B获取到的项目对象

}

catch (e) {

console.error(e)

}

}

}

ok,需回调的函数就这样被执行了,至于怎么根据字符串形式的函数名获取这个函数,看下面。

//findItem函数如下:

findItem: function(data, key) {

// / <summary>获取对象指定键的值</summary>

if (this.include(data, key)) { //data这里就是传进来的window,注意window就是一个对象,首先判断window对象里是否存在"noticeInfo.setProjectInfo"这个属性

return eval('data.' + key) //如果存在,就执行"data.noticeInfo.setProjectInfo",这样就获取到了这个函数了。(eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码)

}

}

//include函数如下:

include: function(data, key) {

// / <summary>判断对象是否存在键值</summary>

if (data == null || typeof data !== 'object' || !key || typeof key !== 'string') {

return false

}

var keys = key.split('.'),

item = data,

result = true

keys.forEach(function(k) {

if (item != null && typeof item === 'object' && k in item) {

//依次循环遍历,第一次item = data,那就是window这个对象,k="noticeInfo",window[noticeInfo]是存在的,因为在页面A里定义了noticeInfo这么一个对象

//第二次循环,item=window.noticeInfo,k="setProjectInfo",window.noticeInfo[setProjectInfo]也是存在的,因为在页面A里也定义了setProjectInfo这么一个函数

//这里没有第三次循环了,所以最后返回是true,说明window对象里存在"noticeInfo.setProjectInfo"这个属性,接下来使用eval()拿到它即可

item = item[k]

} else {

return result = false

}

})

return result

}

对eval() 函数也介绍一下:

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。

返回值就是通过计算 string 得到的值(如果有的话)。如:

eval("x=10;y=20;document.write(x*y)") //输出 200

document.write(eval("2+2")) //输出 4

var x=10

document.write(eval(x+17)) //输出 27

所以上面的eval('data.' + key)就是执行"data.noticeInfo.setProjectInfo"这个字符串,

因为data在这里就是指window,所以返回值就是window.noticeInfo.setProjectInfo()这个函数


其实可以在简单一点,根本没必要使用eval()来获取这个函数,因为在include函数里,item就已经是window.noticeInfo.setProjectInfo这个对象了,这个对象就是我们想要的函数。

(在js中函数也是对象,函数名就是这个函数的引用,就和地址差不多)

既然都拿到这个函数了,直接返回不就行了,所以上面的include()和findItem可以这样简化:

include: function(data, key) {

if (data == null || typeof data !== 'object' || !key || typeof key !== 'string') {

}else{

var keys = key.split('.'),

item = data,

result = true

keys.forEach(function(k) {

if (item != null && typeof item === 'object' && k in item) {

item = item[k]

} else {

result = false;

}

})

if(result)

return item

}

},

findItem: function(data, key) {

return this.include(data, key)

}

经过测试,发现这两个根据字符串形式的函数名获取函数的方法都可以达到一模一样的效果。

------------------------------------------------------------------------声明-------------------------------------------------------------

以上部分是copy别人的,部分是自己的理解!

本人水平有限,如有不对之处,还望大神指正,谢谢!

---------------------

版权声明:本文为CSDN博主「yjclsx」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/yjclsx/article/details/53812225

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容