当前位置: 首页 > news >正文

jQuery动态载入JS文件研究

在前端开发过程中,有时候会遇到插件化设计的需要。在运行过程中动态的加载一些资源文件。而在动态加载js文件的时候,遇到一些有趣的现象。以此让我们简单的探讨一下。

 

假设我们现在有一个页面,会动态加载一段html片段,并将其显示在页面上。而这个片段中会携带<script>。


index.html

<!DOCTYPE html>

<html lang="en">

 <head>

  <!-- jQuery -->

  <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>

 </head>

 <body>

  <div id="cntr"></div>

  <script>

   $(function() {

    $("#cntr").load("htmlWithJS.html");

   });

  </script>

 </body>

</html>

 

htmlWithJS.html

<script>

 alert("include!");

</script>




顺利载入:

 

接着,我们改写一下htmlWithJS.html。将script改成引用一个test.js文件。内容不变:


htmlWithJS.html

<script src="test.js"></script>

 

test.js

alert("include!");

顺利载入,但是出现了warning:



这行文字表述的意思是,文档中包含了同步的http请求。而这个请求会阻塞当前的主线程那个,从而降低用户体验(因为载入过程中会导致页面卡顿)。

看起来问题似乎很好解决,我们只要添加html5的属性async即可:

htmlWithJS.html

<script src="test.js" async="async"></script>


运行结果:




问题依旧没有被解决,而查看网络请求,则会发现test.js跟了一串尾巴:




看来是jQuery搞的鬼。让我们看看jQuery的load方法描述:

Whencalling .load() using aURL without a suffixed selector expression, the content is passed to .html() prior toscripts being removed.

 

jQuery会进行html转换。为了更好的理解,让我们看一下jQuery源代码:


jQuery.ajax({

    url: url,

    // if "type" variable is undefined, then "GET" method will be used

    type: type,

    dataType: "html",

    data: params

}).done(function( responseText ) {

    // Save response for use in complete callback

    response = arguments;

    self.html( selector ?

        // If a selector was specified, locate the right elements in a dummy div

        // Exclude scripts to avoid IE 'Permission Denied' errors

        jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

        // Otherwise use the full result

        responseText );

}).complete( callback && function( jqXHR, status ) {

    self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );

});


可以看出,如果没有selector则直接调用了html方法。

 

接着,我们读一下html方法的文档:

By design, anyjQuery constructor or method that accepts an HTML string — jQuery().append().after(), etc. — canpotentially execute code. This can occur by injection of script tags or use ofHTML attributes that execute code (for example, <img οnlοad="">). Do not usethese methods to insert strings obtained from untrusted sources such as URLquery parameters, cookies, or form inputs. Doing so can introducecross-site-scripting (XSS) vulnerabilities. Remove or escape any user inputbefore adding content to the document.

 

好像没有什么关于script的具体内容。我们接着看看源代码:


html: function( value ) {

    return access( this, function( value ) {

        var elem = this[ 0 ] || {},

            i = 0,

            l = this.length;

        if ( value === undefined ) {

            return elem.nodeType === 1 ?

                elem.innerHTML.replace( rinlinejQuery, "" ) :

                undefined;

        }

        // See if we can take a shortcut and just use innerHTML

        if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&

            ( support.htmlSerialize || !rnoshimcache.test( value )  ) &&

            ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&

            !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) {

            value = value.replace( rxhtmlTag, "<$1></$2>" );

            try {

                for (; i < l; i++ ) {

                    // Remove element nodes and prevent memory leaks

                    elem = this[i] || {};

                    if ( elem.nodeType === 1 ) {

                        jQuery.cleanData( getAll( elem, false ) );

                        elem.innerHTML = value;

                    }

                }

                elem = 0;

            // If using innerHTML throws an exception, use the fallback method

            } catch(e) {}

        }

        if ( elem ) {

            this.empty().append( value );

        }

    }, null, value, arguments.length );

},


可见,先调用empty清空,再用append将元素添加进去。我们继续看看append代码:

append: function() {

    return this.domManip( arguments, function( elem ) {

        if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {

            var target = manipulationTarget( this, elem );

            target.appendChild( elem );

        }

    });

},


domManip方法内容较多,我就只挑一部分出来:

……

fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );

……

scripts = jQuery.map( getAll( fragment, "script" ), disableScript);


domManip中会调用buildFragment方法创建一个dom片段,将文本转化成dom元素,之后遍历script元素并更改其type属性:


// Replace/restore the type attribute of script elements for safe DOM manipulation

function disableScript( elem ) {

    elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;

    return elem;

}

function restoreScript( elem ) {

    var match = rscriptTypeMasked.exec( elem.type );

    if ( match ) {

        elem.type = match[1];

    } else {

        elem.removeAttribute("type");

    }

    return elem;

}


在domManip的回调函数中,会正常的添加元素。但是如我们所见,script的type已经被更改,所以不会真的载入这段脚本。让我们回到,domManip方法本身,看看接下去发生了什么:


// Reenable scripts

jQuery.map( scripts, restoreScript );

