首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【前端编程】加载第三方JS的各种姿势

【前端编程】加载第三方JS的各种姿势

作者头像
李海彬
发布于 2018-03-27 04:06:26
发布于 2018-03-27 04:06:26
4.3K00
代码可运行
举报
文章被收录于专栏:Golang语言社区Golang语言社区
运行总次数:0
代码可运行

从网站开发者的角度来看,第三方JS相比第一方JS有如下几个不同之处:

  • 下载速度不可控
  • JS地址域名与网站域名不同
  • 文件内容不可控
  • 不一定有强缓存(Cache-Control/Expires)

如果你的网站上面有很多第三方JS代码,那么“下载速度的不可控”很有可能导致你的网站会被拖慢。因为JS在执行的时候会影响到页面的DOM和样式等情况。浏览器在解析渲染HTML的时候,如果解析到需要下载文件的script标签,那么会停止解析接下来的HTML,然后下载外链JS文件并执行。等JS执行完毕之后才会继续解析剩下的HTML。这就是所谓的『HTML解析被阻止』。浏览器解析渲染页面的抽象流程图如下:

第三方JS代码并不受网站开发者的控制,很有可能会出现加载时间长甚至加载失败的情况。这时候就会导致整个页面的加载速度变慢。第三方JS代码越多这种风险越大。按照互联网守则:

网站加载速度越慢,用户流失越多

所以要考虑下如何在有很多第三方JS的情况下,保证他们不影响到网站自己的加载速度。我们可以异步加载这些第三方JS代码。

异步加载

异步加载JS的方法很多,最常见的就是动态创建一个script标签,然后设置其src和async属性,再插入到页面中。这里有个DEMO。实际操作的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>

function loadScript(url) {

    var scrs = document.getElementsByTagName('script');

    var last = scrs[scrs.length - 1];

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

    scr.src = url;

    scr.async = true;

    last.parentNode.insertBefore(scr, last);

}

loadScript('test.js');

</script>

PS:为了避免IE8以前版本的bug,并且确保script能插入DOM树,所以这里没有直接document.body.append(src),而是调用了insertBefore方法。

改成异步加载第三方JS代码之后,在JS的下载过程中浏览器会继续解析渲染HTML。流程图就变成了如下:

因为loadScript的操作也是使用JS实现的,所以在JS下载之前会有一段执行JS代码的消耗。但是这段JS代码很简单,很快就会执行完毕。

除了动态创建script标签的方法,异步加载JS的方法还有很多其他奇技淫巧,这里也罗列了一下:

  • 先下载再执行 – 通过XMLHttpReqeust对象或者JSONP方法下载可执行的JS文件,然后使用eval()或者script标签执行JS。第三方JS文件一般是不同域名的且JS内容不可控,所以此方法就不适用了
  • iframe中加载JS – 将你的JS文件直接放到另一个页面的HTML中,然后将此页面URL地址作为iframe标签src属性。此方法需要增加一次页面请求,而且因为是在iframe内部执行了,第三方JS文件本身也需要修改,故并不是很适用
  • 先缓存再执行 – 利用JS文件的强缓存,先使用new Image().src = 'http://url.com/sample.js'之类(或者Object对象)的方法下载JS文件。然后在真正需要解析执行JS的时候下载(有缓存,不必再次下载)和执行JS文件。此方法不仅仅适用于JS文件,同样也可以用于CSS文件。这样我们就可以将静态文件的下载和解析执行(使用)分开,批量并行下载,然后在合适的机会解析执行(使用)。但此方法需要强缓存的配合,第三方JS为了在版本发布时更早的更新JS代码一般都不会设置缓存,甚至有些第三方JS的代码是服务器端动态生成的。所以也不是适用于第三方JS。

浏览器预加载机制

动态创建script标签的方法可以异步加载第三方JS,但它也有缺陷。如果加载代码之前有外链JS文件或CSS文件需要下载,如下面的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script src="app1.js"></script>

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

<script>

function loadScript(url) {

    var scrs = document.getElementsByTagName('script');

    var last = scrs[scrs.length - 1];

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

    scr.src = url;

    scr.async = true;

    last.parentNode.insertBefore(scr, last);

}

loadScript('test.js');

</script>

那么会先下载解析app1.js和app2.js再执行我们的loadScript方法,所以第三方脚本的下载也会被暂停。流程图如下:

而如今我们页面中代码如此复杂,触发这种case的情况很多。上面的DEMO中实际下载过程也确实是这样,动态创建script标签方式下载的test.js需要等到其他CSS和JS文件下载执行完毕之后才开始下载。如下图:

