关于JSONP的两点疑问

昨天去面试阿里前端开发的实习招聘,面试官问了我两个关于JSONP的两个问题,表示平时用的时候没有怎么去思考。

问题一:JSONP是需要动态创建script标签的,我们需不需要处理这些script元素?怎么处理? 我回答的是需要,然后回答了需要去移除script标签。面试官接着问:这样处理有没有什么副作用?没答上来;
问题二:JSONP请求的时候,服务器发生错误该怎么办,比如服务器崩掉,比如返回了404页面,前端该怎么处理这个错误,难道直接让它抛出么?又没答上来。

回来google了一下,第一个问题关于JSONP产生的动态JS的处理:只清除script标签是不够的,需要手动去清除这段JS占用的内存;第二个问题关于JSONP的服务器错误处理:查到的结果是JSONP不提供错误处理。

但是对自己查到的结果还是不够满意,希望大神可以指点一下,第一个问题产生副作用的原因、怎么去手动清除这些动态JS占用的内存以及第二个问题的解答,谢谢!

阅读 12.1k
2 个回答

这两问题,建议楼主去看看jQuery、yui、kissy等框架/库的源码!

我主要想谈谈第一个问题。

jsonp动态创建的节点确实是需要删除的。

目前市面上不同的框架/库,它们的处理方法是不一样的,主要有两种:

  1. onload/complete时,删除节点,比如jQuery
  2. 暂不删除节点,积累到一定数目时才删除,比如yui(不知道为啥要这么干)

jsonp创建script节点后,通常还会挂上onload、onreadystatechagne、onerror等事件(如果支持这些事件的话),面试者可能想考的就是在删除节点后怎么处理这些事件等。

还有一个需要考虑的就是,很多框架/库在实现jsonp时,一般都会生成一个uuid,比如jQuery19109801354627124965_1398582826844,然后将它挂载到window上,用以包装用户的callback,比如:

window['jQuery19109801354627124965_1398582826844'] = function() {
    // ...
    callback();
    // ...
}

这些挂载到window上的callback也是需要释放的。

至于删除节点后,还需不需要删除属性?我认为是不需要的,虽然一个for-in可以搞定一切,包括事件也删除了。

1楼所说的script标签删除,元素的属性还是可以取到的?没错,是可以取到,怎么取的?

function x(id) {
   var script = document.getElementById(id)
   document.head.removeChild(script);
   console.log(script.src);
;}

这样获取的话,肯定是能拿到的,因为此刻script确实还没释放!什么时候释放?至少也得函数x执行完后吧(实际的释放时机可能比这个还晚,函数执行完不一定立马触发gc)!

实际上,gc一般会发生在unload、inactive tab或者产生了太多垃圾不得不gc时。

而gc发生时,以上面的x函数为例,只要x函数中的script变量没有引用计数,它将会被回收。

一旦释放,内存就回归正常,不存在memory leak!

测试代码:gist

打开chrome timeline面板,切换到memory选项上,点击record开始记录,在console上运行loop方法,观察memory图形变化,同时注意record上的gc event(把chrome dev tool往上拉长一点就可以同时看到memory图形、records以及下方的counters),过一段时间后,可能你的图形会是这样:

结果1

注意到图中的两个箭头,内存以及dom node count为什么在线性增加中?可能有的人看到这里就惊呼,这不就是内存泄露吗?但请注意在这段时间内并没有gc(观察中间的RECORDS),所以内存增加是正常的。(每次调用一个函数都会增加一定的内存,除非gc了,才能释放这些内存,请看这里)。

这个时候,如果我手工点击chrome开发工具的gc按钮,强制gc,结果会是这样:

结果2

可以看到,内存立马释放了,如果你把顶部的时间轴选定在陡峰之后:

结果3

如上图红框所示,DOM Node count也下降到初始值16了!

结论:不是没释放,而是时候未到!!!

真正memory leak的情况?

pattern 1:没有删除节点,并且dom节点与js对象存在循环引用!

function leak() {
    var script = document.createElement('script');
    var id = script.id = (+new Date).toString(36);
    document.head.appendChild(script);
    // 循环引用
    document.getElementById(id).expando = script;
    // leak case
    script.bigString = new Array(10000).join("xxxx");
}
setInterval(leak, 1000);

在chrome上试一下以上代码,record一两分钟之后,手工点一下chrome的gc按钮,停止record。

这种情况是时候到了,却gc不了!!!这才是真正的内存泄露!

没想到看到校友了
JSONP我用到的也不多,我挑我知道的说吧。
1.清除标签后浏览器仅仅是移除这个节点而已,并没有对节点内的JS进行垃圾回收。你可以测试一下,即使script标签移除了,元素的属性还是可以取到的,比如src属性。要回收这段JS可以手动清除script元素所有属性:

var jsonp = document.getElementById('myJson'); //取得script元素
for (var prop in jsonp) {
    delete jsonp[prop];
}

2.这个我也没什么把握,在JSONP的返回中嵌入状态码,前端根据状态码判断成功或失败然后调用不同的回调函数。还有比如用jquery的话,用$.getJSON()$.ajax()方法在发生错误时可以用error回调函数来捕获错误,前端对用户进行提示。另一个方法,设定一段延时,超过设定时间无响应的话提示用户稍后重试等等。

推荐问题
宣传栏