前言

  前段时间决定弃用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() {
// all scripts loaded
});

  其实你会发现代码都提供了,只管用就是了。
  虽然不清楚是怎么实现的,但是可以知道它是通过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(){//动态加载renderPass相关脚本

pass.outlinePass(function(outline){//动态加载outlinePass相关脚本

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
// Normal callback usage => PYRAMID OF DOOOOOOOOM
asyncOperation(function(data){
// Do some processing with `data`
anotherAsync(function(data2){
// Some more processing with `data2`
yetAnotherAsync(function(){
// Yay we're finished!
});
});
});
//这只是三层而已,如果嵌入了10层,那么末尾的 }); 将会多到眼花缭乱的。

//ajax多重嵌套就更清晰了
$.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(); //在函数内部,新建一个Deferred对象

var tasks = function () {

alert("执行完毕!");

dtd.resolve(); // 改变Deferred对象的执行状态

};

setTimeout(tasks, 5000);

return dtd.promise(); // 返回promise对象

};

//通过when调用,实现done操作
$.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);
// 这里添加promiseA的回调

var promiseB = promiseA.then(function(){
return $.get(urlB);
});
// 这里添加promiseB的回调

var promiseC = promiseB.then(function(){
return $.get(urlC);
});
// 这里添加promiseC的回调

var promiseD = promiseC.then(function(){
return $.get(urlD);
});
// 这里添加promiseD的回调

  这下子就清晰了很多了,回调函数不需要嵌套在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);//a = 2b = 3
return a * b;

}).done(function( result ) {
console.log("result = " + result);//result = 2

}).then(function( a, b ) {
console.log("a = " + a+"b = " + b);//a = 2b = 3
return a * b;

}).done(function( result ) {
console.log("result = " + result);//result = 6

}).then(function( a, b ) {
console.log("a = " + a+"b = " + b);//a = 6b = undefined
return a * b;

}).done(function( result ) {
console.log("result = " + result);//result = NaN
});

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(){//动态加载renderPass相关脚本

pass.outlinePass(function(outline){//动态加载outlinePass相关脚本

var RayCaster = app.Raycaster(function(selectedObject){
let selectedObjects = [];
selectedObjects.push(selectedObject)
outline.selectedObjects = selectedObjects;
},function(){
outline.selectedObjects = [];
})

RayCaster.Click();

});

});


//jQuery实现的Deferred回调

var pass = app.renderPass();//加载renderPass相关的脚本

pass.then(function (renderPass) {
return renderPass.outlinePass();//加载outlinePass相关的脚本
})
.done(function (outlinePass) {//回调获取outlinePass
var RayCaster = app.Raycaster(function (selectedObject) {
let selectedObjects = [];
selectedObjects.push(selectedObject)
outlinePass.selectedObjects = selectedObjects;
}, function () {
outlinePass.selectedObjects = [];
})
})

  真是没有对比就没有伤害。
  显然后者更加清晰,更加易于理解程序的操作。
  当然背后的工程量也就越大。
  个人觉得Deferred也不必滥用,如果回调函数比较少的时候,还是回调起来比较方便。