浅析jQuery Address Plugin

你是否经历过这样的场景,坐在电脑前,打开网页,看到吸引人的图片,我们兴冲冲的点了进去,一看究竟。
进去后我们大呼上当,“什么破玩意儿啊”,接下来你会怎么做?
关闭网页?又或者是点击“后退”?如果你是“后退综合症”患者,点一下后退,不动,再点一下,又不动,~!@#$%^ 果断关毕这个网页~~

如果我是开发者,我有一个非常多页面的网站,为了不让页面重载,我用Ajax加载这些页面。
但是连我这样的小白都知道,Ajax请求不会改变url,浏览器没法保存历史记录,“前进” “后退”根本不能用~
自己想办法解决,当然没问题,onhashchange啊iframe啊,肯定要花你不少时间,其实有个jQuery有个插件,把这个问题解决了,叫jQuery Address plugin

使用jQuery Address plugin很简单,加一个文件,写几句代码:

<script type="text/javascript" src="jquery.address-1.4.js"></script>
    <script type="text/javascript">
        $.address.state('这里需要基本URL,地址不改变部分,一般为初始化进入页面地址').init(function(event) {
            //插件初始化,一般这里调用 $('.nav a').address(); 实现链接单击监听
        }).change(function(event) {
            //当页面地址更改的时候调用,即#号之后的地址更改
        }).internalChange(function(event) {
            //内部地址更改,即非通过手动更改URL#号后的内容
        }).bind('externalChange',function(event) {
            //外部地址更改,手动修改URL#号后的内容
        });
    </script>

你肯定想问,他是怎么实现的呢?我看了一下源码,总结了几个关键点:

1.自定义事件
Address定义了四个自定义事件(init,change,internalChange,externalChange),
首次载入时,触发init()事件;
其中只要url变化,会触发change()事件;
点击链接自动改变url时,会触发内部变化internalChange()事件;
点击浏览器前进后退时,会触发外部变化externalChange()事件。

_update:更新器,触发相应的自定义事件

    _update = function(internal) {
        _trigger(CHANGE);
        _trigger(internal ? INTERNAL_CHANGE : EXTERNAL_CHANGE);
        _st(_track, 10);
    },

_trigger:触发器,触发自定义事件,继承自jquery的trigger()方法。事件触发后,返回一系列location信息:

{
        value: $.address.value(),
        path: $.address.path(),
        pathNames: $.address.pathNames(),
        parameterNames: parameterNames,
        parameters: parameters,
        queryString: $.address.queryString()
    }

2.检测hash变化,此处要考虑浏览器兼容性

IE6、7要定时检测hash变化,其他浏览器用window.onhashchange监测。

if (!_supportsState()) {
        if ((_msie && _version > 7) || (!_msie && ('on' + HASH_CHANGE) in _t)) {
            if (_t.addEventListener) {
                _t.addEventListener(HASH_CHANGE, _listen, FALSE);
            } else if (_t.attachEvent) {
                _t.attachEvent('on' + HASH_CHANGE, _listen);
            }
        } else {
            _si(_listen, 50);
        }
    }
    listen = function() {
        if (!_silent) {
            var hash = _href(),
                diff = _value != hash;
            if (diff) {
                if (_msie && _version < 7) {
                    _l.reload();
                } else {
                    if (_msie && _version < 8 && _opts.history) {
                        _st(_html, 50);
                    }
                    _value = hash;
                    _update(FALSE);
                }
            }
        }
    }

支持HTML5的浏览器,引用了新的API,就是history.pushState和history.replaceState,通过这个接口可以做到无刷新改变页面URL。pushState是将指定的URL添加到浏览器历史里,replaceState是将指定的URL替换当前的URL。

   if (_supportsState()) {
        _h\[_opts.history ? 'pushState' : 'replaceState'\]({}, '', 
                _opts.state.replace(/\\/$/, '') + (_value === '' ? '/' : _value));
    }

响应前进、后退操作,window对象上提供了onpopstate事件。

$(window).bind('popstate', _popstate).bind('unload', _unload);
    _popstate = function() {
        if (_value != _href()) {
            _value = _href();
            _update(FALSE);
        }
    },

IE6、7浏览器要创建iframe/frame,用来保存hash信息:

if (_msie && _version < 8) {
        var frameset = _d.getElementsByTagName('frameset')\[0\];
        _frame = _d.createElement((frameset ? '' : 'i') + 'frame');
        if (frameset) {
            frameset.insertAdjacentElement('beforeEnd', _frame);
            frameset\[frameset.cols ? 'cols' : 'rows'\] += ',0';
            _frame.noResize = TRUE;
            _frame.frameBorder = _frame.frameSpacing = 0;
        } else {
            _frame.style.display = 'none';
            _frame.style.width = _frame.style.height = 0;
            _frame.tabIndex = -1;
            _d.body.insertAdjacentElement('afterBegin', _frame);
        }
        _st(function() {
            $(_frame).bind('load', function() {
                var win = _frame.contentWindow;                
                _value = win\[ID\] !== UNDEFINED ? win\[ID\] : '';
                if (_value != _href()) {
                    _update(FALSE);
                    _l.hash = _crawl(_value, TRUE);
                }
            });
            if (_frame.contentWindow\[ID\] === UNDEFINED) {
                _html();
            }
        }, 50);
    }
    _html = function() {
        var src = _js() + ':' + FALSE + ';document.open();document.writeln(\\'<html><head><title>' + 
            _d.title.replace('\\'', '\\\\\\'') + '</title><script>var ' + ID + ' = "' + encodeURIComponent(_href()) + 
            (_d.domain != _l.hostname ? '";document.domain="' + _d.domain : '') + 
            '";</' + 'script></head></html>\\');document.close();';
        if (_version < 7) {
            _frame.src = src;
        } else {
            _frame.contentWindow.location.replace(src);
        }
    },

创建的iframe/frame,包含location的hash信息:

<IFRAME style="DISPLAY: none; WIDTH: 0px; HEIGHT: 0px" tabIndex=-1 src="javascript:false;document.open();document.writeln('<html><head><title>jQuery Address Events</title><script>var jQueryAddress = "%2Fjquery%2Faddress%2Fsamples%2Fevents%2Fabout";</script></head></html>');document.close();" jQuery1333892941446="10">
        <HTML>
        <HEAD>
          <TITLE>jQuery Address Events</TITLE>
             <SCRIPT>var jQueryAddress = "/jquery/address/samples/events/about";</SCRIPT>
           </HEAD>
           <BODY></BODY>
     </HTML>
    </IFRAME>