背景
在不反对打包构建的前端历史我的项目中,咱们常常会有动静引入script的诉求,为了书写不便,开发的时候会习惯性的用jQuery生成script标签再append到body中,并将其视为和原生写法性能完全一致
// jQueryvar $script = $('<script src="xxx.js">');$('body').append($script);// ECMAScriptvar script = document.createElement('script');script.src = 'xxx.js';document.body.appendChild(script);
但它们真的统一么?jQuery在这里什么都没做么?不同写法对咱们又会有怎么的影响呢?
先说论断
jQuery写法与原生相比差别如下:
- 并非通过规范的jsonp形式加载JS内容,而是通过一个xhr申请
- 此xhr是同步的,而动静script则是强制async的
- 非法的type赋值会让xhr申请不发送( 不写type是非法的,要写就写对
除了那个同步异步的操作,是不是看起来对咱们理论开发影响不大?如果真不大我也不会有情绪探索这个问题了,这里须要额定补充一个xhr和jsonp申请JS内容的差别点:
- 重点:在iOS环境下,浏览器cache的JS申请并不会被用于xhr,缓存的形式包含刚申请过、提前prefetch等
所以如果对同步插入没有诉求,能不必jQuery插入就不要用
理论场景
下面我提到了,iOS下的xhr申请JS是不会走缓存的,那么这个影响到底大不大呢?
咱们的我的项目因为一些非凡起因,会在页面内容显示前动静插入7-9个script标签,且均是通过jQuery插入的,大小1KB - 200KB不等,不算小但也没有特地夸大,在PC模仿环境下加载和执行耗时如下:
如果容许xhr应用缓存的话,对应的耗时如下:
但这部分JS在挪动端用户的理论应用场景下,加载和执行的理论统计工夫如下:
样本里安卓和iOS用户数量基本一致,也没有太多极值,能够了解为比安卓多的局部就是本起因导致的。这可不是95线、75线那种较差环境下的指标,光失常的均值和中位数都无能到1s以上,要晓得页面关上满共才2-3s,如果碰到网略微差一点,那这个页面就奔着5s+去了
解决计划
现实的状况当然是把这些JS原地位替换成原生写法即可,但在咱们我的项目里有两个艰难点:
- 这些动静script是有同步要求的,必须在引入的中央执行,不然会缺失必要的上下文
- 引入的反复书写十分多(300+),而且因为历史起因,代码还存储在数据库而非代码库中,无奈做到平安的全量批改
最初我的计划是: - 批改jQuery源码,拦挡这些已确定的script,不让其走xhr申请
- 在原引入逻辑的上方通过script标签失常引入确认拦挡的JS
- 将拦挡的script的非Function申明局部都装在各自的init办法中,在原来引入的中央执行init。就相似于引入Vuex后须要在适合的机会执行Vue.ues原理一样,基于Vue的库都会有启动函数,我也将咱们动静的几个JS的立刻执行局部也别离装进了各自的启动函数,并在原地位执行,模仿同步执行的过程,以便缩小逻辑影响
最初的成果就是常见的动静script都会通过jsonp规范模式而非xhr的形式来申请下发,并顺利借道浏览器缓存,预期iOS加载时常能够升高到Android的程度
jQuery源码剖析
下面我说针对动静script,jQuery会走xhr来进行申请,但仔细的小伙伴在HTML里一搜,发现明明有对应<script>标签呀,但网络上也发了xhr的申请,这是怎么回事呢?这些就须要看看源码了
// 咱们应用jQuery插入动静script// 留神通过jQuery生成的这个script和原生是存在轻微差别的// 例如用原生形式插入本script标签也不会发jsonp申请,起因我还没找到// 所以要走规范jsonp,就都须要用原生书写,不要偷懒var $script = $('<script src="xxx.js">');$('body').append($script);
// jQuery源码(截取并减少正文)// append办法append: function() { // 发xhr的逻辑在这个domManip中,咱们等下看 return domManip( this, arguments, function( elem ) { // 这里是回调函数,会在针对script标签进行加工后、发xhr前执行 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); // 原生的插入动作,也正是有这句,才让咱们能够在HTML中搜到script标签构造 // 但为什么标签明明在,却没发jsonp申请呢?往下看 target.appendChild( elem ); } }); }, // domManip重要局部截取 function domManip (...) { ... // 所有script标签都被disableScript办法加工了 scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); ... // 发送对应xhr申请 jQuery._evalUrl( node.src ); ... } // 下面两个片段咱们离开说 // 先说disableScript做了什么 function disableScript( elem ) { // 通过这行后,script标签的type会被加工,模式例如【trur/text/javascript】 // 很显著,后面加上boolean的type无论如何都是不非法的 // 所以这个奇怪type的script标签会被当作文本块,而不是发网络申请 // 这个type会在前面的逻辑中修改回来,但已插入的script标签批改type并不会让它从新申请 elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; return elem;}// 再看看_evalUrljQuery._evalUrl = function( url ) { // 须要留神,因为是走xhr,所以全局的拦截器设置是失效的,就可能会对本次申请进行加工,例如加上工夫戳之类 return jQuery.ajax( { url: url, type: "GET", dataType: "script", async: false, // 留神这里,是同步的,动静script标签也只有通过这种形式能力做到同步 global: false, "throws": true } );};