// Evaluate executable scripts on first document insertion

for ( i = 0; i < hasScripts; i++ ) {

    node = scripts[ i ];

    if ( rscriptType.test( node.type || "" ) &&

        !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {

        if ( node.src ) {

            // Optional AJAX dependency, but won't run scripts if not present

            if ( jQuery._evalUrl ) {

                jQuery._evalUrl( node.src );

            }

        } else {

            jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );

        }

    }

}



好了,快到目的地了。我们看看_evalUrl里面是什么内容:


jQuery._evalUrl = function( url ) {

    return jQuery.ajax({

        url: url,

        type: "GET",

        dataType: "script",

        async: false,

        global: false,

        "throws": true

    });

};


是否恍然大悟?虽然我们在插入的script中指定了async属性,但是jQuery不会读取这个属性,而是创建一个async为false的ajax请求再次发送。唯一读取的,只是url而已。

我们继续看看条件分支的另一边:

jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );


jQuery会将内联的script代码通过eval方式执行。

 

* 在jQuery 1.8.x及之前版本中,script片段不会被添加进文档中,因而如果使用了type="text/template"的script也会被忽略掉,例如underscore、lodash中的如下模板会被append无视掉(所以请尽量保持jQuery库的更新)。



好了,好了。言归正传,如果我们想使用async来载入script怎么办呢?让我们动动手自己实现一个async吧:


$.fn.extend({

    asyncLoad: function(url) {

        var $cntr = this;

        $.get(url, function(data) {

            // Parse HTML and modify script type

            var $content = $("<div>").html(data);

            var scripts = $content.find("script[src]");

            scripts.each(function(i, script) {

                script.type = "tmpType";

                $.getScript(script.src);

            });

            $cntr.html($content.children());

            $content.find("script[src]").removeAttr("type");

        });

        return this;

    }

});



但是使用getScript方法有一个缺点,通过该方法引入的js文件无法被debug。



所以我们可以做如下修改:

$.fn.extend({

    asyncLoad: function(url) {

        var $cntr = this;

        $.get(url, function(data) {

            // Parse HTML and modify script type

            var $content = $("<div>").html(data);

            var scripts = $content.find("script[src]");

            scripts.each(function(i, script) {

                script.type = "tmpType";

                var _script = document.createElement('script');

                _script.type = 'text/javascript';

                _script.src = script.src;

                document.head.appendChild(_script);

                _script.onload = function() {

$(_script).remove();

};

            });

            $cntr.html($content.children());

            $cntr.find("script[src]").removeAttr("type");

        });

        return this;

    }

});


(test.js出现在了Sources Tab中)

以上就是这次的jQuery动态载入文件的研究内容。建议可以自己通过断点调试的功能一步步跟踪jQuery代码来加深了解。




相关文章:

  • SolrCloud之分布式索引及与Zookeeper的集成
  • Kafka的分布式架构设计与High Availability机制
  • JS方法代理
  • Hadoop作业性能指标及参数调优实例 (一)Hadoop作业性能异常指标
  • Hadoop作业性能指标及参数调优实例 (二)Hadoop作业性能调优7个建议
  • Hadoop作业性能指标及参数调优实例 (三)Hadoop作业性能参数调优方法
  • 漫谈程序控制流
  • Hadoop集群硬盘故障分析与自动化修复
  • jQuery数据赋值解析
  • Apache Kylin的快速数据立方体算法——概述
  • eBay RUM实践
  • 基于数理统计分析的服务器端持续性能压力测试方案
  • 支付结果通知机制研究
  • Apache Eagle:eBay开源分布式实时Hadoop数据安全引擎
  • Ebay开源:Eclipse Plugin Repository Portal
  • 【407天】跃迁之路——程序员高效学习方法论探索系列(实验阶段164-2018.03.19)...
  • 【刷算法】求1+2+3+...+n
  • 3.7、@ResponseBody 和 @RestController
  • flask接收请求并推入栈
  • github指令
  • Git初体验
  • js中forEach回调同异步问题
  • Nodejs和JavaWeb协助开发
  • PHP的Ev教程三(Periodic watcher)
  • SpiderData 2019年2月13日 DApp数据排行榜
  • Theano - 导数
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • v-if和v-for连用出现的问题
  • 测试开发系类之接口自动化测试
  • 程序员最讨厌的9句话,你可有补充?
  • 大整数乘法-表格法
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 用Python写一份独特的元宵节祝福
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 数据可视化之下发图实践
  • 智能情侣枕Pillow Talk,倾听彼此的心跳
  • ​如何防止网络攻击?
  • # 安徽锐锋科技IDMS系统简介
  • #中国IT界的第一本漂流日记 传递IT正能量# 【分享得“IT漂友”勋章】
  • (2020)Java后端开发----(面试题和笔试题)
  • (翻译)terry crowley: 写给程序员
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (接口自动化)Python3操作MySQL数据库
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET连接MongoDB数据库实例教程
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • @angular/cli项目构建--http(2)
  • @KafkaListener注解详解(一)| 常用参数详解