平常的前端開發工作中,寫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)
經過測試,發現這兩個根據字串形式的函數名稱取得函數的方法都可以達到一模一樣的效果。
以上是js函數的回調的詳細內容。更多資訊請關注PHP中文網其他相關文章!