从零写一个路由插件
因为工作的需要,可能有的时候需要用的路由,虽然在Github上有许多大大小小已成熟的路由插件,但人有时候就是这样,宁愿自己写一个,也不愿多看几遍文档。这个是已经完成的路由插件地址:miniRouter,插件大小不足3kb,也没有任何依赖。虽然简单,我也觉得有必要整理成文,分享给大家。因为我使用的代码风格规范是JavaScript Standard Style所以代码我不会以分号结尾。
那么现在写一个插件,很重要的是要兼容AMD/CMD等标准,自适应不同的引用。那么这个“起手式”就很重要。我记得我出来工作那会写插件大多以这种形式:
(function(){
var MiniRouter = function(ele, opt) {
// ...
this.init()
}
MiniRouter.prototype = {
init: function() {
// ...
}
}
})()
原型模式在插件开发中有举足轻重的地位,但这并不是我们上述的起手式,那么插件开发的起手式是什么呢?我也就不卖关子了,就是下面这样:
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(function () {
return factory(root)
})
} else if (typeof exports === 'object') {
module.exports = factory
} else {
root.PluginName = factory(root)
}
})(this, function (root) {
'use strict'
// 这里写实现方法,并将插件对象return出去
var PluginName = {}
return PluginName
})
上面这种写法,兼顾了多种引用形式,同样的,以一个自执行函数将实参this和插件实现方法传给了内部函数 。在浏览器中这里的实参this指向了全局window对象。在这里的路由插件,我依然选择原型模式来实现。
首先开始,路由插件最重要的是监听路由的变化,这个很好办。我们只要监听window对象下的hashchange
事件即可,事件对象有下面两个属性:
- newURL:当前页面新的URL
- oldURL:当前页面旧的URL
而针对某些不支持该事件的浏览器,我们使用轮询的URL变化监听即可。那么路由变化监听的代码就是这样的:
// 监听路由变化
if (('onhashchange' in window) && ((typeof document.documentMode === 'undefined') || document.documentMode === 8)) {
window.onhashchange = function () {
this.router(this.hashHandle())
}.bind(this)
} else {
setInterval(function () {
var ischanged = this.isHashChanged()
if (ischanged) {
this.router(this.hashHandle())
}
}.bind(this), 150)
}
// 如果不支持onhashchange,isHashChanged方法的实现
var hash = this.hashHandle()
if (hash !== this.prevRouterHash) {
this.prevRouterHash = hash
return true
}
this.prevRouterHash = hash
在这段代码中,我们实现了上述的监听路由变化功能,在不支持onhashchange
事件的情况下,我们调用了isHashChanged
方法来判断路由的变化。
那么路由监听完了,还有一个问题是第一次进入页面,并不会触发onhashchange
事件,那我们就需要在页面加载之后执行,页面加载完成之后就执行,使用onload
事件?这明显是不行的,该事件会等到页面所有元素都加载完成才会执行。那么如果我们使用了jQuery十分好实现,只需要:
$(function(){
// ...
})
或者这样:
$.ready(function(){
// ...
})
但前面我也说过,这个插件并没有依赖任何库,所以上述的方法是不行的,我们得自己封装一个DOM加载完成后的方法。
如果这个插件你准备只兼容现代浏览器,那么太棒了,我们可以用很少的代码完成这件事。这样,我们只需要监听document.readyState
或者是监听DOMContentLoaded
即可。不过需要注意的是,浏览器对document.readyState
的支持也有着一些差异。我们需要通过判断浏览器是否支持document.attachEvent
来确定。那么我们就该这样实现这个DOM加载完成方法:
function ready(fn) {
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
如果你遗憾的需要支持到IE8,像我这样,那么上述的方法就不能使用了,我们需要多一点的判断,在这里,我么多监听了一个onreadystatechange
:
function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn);
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState != 'loading')
fn();
});
}
}
那么这样,我们就完成了基本的工作。如果有兴趣,可以点开第一段miniRouter的链接,查看完整的代码。