前言
前段时间决定弃用require引用脚本的方式,而是尝试用jQuery的$.getScript()
的方式动态获取脚本,结果在过程中又遇到了许多的坑。
当我开始封装outlinePass函数的时候,我遇到了需要加载数个脚本的问题。但是jQuery提供的解决方案只能加载一个脚本。 于是我上网查了一下,找到了解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $.getMultiScripts = function (arr, path ) { var _arr = $.map (arr, function (scr ) { return $.getScript ( (path||"" ) + scr ); }); _arr.push ($.Deferred (function ( deferred ){ $( deferred.resolve ); })); return $.when.apply ($, _arr); } var script_arr = [ 'myscript1.js' , 'myscript2.js' , 'myscript3.js' ]; $.getMultiScripts (script_arr, '/mypath/' ).done (function ( ) { });
其实你会发现代码都提供了,只管用就是了。 虽然不清楚是怎么实现的,但是可以知道它是通过jQuery的对象对jQuery的$.getScript()
重新封装了。 但是当我封装renderPass相关的四个脚本(EffectComposer.js | RenderPass.js | ShaderPass.js | CopyShader.js)的时候,我遇到了报错问题(至今我也没完全弄明白) 当我错开脚本加载之后,问题就得以解决,这个过程中就接触到了done()操作。 也就是第一个脚本完成之后再加载后面三个脚本
$.getMultiScripts()
用起来也问题不大,但是当我将所有的代码封装好之后,我遇到了金字塔回调的地狱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var pass = app.renderPass (function ( ){ pass.outlinePass (function (outline ){ var RayCaster = app.Raycaster (function (selectedObject ){ let selectedObjects = []; selectedObjects.push (selectedObject) outline.selectedObjects = selectedObjects; },function ( ){ outline.selectedObjects = []; }) RayCaster .Click (); }); });
看起来好像问题还不是很大,毕竟现在只是两层嵌套而已。 但是如果回调函数如果不停增多的话,那么代码就会变得非常复杂,代码的可读性就会大大下降。
鉴于这个问题,我网上找了一下。发现这个详解 另外,博文里面附带的链接 有国外大神文章的翻译。 里面有个例子就非常形象.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 asyncOperation (function (data ){ anotherAsync (function (data2 ){ yetAnotherAsync (function ( ){ }); }); }); $.ajax ({ url : url1, success : function (data ){ $.ajax ({ url : url2, data : data, success : function (data ){ $.ajax ({ }); } }); } });
看来这个问题也是早已有之,国外的大神早就开始寻求解决方案了。 在搜索的过程中还额外发现了另一篇挺有用的博文 ,没想到最有用的四种设计模式都是在pluralsight上面学习的。如果还不懂什么是回调函数之类的可以看看。
现在是问题很清楚了,但是如何解决呢。 CommonJS给出的解决方案是使用Promise/A规范,具体的方案可以参照国外大神文章的讲法。 不过单单是使用起来还不是很方便。 鉴于此,还是使用jQuery的方法比较好,而且我也想弄懂multiScript封装是怎么实现的。
jQuery Deferred
通过jQuery的deferred对象可以通过promise对象实现脚本延时加载,避免回调地狱的噩梦。 那jQuery的deferred要怎么用呢? 有在网上寻找解决方案,但是很多网页都是一大堆文字,讲得我懵逼得很。 后面我发现阮一峰的讲解 还是挺不错的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var wait = function (dtd ) { var dtd = $.Deferred (); var tasks = function ( ) { alert ("执行完毕!" ); dtd.resolve (); }; setTimeout (tasks, 5000 ); return dtd.promise (); }; $.when (wait ()) .done (function ( ) { alert ("哈哈,成功了!" ); }) .fail (function ( ) { alert ("出错啦!" ); });
这样写有什么好处呢?看完之后好像也不比回调函数好多少呀。 还不是得function来function去的吗?
我一开始也是那么懵逼的,虽然在上面的promise原生讲解中多少体会到了一些promise对象的魅力,但是jQuery似乎并没有什么值得称道的地方。 直到我看了这篇博文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var promiseA = $.get (urlA);var promiseB = promiseA.then (function ( ){ return $.get (urlB); }); var promiseC = promiseB.then (function ( ){ return $.get (urlC); }); var promiseD = promiseC.then (function ( ){ return $.get (urlD); });
这下子就清晰了很多了,回调函数不需要嵌套在callback函数里面。 而是通过then去直接调用,可读性大大增加了。
jQuery done() 和 then()
deferred对象可以通过$.when()
来触发,实现then操作。 而除了用then之外,也可以用done来执行成功回调,用fail来执行失败回调。 而then操作则是包含了 success、fail和always三个参数。
我一开始没有搞懂它们之间的区别,结果吃了大亏。
经过网上搜索,我又找到了一篇博文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var defer = jQuery.Deferred (); defer.done (function (a,b ){ console .log ("a = " + a+"b = " + b); return a * b; }).done (function ( result ) { console .log ("result = " + result); }).then (function ( a, b ) { console .log ("a = " + a+"b = " + b); return a * b; }).done (function ( result ) { console .log ("result = " + result); }).then (function ( a, b ) { console .log ("a = " + a+"b = " + b); return a * b; }).done (function ( result ) { console .log ("result = " + result); }); defer.resolve ( 2 , 3 );
通过这篇文章可以找到done()
的返回值是没有意义的。 但是then()
的返回值是可以返回的。 这个巨大的差别让我卡了很久。 我一直以为done实现会更加简洁,其实就是坑了爹。
总结
经过了一天的调试,最后我利用jquery实现了promise对象回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var pass = app.renderPass (function ( ){ pass.outlinePass (function (outline ){ var RayCaster = app.Raycaster (function (selectedObject ){ let selectedObjects = []; selectedObjects.push (selectedObject) outline.selectedObjects = selectedObjects; },function ( ){ outline.selectedObjects = []; }) RayCaster .Click (); }); }); var pass = app.renderPass ();pass.then (function (renderPass ) { return renderPass.outlinePass (); }) .done (function (outlinePass ) { var RayCaster = app.Raycaster (function (selectedObject ) { let selectedObjects = []; selectedObjects.push (selectedObject) outlinePass.selectedObjects = selectedObjects; }, function ( ) { outlinePass.selectedObjects = []; }) })
真是没有对比就没有伤害。 显然后者更加清晰,更加易于理解程序的操作。 当然背后的工程量也就越大。 个人觉得Deferred也不必滥用,如果回调函数比较少的时候,还是回调起来比较方便。