虽然这对页面原有JS的执行不会有大的影响,但会影响到第三方JS代码本身的下载与执行。如何解决这个问题呢?

你可能已经发现上面的例子有个问题:HTML代码中g.js的位置在test.js之后却先下载了。其实这得益于浏览器的预解析机制,会先对HTML代码做静态分析找到外链的JS和CSS文件,然后并行下载下来(但是执行顺序不变)。IE>=8 及其他主流浏览器基本都实现了这个功能。所以在这些支持预先载的浏览器中流程图应该是这样的:

为了利用预加载这个特性,我们可以使用如下的写法:

  1. <script src="app1.js"></script>
  2. <script src="app2.js"></script>
  3. <script src="./test.js" async></script>

复制代码

使用标准的script标签写法,确保浏览器能够正确的识别这是一个外链JS文件。同时设置async标签,浏览器便会异步加载test.js文件,不会暂停掉浏览器的解析执行。流程图如下:

这里有一个DEMO。

但它也并不完美,因为一些旧浏览器并不支持async属性。这会导致这个test.js文件在这些浏览器中不是异步的,并且会阻止掉页面渲染。有一个好消息是移动浏览器大多都支持async标签,如果你的用户大都是移动浏览器的,或者你的产品不支持旧浏览器,那么你可以使用这种方法。

当然如果你不介意第三方JS代码(本身也支持支持)被延后到页面解析完毕后执行,那么你可以再加上defer属性:

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

复制代码

Firefox支持defer属性要比支持async早一点点。而且当浏览器同时使用了async和defer属性之后,浏览器会忽略defer属性。所以可以放心的同时使用async和defer属性。对于不支持async的浏览器,会自动fallback到defer。不过支持程度也就多了一点点,Firefox的旧版占比已经很低了,基本可以忽略不计。

页面onload事件

上面提到的两种方法还有一个缺点:会影响到页面的onload事件。这对第一方JS可能没有影响,因为第一方JS大都是页面主要逻辑,从业务逻辑上来说它们的加载影响到页面onload事件触发不会有问题。但第三方JS则不一样,曾经因为Google被墙GA(Google Analytics简称)的加载就会特别慢甚至失败。导致了很多使用了GA的页面加载特别”慢”,页面一直处于loading状态。大家先通过fiddler代理来设置test.js的加载时间为10秒,然后打开之前的DEMO,查看页面的loading是否会被延长。下面是我打开第一个DEMO的结果:

可以看到因为test.js的下载速度变慢,整个页面一直处于loading状态。页面的load事件要等到全部加载完成之后才会触发。如果页面中的主要逻辑是在页面load之后再执行,那么页面很可能会在很长一段时间内不可用。极大的影响了用户的使用体验。

Friendly IFrame方法

为了解决这个问题,meebo的工程师想了一个方案来解决这个问题:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
(function(url){

    // 第一部分

    var dom,doc,where,iframe = document.createElement('iframe');

    iframe.src = "javascript:false";

    iframe.title = ""; iframe.role="presentation";

    (iframe.frameElement || iframe).style.cssText = "width: 0; height: 0; border: 0";

    where = document.getElementsByTagName('script');

    where = where[where.length - 1];

    where.parentNode.insertBefore(iframe, where);



    // 第二部分

    try {

        doc = iframe.contentWindow.document;

    } catch(e) {

        // IE下如果主页面修改过document.domain,那么访问用js创建的匿名iframe会发生跨域问题,必须通过js伪协议修改iframe内部的domain

        dom = document.domain;

        iframe.src="javascript:var d=document.open();d.domain='"+dom+"';void(0);";

        doc = iframe.contentWindow.document;

    }

    doc.open()._l = function() {

        var js = this.createElement("script");

        if(dom) this.domain = dom;

        js.id = "js-iframe-async";

        js.src = url;

        this.body.appendChild(js);

    };

    doc.write('<body onload="document._l();">');

    doc.close();

})('test.js');

上述代码分为两个部分:

  • 创建了一个隐藏的iframe标签,设置其src值为JS代码,然后插入到主页面中
  • 在iframe标签load之后加载JS脚本

这样加载Javascript,就不会阻止浏览器的onload事件,提升普通用户的体验。还有另一个好处:第三方的Javascript代码在独立的iframe中运行,不会与主页面中的JS相互干扰。已经有了一些基于这个想法的开源实现,例如:lightning.js是一个专用于快速、安全、异步地加载第三方JS代码的库。

