这篇文章主要介绍了重写 document.write 实现无阻塞加载 js 广告, 需要的朋友可以参考下
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
无阻塞加载 javascript,对于页面性能优化有很大的作用,这样能有效的减少 js 对页面加载的阻塞。特别是一些广告 js 文件,由于广告内容有可能是富媒体,更是很可能成为你页面加载提速的瓶颈,高性能 javascript 告诉我们,同学,提升你的网页速度,就无阻塞地加载 JS 吧。
于是便有一下代码出现。
- (function() {
- var s = document.createElement('script');
- s.type = 'text/javascript';
- s.async = true;
- s.src = 'http://yourdomain.com/script.js';
- var x = document.getElementsByTagName('script')[0];
- x.parentNode.insertBefore(s, x);
- })();
上边都是大家熟悉的,看过书的同学都知道这样无阻塞加载的好处,效果挺不错的,当此等无阻塞脚本遇到一般 js 广告就来了写问题——广告代码出现在 html 里面了却不显示广告。
纳尼?HTML 出来了不渲染到页面上?
先看看广告 js 代码
- document.write('<img src="http://img.phperz.com/logo_small.gif" alt="Logo">');
代码挺简单就一个 document.write 输出 HTML 代码(相信很多广告商的广告都这样),页面不显示广告问题在哪里呢? 问题就在这个 document.write。为什么?先 w3schools 看看 document.write 的定义很使用吧。
定义和用法
write() 方法可向文档写入 HTML 表达式或 JavaScript 代码。
可列出多个参数 (exp1,exp2,exp3,...) ,它们将按顺序被追加到文档中。
方法:
一是在使用该方在文档中输出 HTML,另一种是在调用该方法的的窗口之外的窗口、框架中产生新文档。在第二种情况中,请务必使用 close() 方法来关闭文档。
但其原理是在页面流输入过程中执行,一旦页面加载完毕,再次调用 document.write(),会隐式地调用 document.open() 来擦除当前文档并开始一个新的文档。也就是说如果在 HTML 加载完后我们再使用 document.write 会檫除之前生成 html,而显示 document.write 输出的内容。
而我们例子中在页面加载完后在在 html 中输出 document.write,就不会被执行了。问题知道了,原理知道了,那怎么解决这个问题呢?
异步利用 ajax,行不同,很多广告文件都是第三方的,在不同域名下,存在跨域问题,而且不能我们控制其代码的输出。在这种情况下我们想到了一个办法就是重写掉 document.write,在 js 文件加载结束后再把 document.write 重写回去。看代码。
第一版本无阻塞加载 js 广告:
- function LoadADScript(url, container, callback) {
- this.dw = document.write;
- this.url = url;
- this.containerObj = (typeof container == 'string' ? document.getElementById(container) : container);
- this.callback = callback ||
- function() {};
- }
- LoadADScript.prototype = {
- startLoad: function() {
- var script = document.createElement('script'),
- _this = this;
- if (script.readyState) { //IE
- script.onreadystatechange = function() {
- if (script.readyState == "loaded" || script.readyState == "complete") {
- script.onreadystatechange = null;
- _this.finished();
- }
- };
- } else { //Other
- script.onload = function() {
- _this.finished();
- };
- }
- document.write = function(ad) {
- var html = _this.containerObj.innerHTML;
- _this.containerObj.innerHTML = html + ad;
- }
- script.src = _this.url;
- script.type = 'text/javascript';
- document.getElementsByTagName('head')[0].appendChild(script);
- },
- finished: function() {
- document.write = this.dw;
- this.callback.apply();
- }
- };
页面调用代码:
- var loadScript = new LoadADScript('ad.js', 'msat-adwrap',
- function() {
- console.log('msat-adwrap');
- });
- loadScript.startLoad();
- var loadScript = new LoadADScript('ad2.js', 'msat-adwrap',
- function() {
- console.log('msat-adwrap2');
- });
- loadScript.startLoad();
- var loadScript = new LoadADScript('ad3.js', 'msat-adwrap',
- function() {
- console.log('msat-adwrap3');
- });
- loadScript.startLoad();
广告 js 代码
- //ad.js
- document.write('<img src="http://images.cnblogs.com/logo_small.gif" alt="Logo">');
- //ad2.js
- document.write('<img src="http://www.baidu.com/img/baidu_sylogo1.gif" width="270" height="129" usemap="#mp">');
- //ad3.js
- document.write('<img alt="Google" height="95" id="hplogo" src="http://www.google.com/images/srpr/logo3w.png" width="275">');
第一版本的问题是在多个文件调用的时候,会出现一些问题:
1. 由于文件加载的速度不一样,导致可能有些先加载有些后加载,也就是无序的,而且很多时候我们需要的是有序的。比如我们需要先加载第一屏的广告。
2. 想有些广告需要前置设置一些参数的,例如 google adsense
为了解决这两个问题好进一步修改成最终无阻塞加载 js 版本。
HTML 页面代码:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <title>
- new_file
- </title>
- <script type="text/javascript" src="loadscript.js">
- </script>
- </head>
- <body>
- <div id="msat-adwrap">
- </div>
- <div id="msat-adwrap2">
- </div>
- <script type="text/javascript">
- loadScript.add({
- url: 'ad.js',
- container: 'msat-adwrap',
- callback: function() {
- console.log('msat-adwrap');
- }
- }).add({
- url: 'ad2.js',
- container: 'msat-adwrap2',
- callback: function() {
- console.log('msat-adwrap2');
- }
- }).add({ //google adsense
- url: 'http://pagead2.googlesyndication.com/pagead/show_ads.js',
- container: 'msat-adwrap',
- init: function() {
- google_ad_client = "ca-pub-2152294856721899";
- /* 250x250 rich */
- google_ad_slot = "3929903770";
- google_ad_width = 250;
- google_ad_height = 250;
- },
- callback: function() {
- console.log('msat-adwrap3');
- }
- }).execute();
- </script>
- </body>
- </html>
loadscript.js 源代码
- /**
- * 无阻塞加载广告
- * @author Arain.Yu
- */
- var loadScript = ( function() {
- var adQueue = [], dw = document.write;
- //缓存js自身的document.write
- function LoadADScript(url, container, init, callback) {
- this.url = url;
- this.containerObj = ( typeof container == 'string' ? document.getElementById(container) : container);
- this.init = init ||
- function() {
- };
- this.callback = callback ||
- function() {
- };
- }
- LoadADScript.prototype = {
- startLoad : function() {
- var script = document.createElement('script'), _this = this;
- _this.init.apply();
- if(script.readyState) {//IE
- script.onreadystatechange = function() {
- if(script.readyState == "loaded" || script.readyState == "complete") {
- script.onreadystatechange = null;
- _this.startNext();
- }
- };
- } else {//Other
- script.onload = function() {
- _this.startNext();
- };
- }
- //重写document.write
- document.write = function(ad) {
- var html = _this.containerObj.innerHTML;
- _this.containerObj.innerHTML = html + ad;
- }
- script.src = _this.url;
- script.type = 'text/javascript';
- document.getElementsByTagName('head')[0].appendChild(script);
- },
- finished : function() {
- //还原document.write
- document.write = this.dw;
- },
- startNext : function() {
- adQueue.shift();
- this.callback.apply();
- if(adQueue.length > 0) {
- adQueue[0].startLoad();
- } else {
- this.finished();
- }
- }
- };
- return {
- add : function(adObj) {
- if(!adObj)
- return;
- adQueue.push(new LoadADScript(adObj.url, adObj.container, adObj.init, adObj.callback));
- return this;
- },
- execute : function() {
- if(adQueue.length > 0) {
- adQueue[0].startLoad();
- }
- }
- };
- }());
来源: