• JSONP Memory Leak


    前言

    JSONP是一种常用的跨域请求脚本的方式。但是当页面使用到轮询时,需要额外小心由此带来的内存泄漏!
    如果页面不涉及轮询,那也不是什么大问题。但是当页面中存在轮询跨域请求时,问题就被无数倍的放大了。
    当然毫无疑问,IE系列始终是最让人纠结的。

    加载脚本

    首先,来看看加载脚本的方式:

    var script = document.createElement('script');
    script.src = 'http://www.abc.com/somepage?callback=check';
    script.id = 'JSONP';
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(script);

    如果只是这样增加节点而不删除,由于轮询导致DOM开销不断增长,内存消耗也不断增长。

    如果请求的脚本加载了较多内容,那么问题就会更加明显。

    读者可以尝试向页面不断载入jquery源代码,设定轮询间隔为2s,可以看到内存的增速达到几十M每秒。

    所以十分有必要在脚本执行完成以后删除这些节点。

    删除脚本

    var script = document.createElement('script');
    script.src = 'http://www.abc.com/somepage?callback=check';
    script.id = 'JSONP';
    script.type = 'text/javascript';
    script.charset = 'utf-8';
    var head = document.getElementsByTagName('head')[0];
    head.appendChild(script);
    head.removeChild(script);
    很显然这样的方式不行,为什么不行是因为脚本还没有来得及执行,节点就被删除了。
    那就先让脚本执行一会儿:
    setTimeout(function(){head.removeChild(script);},200);
    

    不得不说,这样的方式很挫,要是脚本200ms内还没有被执行完成,这样肯定会出问题。

    所以需要保证脚本执行完之后自动删除。

    幸好IE支持onreadysctatechange,而标准支持onload来判断脚本的执行状态。

    var script = document.createElement('script');
    head.appendChild(script);
    if(script.readyState){
    script.onreadysctatechange =function(){
    //注意使用this避免内存泄漏
    this.onload = null;
    this.parentNode.removeChild(this);

    }else{
    script.onload =function(){
    //注意使用this避免内存泄漏
    this.onload = null;
    this.parentNode.removeChild(this);
    };
    }
    script.src = url;

    IE的问题

    其实即使是这样也不能完全避免内存泄漏,标准浏览器包括Chrome随着轮询的都会有内存泄漏的现象,
    不过泄漏程度微乎其微(轮询间隔2m,泄漏速度也只有4k-8k)。
    但是IE会有十几到几十K的内存泄漏,所以需要对IE进行特殊照顾,这个时候我们还是得借助各个浏览器对script标签的解释。

    重用Script标签

    标准浏览器对script标签的处理就是每个script标签的地址(src)只能设置一次,
    后续的设置虽然能改变script标签的地址但是脚本内容不会执行,不管script标签是页面预留的还是动态插入。
    IE下面则很神奇,上面的规则只适合页面上预留的script标签,使用js动态插入的script标签不遵守这一规则。

    于是我们只需要动态的插入一个id已知的script标签,然后不断地改变它的src,以此方式加载的脚本都会执行。 

    而且这个script节点也不必删除,下次请求继续重用即可。 

    var i = 0;
    function _(id){return document.getElementById(id);}
    _.isIE = window.ActiveXObject?true:false;
    _.ajax = function(){};
    _.ajax.jsonp = function(url){
    var script, head = document.head || document.getElementsByTagName('head')[0], scriptId = 'ie_script_for_jsonp';
    url += (url.indexOf('?')>-1?'×tamp=':'?timestamp=') + new Date().getTime();
    //如果是IE浏览器,动态生成script标签,改变src(此方式对标准浏览器无效,预留script标签也无效)
    if(_.isIE){
    script= document.getElementById(scriptId);
    if(!script){
    script = document.createElement('script');
    script.id = scriptId;
    //如果是IE此节点不能删除
    head.appendChild(script);
    } }else{
    script = document.createElement('script');
    head.appendChild(script);
    script.onload =function(){
    this.onload = null;
    //删除此节点
    this.parentNode.removeChild(this);
    };
    script.src = url;
    }
    };

     结局

    对上述代码测试,使用sieve和任务管理器,发现此方法引起的内存泄漏和chrome下面的差不多都在4K左右,测试环境是IE 8。测试代码结构如下:

    window.onload = function(){
    setInterval(function(){
    _.ajax.jsonp('123.js');
    },1000);
    };

     123.js的内容

    i++; 


    后续

    其实传统的方式之所以存在内存泄漏是因为IE的removeChild方法存在内存泄漏的问题,这个jQuery的empty方法已经考虑到。但是为什么jQuery的getJSON在进行跨域请求时仍然存在比较严重的内存泄漏,这个我就不清楚了,有时间探究下源代码。关于removeChild请看这里

    更新说明:

    上述重用标签的方法对IE 9不适用 

  • 相关阅读:
    IdentityServer4 接口说明
    MQTT中的Retained(保留消息) 与 LWT(最后遗嘱)
    Docker常用命令
    开源服务容错处理库Polly使用文档
    MQTT 主题的高级特性
    MQTT的$SYS主题定义
    RabbitMQ消息队列之Windows下安装和部署
    RabbitMQ多台物理机集群搭建
    Ocelot.json完整配置文件
    nginx.conf文件配置明细详解
  • 原文地址:https://www.cnblogs.com/1000/p/2221253.html
Copyright © 2020-2023  润新知