通常のフロントエンド開発作業では、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 はパラメータを 1 つずつ渡すことしかできないのに対し、apply はパラメータを配列でしか渡せない点が異なります。
最初のパラメータはすべてスコープです。たとえば、これが上で渡された場合、それは doSomething 関数と同じスコープを持つことを意味します。もちろん、window を渡すこともできます。これはウィンドウ全体のスコープを意味します。
3. apply
apply を上手に使うと、ある関数を実行するための関数である関数の実行関数とみなすこともできます。したがって、apply をうまく使用すると、当初は複雑だった多くのことが非常に簡単になることがあります。
たとえば、配列のプッシュ メソッドは apply を使用して呼び出されます:
var arr1=[1,3,4];
var arr2=[3,4 ,5];
arr2 を展開したい場合は、arr1 に 1 つずつ追加し、最後に arr1=[1,3,4,3,4,5]
arr1 とします。 .push(arr2 ) は明らかに不可能です。これを実行すると [1,3,4,[3,4,5]]
が取得されるため、ループを使用して 1 つずつプッシュすることしかできません (もちろん、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)
1 行のコードで問題は解決します。原理がわかります。Array.prototype.push は配列のプッシュ関数です。apply(arr1, arr2) は、arr1 がスコープであることを示します。これは、arr1 がプッシュを呼び出すのと同等です。
そして、arr1 は確かに配列なので呼び出すことができ、arr2 はパラメーターの配列を表します。したがって、上記のステートメントは arr1.push(3,4,5) と同等です。 (push 関数は、複数の入力パラメータの受け渡しをサポートしています。これは、ここで使用する apply の前提条件でもあります)
上記のステートメントは、次のように記述することもできます: arr1.push.apply(arr1 , arr2); 両方 arr1.push は配列のプッシュ関数である arr1 のプッシュ関数を表すため、この 2 つは完全に同等です。
call を使用する場合は、Array.prototype.push.call(arr1, arr2[0], arr2[1]...) のようになりますが、明らかに apply が適切です。
それでも尋ねるなら、なぜ arr1.push(3,4,5) を使用しないのですか? これはあなたの IQ を暴露しています。arr2 を変更できないわけではありません。次回は [3, 4、5]。
配列内の最大の数値を取得するには、apply を使用して Math.max 関数を呼び出すこともできます。
var arr1=[1,3,4];
alert (Math.max.apply(window,arr1)); /* スコープが 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 コードを実行できます。
戻り値は、文字列 (存在する場合) を計算して得られた値です。例:
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" を実行することになります。
ここでのデータはウィンドウを参照しているため、戻り値はvalue これは関数 window.noticeInfo.setProjectInfo()
です。実際には、これはもっと単純です。この関数を取得するために eval() を使用する必要はありません。これは、 include 関数内で項目がすでに存在しているためです。 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)
テストの結果、関数に基づいて関数を取得するこれら 2 つの方法が有効であることがわかりました。文字列形式の名前でもまったく同じ効果が得られます。
以上がjs関数コールバックの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。