这个方法也不完美,它需要创建一个iframe标签导致了开销较大。同时还需要第三方JS本身的支持。第三方JS代码运行在iframe中,导致它无法获取到页面上的信息。虽然它并非跨域可以获得window.parent,但是第三方代码并不能知道自己是否在iframe中,需要在加载第三方JS代码的时候通知它。具体的通知方法千变万化,而第三方JS的内容又不受我们控制。

富媒体广告JS(用于展示交互广告的JS)一般都会运行在隔离环境里面,且不需要(不允许)访问外部的window对象。如果你需要加载的第三方JS全部是广告时,那么使用这个方案是OK的,否则并不是最为合适。幸运的是有一个叫iAB(The Interactive Advertising Bureau,简称iAB)的组织,建立了一套工业级标准。虽然标准已经比较旧了,但是里面提到了通过设置变量inDapIF为true来通知第三方JS:你现在正运行在iframe中。因为iAB成员较多影响力大,所以遵循这个标准是有好处的,比起自己玩一套要好的多。

总结

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Golang语言社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
给冰冰看的SpringAOP面试题
Spring之前已经跟学弟具体聊很详细的IOC已经循环依赖问题,接下来要接着为跟学妹们聊另外的一个模块了,那就是AOP,这也是面试官比较喜欢问的一个模块点。
敖丙
2021/09/16
4030
AOP执行流程
之前跟大家聊IOC的时候跟大家聊过它的启动过程,同样的AOP也有指定的执行流程,但是需要IOC作为基础。
Vincent-yuan
2021/12/16
3500
AOP执行流程
Spring AOP原理分析一次看懂
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于
Tanyboye
2018/07/02
5790
Spring AOP分析(2) -- JdkDynamicAopProxy实现AOP
YGingko
2017/12/28
1.6K0
Spring AOP,从入门到进阶
我们常常在核心业务逻辑中看到诸如事务管理、日志记录或性能统计等行为,这些行为的代码量一般也就几行,但是却分散在多个类中的多个方法内;这些四处分散的重复代码不仅不利于后期的维护工作,同时也显得核心业务逻辑混乱无章。为了解决这一问题,面向切面编程(Aspect-Oriented Programming)应运而生。不同于面向对象编程(Object-oriented Programming),AOP不再以类(Class)为模块化单元,而是以切面(Aspect)作为模块化单元,也就是通过切面来封装那些四处分散的事务管理、日志记录和性能统计等行为。可能有的人会疑惑,可以将这些行为单独封装起来,并不见得一定要使用AOP啊!别杠,单独封装依然无法保持核心业务逻辑的清清爽爽啊,还是会夹杂在一起,不是吗?顺便提一句,横切关注点(Crosscutting Concern),指的就是事务管理、日志记录和性能统计等行为。
程序猿杜小头
2022/12/01
4210
Spring AOP,从入门到进阶
深入理解Spring框架之AOP实现原理
该动态代理是基于接口的动态代理,所以并没有一个原始方法的调用过程,整个方法都是被拦截的。
JavaQ
2019/05/15
2.3K0
深入理解Spring框架之AOP实现原理
SpringAop源码分析(基于注解)四:拦截器链
本文依据JdkDynamicAopProxy来分析,对CGLIB感兴趣的同学看一看ObjenesisCglibAopProxy相关代码。 JdkDynamicAopProxy实现了InvocationHandler接口,我们来看下invoke()方法:
周同学
2019/10/24
9200
Spring源码解析(九):AOP源码之@Aspect所有相关注解解析
InstantiationModelAwarePointcutAdvisorImpl构造函数 创建具体通知
Java微观世界
2025/01/21
3900
Spring源码解析(九):AOP源码之@Aspect所有相关注解解析
Small Spring系列九:aop (二)
在Small Spring系列八:aop (一)中,我们实现了Pointcut和MethodLocatingFactory,Pointcut根据给定一个类的方法判断是否符合expression表达式,MethodLocatingFactory更具targetBeanName和methodName返回一个Method对象。本章我们来实现aop的链式调用和Cglib的动态代理。
java干货
2021/02/19
3990
Small Spring系列九:aop (二)
SpringFramework之ReflectiveMethodInvocation
    ReflectiveMethodInvocation是AOP中一个重要的类,这个类在JdkDynamicAopProxy的invoke方法中使用到它,如下的List-1
克虏伯
2019/07/15
1.5K0
SpringFramework之ReflectiveMethodInvocation
Spring的JDK动态代理如何实现的(源码解析)
从上面的源码可以看出Spring中的JDKDynamicAopProxy和我们自定一JDK代理是一样的,也是实现了InvocationHandler接口。并且提供了getProxy方法创建代理类,重写了invoke方法(该方法是一个回调方法)。具体看源码
@派大星
2023/06/28
2740
Spring的JDK动态代理如何实现的(源码解析)
一文搞懂Spring-AOP原理
文章目录 1. 简介 2. 添加依赖 3. 通知 4. 连接点 5. 切点 6. 切面 7. 实现 8. 注解的实现 9. 切入点表达式 10. 切面执行顺序(Order) 10.1. 注意点 11. Aspect实例化模型 12. 获取参数(args) 13. PointCut 14. PointCuts 15. ClassFilter 16. ClassFilters 17. MethodMatcher 18. MethodMatchers 19. Advice 20. Advisor 20.1.
爱撒谎的男孩
2019/12/31
1.1K0
徒手撸框架--实现Aop
上一讲我们讲解了Spring 的 IoC 实现。大家可以去我的博客查看,这一讲我们继续说说 Spring 的另外一个重要特性 AOP。之前在看过的大部分教程,对于Spring Aop的实现讲解的都不太透彻,大部分文章介绍了Spring Aop的底层技术使用了动态代理,至于Spring Aop的具体实现都语焉不详。这类文章看以后以后,我脑子里浮现的就是这样一个画面:
技术zhai
2019/02/15
3580
《Spring设计思想》AOP实现原理(基于JDK和基于CGLIB)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://louluan.blog.csdn.net/article/details/51155821
亦山
2019/05/25
5370
spring aop (下)调用拦截链
之前我们说到,当使用jdk动态代理时,会调用该类的getProxy方法生成一个代理对象,返回给外界调用。该类继承了InvocationHandler,所有代理对象的方法调用都会被拦截到该对象的invoke上。
平凡的学生族
2019/06/03
9570
【10】Spring源码-分析篇-AOP源码分析
  本文我们开始讲解Spring中的AOP原理和源码,我们前面手写了AOP的实现,了解和自己实现AOP应该要具备的内容,我们先回顾下,这对我们理解Spring的AOP是非常有帮助的。
用户4919348
2022/10/28
8980
【10】Spring源码-分析篇-AOP源码分析
Spring AOP高级——源码实现(3)AopProxy代理对象之JDK动态代理的创建过程
spring-aop-4.3.7.RELEASE    在《Spring AOP高级——源码实现(1)动态代理技术》中介绍了两种动态代理技术,当然在Spring AOP中代理对象的生成也是运用的这两种技术。本文将介绍Spring AOP如何通过JDK动态代理的方式创建代理对象。   JDK动态代理以及CGLIB代理这两种生成代理对象的方式在Spring AOP中分别对应两个类:JdkDynamicAopProxy和CglibAopProxy,而AopProxy是这两个类的父接口。   AopProxy接口中
用户1148394
2018/01/09
1.2K0
【小家Spring】探索Spring AOP中aopalliance的Joinpoint、MethodInvocation、Interceptor、MethodInterceptor...
在这篇博文:【小家Spring】详解Spring AOP中底层代理模式之JdkDynamicAopProxy和CglibAopProxy(ObjenesisCglibAopProxy)的源码分析 我们已经能够知道了,代理对象创建好后,其实最终的拦截工作都是交给了MethodInvocation,JDK交给:ReflectiveMethodInvocation,CGLIB交给CglibMethodInvocation
YourBatman
2019/09/03
2.9K0
【小家Spring】探索Spring AOP中aopalliance的Joinpoint、MethodInvocation、Interceptor、MethodInterceptor...
【小家Spring】Spring AOP各个组件概述与总结【Pointcut、Advice、Advisor、Advised、TargetSource、AdvisorChainFactory...】
Spring AOP作为整个Spring体系最最重要的分支之一,若干技术都是基于它的(比如事务、异步、缓存等)
YourBatman
2019/09/03
3.8K0
【小家Spring】Spring AOP各个组件概述与总结【Pointcut、Advice、Advisor、Advised、TargetSource、AdvisorChainFactory...】
AOP,确实难,会让很多人懵逼,那是因为你没有看这篇文章!
原理比较简单,主要就是使用jdk动态代理和cglib代理来创建代理对象,通过代理对象来访问目标对象,而代理对象中融入了增强的代码,最终起到对目标对象增强的效果。
路人甲Java
2020/06/30
5K0
AOP,确实难,会让很多人懵逼,那是因为你没有看这篇文章!
推荐阅读
相关推荐
给冰冰看的SpringAOP面试题
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验