论坛首页 Web前端技术论坛

网页插件开发总结

浏览 5192 次
精华帖 (0) :: 良好帖 (2) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-03-07   最后修改:2010-03-14

目前我们开发的产品涉及到大量的JavaScript,特别是前台的网页插件部分。因为这部分是一个网页中的第三方插件,是用户(站长或者blogger)通过在其网页上加入<script>标签来载入,所以和其他的JavaScript略有不同。


我总结一下,这部分功能有如下需求:

  1. 不影响网页加载。这个很关键,因为很多站长特别关心网页的载入速度。
  2. 不影响网页原有的内容。意味着插件里的HTML元素不能和网页里原有的冲突,JavaScript的命名空间也不能有冲突。
  3. 支持参数和用户自定义。如果使用普通的HTTP请求并动态生成JavaScript的话,必然增大对服务器的压力并大大降低Cache的效率。必须有一种方法使用普通的客户端JavaScript来处理参数。
  4. 支持IE6。这个很让人痛苦。国外的调查显示IE6使用率只有10%多点,但我们网站的数据显示有大约1/3的人还在使用IE6访问。
  5. 代码应该是可维护、可扩展的,最好能像jQuery一样支持自定义组件。这样至少我们自己添加新功能时就很方便,以后还能开放支持第三方应用。

这部分的代码是我从去年下半年开始写的,到目前经过多次调整,目前结构已经基本稳定。对于上面提到的1、3、4已经很好的解决,而2和5则只是部分解决,至少我对目前的方案还不是很满意 。

(一)提高加载速度

在论坛里经常有站长提到使用了某个网页插件,功能很不错,但因为影响了网页加载,所以又删掉了。或者有人干脆就从来不用插件,怕影响加载速度。所以对我们来说,这是不得不考虑的头等大事。

最开始我们插件的JS代码是一次性全部加载到浏览器,然后所有的逻辑都在浏览器完成。这样的好处是,一旦代码全部加载完,客户端的响应非常快,除了特定情况不会再有更多的HTTP请求。缺点也非常明显,即一次性要加载大量代码,而大部分情况下,用户浏览网页时并不会去使用插件,造成很大浪费。另一个问题是,加载到第三方网页上的JavaScript无法直接操作Cookie,从而无法进行用户相关的操作(其实应该是可以的,不过比较麻烦)。不过那时候功能本来就不多,用户也很少,因此并没有什么影响。

但那时我们就意识到这样的结构是不行的,很快就进行了调整,使用了Iframe。这样,Iframe内的内容其实就相当于独立的网页了,和当前的网页完全没有关系,也不会影响加载。并且Iframe内是我们自己的domain,可以直接使用Cookie。当然用户使用时的响应速度就没有原来快了,但还在可接受范围内。

另外一个加快网页加载的方法是,在<script>标签处只加载一些基本的代码,其他的代码包括CSS式样则等到全部网页加载结束后再去加载。这样做的另外一个很好的副作用就是,只要处理得好,独立出来的代码还可以被重用。

这种方法里面要解决两个问题。第一个是动态加载JS代码。这个很好解决,动态生成一个<script>元素,设置好后放到head元素里就好了。只不过设置callback时必须兼容IE。代码如下

    loadModule: function(url, callback) {
        var script = d.createElement('script');
        script.setAttribute('src', url);
        script.setAttribute('charset', "utf-8");
        script.setAttribute('type','text/javascript');
        if (callback) {
            script.onload = callback;
            script.onreadystatechange = function() {
                if (this.readyState == 'complete' || this.readyState == 'loaded') {
                    callback();
                }
            };
        }
        d.getElementsByTagName("head")[0].appendChild(script);
    }






另外一个问题是,如何等到页面全部加载完成再进行某个动作。如果有jQuery,用$(document).ready()就行了。但jQuery库即使压缩的也有50多k,我们是不可能用的。所以我从jQuery的源代码里参考实现了这个函数,可以替代$(document).ready()使用:

