You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<!-- Google Analytics --><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*newDate();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create','UA-XXXXX-Y','auto');ga('send','pageview');</script><!-- End Google Analytics -->
以上是GA压缩过后的投放代码,可能看起来不是很明显。这里美化一下,看起来会容易些,如下:
<!-- Google Analytics --><script>/** * Creates a temporary global ga object and loads analytics.js. * Parameters o, a, and m are all used internally. They could have been * declared using 'var', instead they are declared as parameters to save * 4 bytes ('var '). * * @param {Window} i The global context object. * @param {HTMLDocument} s The DOM document object. * @param {string} o Must be 'script'. * @param {string} g Protocol relative URL of the analytics.js script. * @param {string} r Global name of analytics object. Defaults to 'ga'. * @param {HTMLElement} a Async script tag. * @param {HTMLElement} m First script tag in document. */(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;// Acts as a pointer to support renaming.// Creates an initial ga() function.// The queued commands will be executed once analytics.js loads.i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},// Sets the time (as an integer) this tag was executed.// Used for timing hits.i[r].l=1*newDate();// Insert the script tag asynchronously.// Inserts above current tag to prevent blocking in addition to using the// async attribute.a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');// Creates a default tracker with automatic cookie domain configuration.ga('create','UA-XXXXX-Y','auto');// Sends a pageview hit from the tracker just created.ga('send','pageview');</script><!-- End Google Analytics -->
<linkhref="http://example.com/test.css?rtt=2" rel="stylesheet"><!-- body 内容 --><script>varscript=document.createElement('script');script.src="http://example.com/test.js?rtt=1&a";document.getElementsByTagName('head')[0].appendChild(script);</script><script>varscript=document.createElement('script');script.src="http://example.com/test.js?rtt=1&b";document.getElementsByTagName('head')[0].appendChild(script);</script>
varGLOBAL=window;if(window.parent!==window&&window['inDapIF']){GLOBAL=window.parent;}// 使用GLOBAL替代window// TODO
其他类型的投放代码
有些第三方服务不需要直接获取页面的数据,它们只需要有展示自己内容的区域即可,比如:
<iframeheight='300' scrolling='no' src='//codepen.io/zmmbreeze/embed/vLbpa/?height=300&theme-id=20219&default-tab=css,result&embed-version=2' frameborder='no' allowtransparency='true' allowfullscreen='true' style='width: 100%;'>See the Pen <ahref='http://codepen.io/zmmbreeze/pen/vLbpa/'>DOWN-TO-THE-LINE control for radical web typography</a> by mzhou (<ahref='http://codepen.io/zmmbreeze'>@zmmbreeze</a>) on <ahref='http://codepen.io'>CodePen</a>.
</iframe>
投放代码与异步加载
本文先从第三方Javascript脚本的重要组成部分“投放代码”讲起。先从一个最例子看起:Google Analytics(以下简称GA),是Google提供的用于网站监测等一系列功能的服务。网站开发者将GA提供的投放代码放到自己网站上需要监测的页面(一般是全站添加)。
以上是GA压缩过后的投放代码,可能看起来不是很明显。这里美化一下,看起来会容易些,如下:
其实做的事情很简单:创建一个script标签,设置其src值为GA的第三方Javascript地址。然后插到页面的DOM树中,再初始化ga的配置。这样来实现浏览器“异步”加载第三方Javascript的功能。之所以采用异步实现,是为了不block掉页面正常的逻辑。这也是在开发第三方脚本时很重要的一个要求:
投放代码的形式有很多种,上面提到的最常见一些。GA其实还提供了另一种投放代码,如下:
GA的官方文档里面说明了:如果开发者的网站主要服务的用户较大比例使用高级浏览器(Chrome,IE11及以上)或者移动端浏览器占比较大那么推荐使用这种形式的投放代码。为什么呢?
首先从浏览器的加载执行顺序开始说起。之前已经说到前一种形式是使用JS来动态创建script标签以实现异步加载外链的JS代码,这样可以不Block掉页面。这是它的巨大优势,但是同时也带来了一个劣势。
上述代码中动态加载两份不同的Javascript代码,虽然使用了异步加载Trick,但是实际浏览器下载的过程是这样的:
因为Javascript可以操作CSSOM,所以浏览器在加载Javascript的时候需要等到CSS完全加载解析完毕之后才能执行 script 标签中的Javascript。再看下面的代码:
上述代码,浏览器是并行下载CSS文件和Javascript文件的,如下图:
现代浏览器(包括 IE8/9 和 Android 2.3/2.2)会预解析查找可以下载的外部文件,并行下载并保持执行不变。不过浏览器无法通过解析HTML来识别动态创建的外链JS地址,所以也无法预下载它们。同时现代浏览器提供了async属性,浏览器会异步的加载async的外链script标签,不会block掉页面(但不保证执行顺序)。这就同时享受到了预下载和异步加载两个福利,带来巨大的性能提升。所以对于使用现代浏览器用户多的网站更推荐使用这种方式。
静态与动态
大部分第三方服务是使用CDN来承载自己的外链JS脚本,比如GA。也有一部分是使用动态server(例如PHP服务器)来生成外链的JS脚本,它的优势在于针对不同的开发者提供不同的代码,免去了初始个性化数据的获取请求。例如:
服务器会根据userid来生成专供指定开发者网站的代码。这些代码里面通常带有其个性化的配置数据,这样减少了一次配置数据的请求,大大提前了代码执行的时间点。
当然这样也是有缺点的,最重要的一点就是比较难利用CDN加速。大部分CDN通常根据文件名来缓存静态文件,即使把Javascript脚本改成“service_1234567.js”的形式缓存到CDN上,最后也会因为文件太多导致脚本更新困难的问题。
除此之外,有些开发者因为安全隐私等原因喜欢将Javascript脚本放在自己的服务器上,然后手动更新。这种情况,动态服务器生成的脚本就比较难满足这个小众需求了。
另外因为CDN不能使用,所以当动态服务器不稳定时,容易导致加载javascript脚本的时间特别长。虽然可以使用异步加载,但是浏览器在加载东西的时候左上角还是会出现loading。普通用户可以感知到页面还没有加载完成。这会让用户很困惑:“页面都已经展现,可为什么浏览器还在展现,到底在做什么请求呢?”
甚至会影响到网站本身的业务。因为单个浏览器标签同时下载的连接数有限制,导致其他网页原本的请求被Block掉。
Friendly Iframe
为了解决这个问题,meebo的工程师们想到了一个用Friendly Iframe来解决js加载时候的问题。所谓Friendly Iframe即是iframe的domian和其所在主页面的domain是相同的。例子如下:
上述代码分为两个部分:
这样加载Javascript,浏览器就不会出现loading,提升普通用户的体验。当然这还有一个附带的好处,第三方的Javascript代码在独立的iframe中运行,不会与主页面中的JS相互干扰。毕竟即使现在还是有不少小众网站会选择扩展Native对象的方法。作者本人就遇到过有网站开发者修改
Array.prototype.push
方法的情况。当然这有方法还是有缺陷的:投放代码本身太过复杂,网页开发者在实际使用时容易有问题。个人推荐的做法是:如何加载是网站开发者来决定的事情,第三方Javascript脚本应该要支持能想得到的各种奇技淫巧。美国互动广告局(The Interactive Advertising Bureau,简称IAB)提出过一个异步加载广告代码的Best Practice。里面提到了用变量 inDapIF 作为标志,提示Javascript脚本在动态iframe内部执行。所以我们可以用如下方法来判断:
其他类型的投放代码
有些第三方服务不需要直接获取页面的数据,它们只需要有展示自己内容的区域即可,比如:
因为使用了不同域名下的iframe,所以是在隔离环境内运行第三方代码。这样第三方代码就不会和开发者站点的代码冲突。而且因为域名不同,天然提供安全性的保障,第三方代码不能获取或修改开发者站点的重要信息。缺点也很明显:就是能做的事情仅限于iframe内部。比较适合不需要访问页面就可以提供内容的需求。
自从Web 2.0开始,UGC类型的网站越来越多,用户可以自主黏贴文字甚至是HTML代码到网站中去,例如社交网站的简介。所以有的时候第三方服务的使用者并直接是网站开发者,而是网站的用户。网站为了安全一般不会让用户直接贴script表情或者是iframe等特殊HTML标签。所以有些第三方服务提供的投放代码仅仅是一个img标签,将需要展示的内容放在图片中。如果你经常浏览github,你会发现有个集成测试的工具叫做Travis CI。它提供了一段投放代码用于展示开源库的测试状态,如下:
至此头脑较为发散的同学可能已经想到如下的投放代码了:
虽然这是一段典型的XSS攻击代码,但是它也可以称得上是一种投放代码......
The text was updated successfully, but these errors were encountered: