下文主要比较两种现有的Javascript callback实现机制,暂且定为简单版和完整版,简单版的实现只有几行代码,但是无法捕获错误响应,完整版的代码多一些确可以实现错误捕获,而且可以使用的更方便。
简单版
简单版的实现大致如下:
- <SPAN style="FONT-SIZE: small"> 1. var head = document.getElementsByTagName('head')[0];
- 2. var script = document.createElement('script');
- 3. /*onreadystatechange在IE下使用,onload在其他浏览器下使用*/
- 4. script.onload = script.onreadystatechange = function(){
- 5. if(script.readyState && script.readyState == 'loaded'){ //源文是 if( !script.readyState || (script.readyState && script.readyState == 'loaded')这个逻辑有错误,
- //0的时候if会成立。
- 6. //TODO
- 7. }
- 8. }
- 9. head.appendChild(script); </SPAN>
1. var head = document.getElementsByTagName('head')[0];
2. var script = document.createElement('script');
3. /*onreadystatechange在IE下使用,onload在其他浏览器下使用*/
4. script.onload = script.onreadystatechange = function(){
5. if( !script.readyState || (script.readyState && script.readyState == 'loaded'){
6. //TODO
7. }
8. }
9. head.appendChild(script);
短短几行代码已经能满足基本的需求了,而且兼容性还很不错。不过无法提供出错处理,而且使用起来不方便。
完整版
具体的说明都注释在代码中,请看注释。
IE
- <SPAN style="FONT-SIZE: small"> 1. function loadIECallback(param){
- 2. param = initParam(param);
- 3. /*清除,防止内存泄露*/
- 4. function clear(){
- 5. frag = script = script.onreadystatechange = frag[param.name] = null;
- 6. }
- 7. var charset = param.charset || 'gb2312';
- 8. /*创建一个DocumentFragment充当script元素的父容器,这样的好处是不需要将节点真正的加入到DOM树中*/
- 9. var frag = document.createDocumentFragment(), script = document.createElement('script');
- 10. script.charset = charset;
- 11. /*将callback函数定义在DocumentFragment上,script返回后可以直接调用到该函数*/
- 12. frag[param.name] = function () {
- 13. /*调用用户传入的callback*/
- 14. param.success && param.success.apply(null, arguments);
- 15. /*使用完成后清除垃圾信息*/
- 16. clear();
- 17. };
- 18. /*注册script的onreadystatechange事件
- 19. *一般情况,IE6下script的readyState会经历loading、interactive和loaded,
- 20. *IE7下没有interactive(表示加载的数据虽然未完成但已经可以使用)状态,
- 21. *而在interactive(IE7下则是loading之后和loaded之前)状态后请求的callback数据其实已经到达并可用
- 22. *也就是说如果正常请求到数据,则在loaded状态前定义在DocumentFragment上的Callback就会被执行。
- 23. *由于加载成功后onreadystatechange事件会被清空,因此一旦readyState到达了loaded状态则表示加载错误。
- 24. */
- 25. script.onreadystatechange = function () {
- 26. if (script.readyState == 'loaded') {
- 27. param.error && param.error();
- 28. clear();
- 29. }
- 30. };
- 31. script.src = param.url;
- 32. frag.appendChild(script);
- 33. } </SPAN>
1. function loadIECallback(param){
2. param = initParam(param);
3. /*清除,防止内存泄露*/
4. function clear(){
5. frag = script = script.onreadystatechange = frag[param.name] = null;
6. }
7. var charset = param.charset || 'gb2312';
8. /*创建一个DocumentFragment充当script元素的父容器,这样的好处是不需要将节点真正的加入到DOM树中*/
9. var frag = document.createDocumentFragment(), script = document.createElement('script');
10. script.charset = charset;
11. /*将callback函数定义在DocumentFragment上,script返回后可以直接调用到该函数*/
12. frag[param.name] = function () {
13. /*调用用户传入的callback*/
14. param.success && param.success.apply(null, arguments);
15. /*使用完成后清除垃圾信息*/
16. clear();
17. };
18. /*注册script的onreadystatechange事件
19. *一般情况,IE6下script的readyState会经历loading、interactive和loaded,
20. *IE7下没有interactive(表示加载的数据虽然未完成但已经可以使用)状态,
21. *而在interactive(IE7下则是loading之后和loaded之前)状态后请求的callback数据其实已经到达并可用
22. *也就是说如果正常请求到数据,则在loaded状态前定义在DocumentFragment上的Callback就会被执行。
23. *由于加载成功后onreadystatechange事件会被清空,因此一旦readyState到达了loaded状态则表示加载错误。
24. */
25. script.onreadystatechange = function () {
26. if (script.readyState == 'loaded') {
27. param.error && param.error();
28. clear();
29. }
30. };
31. script.src = param.url;
32. frag.appendChild(script);
33. }
其他浏览器
- <SPAN style="FONT-SIZE: small"> 1. function loadOther(param){
- 2. param = initParam(param);
- 3. /*清除,防止内存泄露*/
- 4. function clear(){
- 5. iframe.callback = iframe.errorcallback = null;
- 6. iframe.src = 'about:blank', iframe.parentNode.removeChild(iframe), iframe = null;
- 7. }
- 8. var charset = param.charset || 'gb2312';
- 9. /*采用iframe可以对错误进行响应*/
- 10. var iframe = document.createElement('iframe');
- 11. iframe.style.display = 'none';
- 12.
- 13. /*在iframe的frameElement上定义callback*/
- 14. iframe.callback = function () {
- 15. param.success && param.success.apply(null, arguments);
- 16. clear();
- 17. };
- 18. iframe.errorcallback = function () {
- 19. param.error && param.error();
- 20. clear();
- 21. };
- 22. try {
- 23. document.body.appendChild(iframe);
- 24. var doc = iframe.contentWindow.document;
- 25. doc.open();
- 26. doc.write(
- 27. /*在iframe内容中定义真是的callback函数,以供正确请求到数据后进行响应,如果正确地请求到数据则会执行success回调并删除对错误的响应函数*/
- 28. '<script type="text\/javascript">function ' + param.name + '() { window.frameElement.callback.apply(null, arguments); }</script>'
- 29. /*写入script标签请求数据*/
- 30. + '<script type="text\/javascript" src="' + param.url + '" charset="' + charset + '"></script>'
- 31. /*如果没有请求道正确的数据,则会顺利执行完下面的脚步片段,对错误进行响应*/
- 32. + '<script type="text\/javascript">window.setTimeout("try { window.frameElement.errorcallback(); } catch (exp) {}", 1)</script>'
- 33. );
- 34. doc.close();
- 35. } catch (exp) {}
- 36. } </SPAN>
1. function loadOther(param){
2. param = initParam(param);
3. /*清除,防止内存泄露*/
4. function clear(){
5. iframe.callback = iframe.errorcallback = null;
6. iframe.src = 'about:blank', iframe.parentNode.removeChild(iframe), iframe = null;
7. }
8. var charset = param.charset || 'gb2312';
9. /*采用iframe可以对错误进行响应*/
10. var iframe = document.createElement('iframe');
11. iframe.style.display = 'none';
12.
13. /*在iframe的frameElement上定义callback*/
14. iframe.callback = function () {
15. param.success && param.success.apply(null, arguments);
16. clear();
17. };
18. iframe.errorcallback = function () {
19. param.error && param.error();
20. clear();
21. };
22. try {
23. document.body.appendChild(iframe);
24. var doc = iframe.contentWindow.document;
25. doc.open();
26. doc.write(
27. /*在iframe内容中定义真是的callback函数,以供正确请求到数据后进行响应,如果正确地请求到数据则会执行success回调并删除对错误的响应函数*/
28. '<script type="text\/javascript">function ' + param.name + '() { window.frameElement.callback.apply(null, arguments); }</script>'
29. /*写入script标签请求数据*/
30. + '<script type="text\/javascript" src="' + param.url + '" charset="' + charset + '"></script>'
31. /*如果没有请求道正确的数据,则会顺利执行完下面的脚步片段,对错误进行响应*/
32. + '<script type="text\/javascript">window.setTimeout("try { window.frameElement.errorcallback(); } catch (exp) {}", 1)</script>'
33. );
34. doc.close();
35. } catch (exp) {}
36. }
使用实例
最简洁的方式如下,这里省略了name和key,那么实际上在实现时会自动生成一个带时间戳的callback name,并且默认key为callback,因此最后请求的URL应该为:
http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd& max_count=10&keyword=xxx&callback=_callback_func_123123112。
- <SPAN style="FONT-SIZE: small"> 1. Connection.loadCallback({
- 2. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
- 3. success: successHandle1,
- 4. error: errorHandle
- 5. }); </SPAN>
1. Connection.loadCallback({
2. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
3. success: successHandle1,
4. error: errorHandle
5. });
此外,如果返回的callback函数名是写死的,则需要指定该callback的名字,即参数中的name,使用方式如下:
- <SPAN style="FONT-SIZE: small"> 1. Connection.loadCallback({
- 2. name: 'JSCallback',
- 3. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
- 4. success: successHandle1,
- 5. error: errorHandle
- 6. }); </SPAN>
1. Connection.loadCallback({
2. name: 'JSCallback',
3. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
4. success: successHandle1,
5. error: errorHandle
6. });
此外,如果返回的callback函数名是通过传参来指定的,则可以通过name和key来配置,其中name是callback函数名,key则是传参过程中的参数名,使用方式如下:
- <SPAN style="FONT-SIZE: small"> 1. Connection.loadCallback({
- 2. name: 'JSCallback',
- 3. key: 'cb',
- 4. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
- 5. success: successHandle1,
- 6. error: errorHandle
- 7. }); </SPAN>
1. Connection.loadCallback({
2. name: 'JSCallback',
3. key: 'cb',
4. url: 'http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=' + keyword,
5. success: successHandle1,
6. error: errorHandle
7. });
上例中,发出的请求URL应该类似:http://youa.baidu.com/suggest/se/s?cmd=suggest&type=kwd&max_count=10&keyword=&cb=JSCallback
总结一下
- 在IE的实现方案中,将DocumentFragment充当script元素的父容器,并且将用户定义的callback函数定义在DocumentFragment上,这样的好处是不需要将节点真正的加入到DOM树中即可完成callback功能。
- 在IE的实现方案中,利用script在readyState为loaded之前就已经可以做交互的原理实现了出错处理,如果请求正确则在 readyState转变为loaded之前就应经完成响应,并删除script,所以如果readyState转变到loaded则视为error。
- 在IE之外的实现方案中,利用iframe实现了出错处理,脚本是顺序执行的,正确的响应执行后便销毁iframe,这样便没有机会执行后面的出错响应,只有当请求出错时方会执行。