function onDocumentReady(callback){
		// Mozilla, Opera and webkit
        if (document.addEventListener) {
            document.addEventListener("DOMContentLoaded", function(){
                document.removeEventListener("DOMContentLoaded", arguments.callee, false);
                callback.call(this);
            }, false );
		// If IE event model is used
        } else if (document.attachEvent) {
        // ensure firing before onload,
        // maybe late but safe also for iframes
            document.attachEvent("onreadystatechange", function(){
                if ( document.readyState === "complete" ) {
                    document.detachEvent( "onreadystatechange", arguments.callee );
                    callback.call(this);
                }
            });
// If IE and not an iframe
        		// continually check to see if the document is ready
            if (document. documentElement.doScroll && window == window.top) (function(){
                if (isReady) return;
                try {
// http://javascript.nwbox.com/IEContentLoaded/
                    document. documentElement.doScroll("left");
                } catch( error ) {
                    setTimeout(arguments.callee, 0);
                    return;
                }
                callback.call(this);
            })();
        }
        window.onload = callback;
};






目前我们的插件一开始只加载7k左右的代码,现在的网页动辄百来k,其影响基本可以忽略。网页加载结束后,再加载15k代码,另外根据参数还可能加载另外的模块。目前插件运行效果非常好,还没有用户抱怨过加载速度的问题。


   发表时间:2010-03-12  
浏览器前端设计就是受煎熬的事情
0 请登录后投票
   发表时间:2010-03-14  
(二)Ajax和Iframe的跨域通信

既然是网页插件,还用到了iframe,自然会涉及到跨域的问题。其实跨域的问题有很多种解决方案,比如XDomainRequest,Crossdomain XHR,还有让人期待的HTML5,可惜这些目前很多主流浏览器都不支持。其实Ajax的跨域还是很好解决的,目前最常用的就是JSONP了,即使没有jQuery,用前一篇里的loadModule函数也可以进行简单的JSONP调用。

不过iframe的跨域问题就有点棘手了。因为插件是在第三方页面上的,而我们的iframe是在我们自己的域名下面的,因此如果iframe里的内容有所更新,我们无法直接从iframe内来通知页面以更新页面上的内容或是改变iframe大小。

不过我们从这篇文章里找到解决方法,文章里作者提出了下面这一个定义和四个法则。

引用
定义:一个窗口可以指一个iframe或者是顶层窗口(即主窗口)
法则1:这个层级系统中任意一个窗口都可以获取到另一个窗口的对象。
法则2:只有同属一个域名的窗口才能相互存取它们的内部状态和数据。
法则3:这个层级系统中的任意一个窗口都可以设置(但不一定能读取)另一个窗口的URL,即使它们属于不同的域名。
法则4:当一个窗口的URL Fragment发生改变时,页面不会刷新。


作者其实提到了三种解决方法,但它们最基本的原理都是上面提到的那些法则。我们使用的是第一种方法,在iframe里通过设置主窗口的URL Fragment来给主窗口传递信息,而主窗口里的JavaScript则每隔100ms轮询一次处理消息并把URL Fragment复位。主窗口中的代码如下:
    setInterval(function() {
        var h = window.location.hash;
        if (h.substring(0,7) == "#height") {
            window.location.hash = "#_";
            currentHeight = parseInt(h.substring(8), 10);
            adjustPostion(currentHeight);
        } else if (…) {
		……
		}
	}, 100);


在IFrame中则用如下代码传递消息:
window.parent.location.href = srcUrl + "#height=280";

或者
parent.window.location.hash = “height=280”;

前者代码里的的srcUrl就是主窗口的URL;后者更方便,但有时在IE下面好像有问题,所以我们还是用的前者。

当然这个方法也有副作用,首先你需要去轮询,必然就会有延迟,而且会改变主窗口的URL(多一个Fragment)。不过对我们来说都还可接受。作者还提到了另外两个方法,都是通过第三个隐藏的iframe作为proxy来避免轮询和改变主窗口URL。一种是每次发送消息时就创建一个proxy iframe,发送完就关掉;另一种是只创建一个proxy iframe,并同过window.resize和监控resize事件来通知proxy iframe有新的消息。可惜这两个方法我们都没法用。因为它们都要求创建一个和主窗口在同一个域名下的iframe,再由此iframe内的JavaScript来传递消息给主窗口,而主窗口是第三方网页,其域名根本不在我们的控制范围内……当然在一定的情况下,这两种方法还是有用的,有兴趣的可以参考原文。

参考:
Cross-Domain Communication with IFrames
http://softwareas.com/cross-domain-communication-with-iframes

0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics