博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【备忘】指定为同名callback的jsonp && IE下script loaded状态标记
阅读量:6569 次
发布时间:2019-06-24

本文共 4672 字,大约阅读时间需要 15 分钟。

【已知】

不知道大家有木有了解过jQuery1.0到2.0时候针对jsonp那一块的修改。v1.0的时候还在使用iframe作为请求数据的临时暂居地。以便让过往数据有据可查。保证了jsonp请求的时候即便用了同样的全局callback 也只至于先返回的数据丢失,造成数据污染的问题。

但是自从jq2.0之后,就不再采用iframe来记录jsonp获得的数据了。可是依然,不得不支持同一个callback名。

那么,这样,问题就来了...

【问题】

jsonp的原理其实就是我们把请求地址当作一个js地址以script tag 的方式插入到页面,把服务端返回的数据当作script 脚本来运行以获得所需的数据(通常是第一个参数)。既然是以script脚本的方式载入,自然就绕过的跨域的问题。这自然需要服务端做一点配合。把我们约定传的callback参数以函数的形式返回,方便我们执行,获得数据。

那,如果我们有多个jsonp的请求,而且非要,死活要指定同样的callback名呢,肿么办?

因为请求耗时的不确定性,无法确定每个回调执行的顺序,比如,一个简单的input suggest, 假设我们每次用户keyup都去请求一次数据,然后每次请求回调里面重新render我的们suggest list。实际上,这些请求是有“顺序”可言的,也就是说:我们原意是,后来的请求的回调也应该后执行才对, 这样才能保证 我输入 abc ,最后得到的suggest list 不是 a 的, 或者 ab的。(这是一个问题,但不是jsonp本身应该讨论的问题)

当然,你可以创建一个hash维护数据,当成一个自定义的cache来用,用一个唯一的且可以标识这个请求的key就行。每次要render的时候校验一次当前数据是否是你想要的请求得来的数据即可。

可是....

这里要讨论的并不是这个问题。

a) 如果是一个同名的全局callback,单单从这个函数来看的话,我们其实是不知道它被重写了多少次?结果和请求是否能对应起来?尤其是当其中有某些脚本发生错误的时候(当然,如果非要去hack,也并非不可)

b)不管在哪个浏览器里,一个脚本在执行之前,是无法对其干预的(removeNode之类的不算),只有当脚本loaded并且执行后,或者发生错误之后,才能对其进行操作。

c)正因为在execution 之前你不能对其进行操作,所以无法得知,这些脚本会以什么样的顺序进行loading和executing。

【通常】

为了避免所谓的callback污染:

a)用计数器对请求结果进行连接控制,jq v2.0+ 类似的方式

b)每次使用完返回数据后都重置,下一次使用前都检查下,防止二次污染使用。

对jsonp做一个简单通用的requestCallback, onload和onerror都能调

// some generic code// common to all requestsvar lastValue;function genericCallback( value ) {  lastValue = [ value ];}// then the request specific closurefunction request( options ) {  window[ options.callback ] = genericCallback;  function requestCallback() {    var tmp = lastValue;    lastValue = undefined;    if ( ! tmp ) {      options.error();    } else {      options.success( tmp[ 0 ] );    }  }  // create the script tag  // "attach" requestCallback to the script tag  // put the script tag in the DOM}..........scriptTag.onload = scriptTag.onerror = requestCallback;

Opera并没提供不标准的onerror的钩子。但是无妨,onload亦可。

【IE下onreadystatechange 怪癖】

scriptTag.onreadystatechange = function() {  if ( /loaded|complete/.test( scriptTag.readyState ) ) {    requestCallback();  }};

ie下,onreadystatechange 的调用 并不是紧紧跟在脚本执行或者失败之后, 而是有一点点延迟,尤其是当有这个请求有缓存的时候。而有可能这一点点延迟的时候就有另一个脚本回调进来了,那就悲剧了。

如果这样:

奇淫技巧,尝试load脚本,如果成功,ie下会触发divId 的一个onclick,而且不管load是否成功,都会触发script的onreaderstatechange的loaded readyState,而且最重要的是这个发生在脚本load完成,excute之前。

进一步,div的onclick可以被触发,同样的方式对于script呢?

答案是肯定的,所以我们也不用额外的创建一个dom了。

所以可以用 script的event和for属性 来fix ie的onreaderstatechange的毛病了:

scriptTag.event = "onclick";scriptTag.id = scriptTag.htmlFor = generateNewId();scriptTag.onreadystatechange = function() {  if ( /loaded|complete/.test( scriptTag.readyState ) ) {    try {      scriptTag.onclick();    } catch( e ) {}    requestCallback();  }};

ie>=9 或许并不需要这个hack,但是无影响。

【所以】

// 代码其中一段...  var head = document.getElementsByTag('head')[0],      uniqid = 0,      lastValue;        function generalCallback(data) {    lastValue = data  }  function urlappend(url, s) {    return url + (/\?/.test(url) ? '&' : '?') + s  }  function handleJsonp(o, fn, err, url) {    var reqId = uniqid++,        cbkey = o.jsonpCallback || 'callback', // the 'callback' key        cbval = o.jsonpCallbackName || ('__myrequest__' + reqId), // the 'callback' value        cbreg = new RegExp('(' + cbkey + ')=(.+)(&|$)'),        match = url.match(cbreg),        script = doc.createElement('script'),        loaded = 0;    if (match) {      if (match[2] === '?') {        url = url.replace(cbreg, '$1=' + cbval + '$3') // wildcard callback func name      } else {        cbval = match[2] // provided callback func name      }    } else {      url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em    }    win[cbval] = generalCallback;    script.type = 'text/javascript';    script.src = url;    script.async = true;    if (typeof script.onreadystatechange !== 'undefined') {        // need this for IE due to out-of-order onreadystatechange(), binding script        // execution to an event listener gives us control over when the script        // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html        script.event = 'onclick';        script.htmlFor = script.id = '_myrequest_' + reqId;    }    script.onload = script.onreadystatechange = function () {      if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) {        return false      }      script.onload = script.onreadystatechange = null;      script.onclick && script.onclick();      // Call the user callback with the last value stored and clean up values and scripts.      o.success && o.success(lastValue);      lastValue = undefined;      head.removeChild(script);      loaded = 1;    }    // Add the script to the DOM head    head.appendChild(script);  }

  

更多阅读:  

  

  

  

转载地址:http://aapjo.baihongyu.com/

你可能感兴趣的文章
第七次课程作业
查看>>
C++ 文本查询2.0(逻辑查询)
查看>>
Objective-C学习总结-13协议1
查看>>
web学习方向
查看>>
A*算法实现
查看>>
第一周 从C走进C++ 002 命令行参数
查看>>
[转]【NoSQL】NoSQL入门级资料整理(CAP原理、最终一致性)
查看>>
RequireJS进阶(二)
查看>>
我设计的网站的分布式架构
查看>>
linux extract rar files
查看>>
Knockout.Js官网学习(监控属性Observables)
查看>>
ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务解决
查看>>
azure之MSSQL服务性能测试
查看>>
Android BitmapFactory.Options
查看>>
前端构建:Less入了个门
查看>>
phonegap(cordova) 自己定义插件代码篇(三)----支付宝支付工具整合
查看>>
tomcat架构分析(valve源码导读)
查看>>
spring中InitializingBean接口使用理解(转)
查看>>
基于php5.5使用PHPMailer-5.2发送邮件
查看>>
InstallShield 2012 Spring新功能试用(16): Suite/Advanced UI 或 Advanced UI安装程序能在安装时进行输入合法性校验与反馈...
查看>>