前一篇 尝试着实现了前端路由的部分功能 原生 js前端路由系统实现1
影响代码可读和易于理解的因素
1 代码规范 2缩进空格 3减少函数嵌套层次 4函数单一职责 5。。。。
以上这些顶多只能在外观上面看起来清晰(也不一定真的清晰),但随着代码量的增大 代码依然非常难读和易于理解
如果给你几十行代码,程序员通过琢磨也能短时间搞清楚,如果给你上万行代码 即便技术大牛也得花大量的时间去阅读和调试才能看懂
以下是上次实现路由的代码 去掉兼容AMD等库的代码 是百来行,初次看的时候依然有点吃力,需要花点时间才能看懂 之后会把它改成更容易理解和看懂的代码
!function(root,fn){ if (typeof exports === 'object') { // Node. module.exports = fn.call(root); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(function(){ return fn.call(root) }); } else { // Browser globals (root is window) root.diqye = fn.call(root); } }(this,function(){ var TYPE=(function(){ var r={},types=['Arguments','Function','String','Number','Date','RegExp','Error','Null']; for(var i=0,t;t=types[i++];){ !function(t){ r['is'+t]=function(obj){ return Object.prototype.toString.call(obj) === '[object '+t+']'; } }(t) } return r; })(); //前端路由 本来打算写成一个函数,但路由对象只需要全局存在一个即可 并没有发现需要多个对象存在的场景 var route=(function(){ var p={ //全局拦截器,本来想用链表来实现,但基于Javascript的特性用数组实现更加简单 和易用 headUseFns:[], gets:[], type:{ 'number':/^\d+$/, 'string':/^[^\/]+$/, 'date':/^[0-9]{6,6}$/ } }; function getPath(url){ var path=url.split("#")[1]; if(!path)return "/"; if(path.charAt(0)!="/")path="/"+path; return path; } function use(fn){ p.headUseFns.push(fn); } function hashchange(path){ var req={path:path},hlen=p.headUseFns.length; if(hlen==0){ doother(req); return; } //执行拦截器链 !function intec(i){ if(i==hlen){ doother(req); return; } p.headUseFns[i](req,function(){ intec(i+1); }); }(0); } function doother(req){ var path=req.path,hlen=p.gets.length; var a=path.split('?'); if(a[1])path=a[0]; path=path.split('//').join('/'); if(path.length!=1&&path.charAt(path.length-1)=='/')path=path.substr(0,path.length-1); //执行拦截器链 !function intec(i){ if(i==hlen){ return; } if(p.gets[i].match(path,req)){ p.gets[i].fn(req,function(){ intec(i+1); }); }else{ intec(i+1); } }(0); } function get(context,fn){ var match=null; if(TYPE.isFunction(context))match=context; else if(TYPE.isRegExp(context)){ match=function(path,req){ var para=context.exec(path); if(para){ req.para=para; } return para; } }else if(TYPE.isString(context)){ match=stringmatch(context); } var getter={ match:match, fn:fn }; p.gets.push(getter); } function stringmatch(context){ var a=context.split(':'),b=context.split('*'); if(a.length==1&&b.length==1){ return function(path,req){ return path==context; } }else if(a.length!=1){ var reg=p.type[a[1]]; if(reg==null)reg=new RegExp(a[1]); return function(path,req){ var para=path.substr(a[0].length); var r=path.indexOf(a[0])==0&®.test(para); if(r)req.para=para; return r; } }else if(b.length!=1){ return function(path,req){ return path.indexOf(b[0])==0; } } } function start(){ window.onhashchange=function(){ var path=getPath(location.href); hashchange(path); } var path=getPath(location.href); hashchange(path); } function to(path){ window.location.hash=path; } return { start:start, use:use, get:get, to:to, p:p } })(); return { type:TYPE, route:route } });
代码不管怎么写 最终都是操作数据的 没有数据代码啥都不是(即使语法级别的 也算是一个语法树的数据) 所以看代码的时候 一般脑子中会勾勒出一幅数据图
以上代码主要提供几个api start,use,get,to 他们维护的数据是p
var p={ headUseFns:[], gets:[], type:{ 'number':/^\d+$/, 'string':/^[^\/]+$/, 'date':/^[0-9]{6,6}$/ } };
p.headUseFns 是一个数组 里面存放的全部是拦截器 由use函数 和 hashchange事件去维护
p.gets 类似上面 不过是由get函数维护 p.type是为p.gets服务的
当搞明白这些数据的意义时候 不需要看代码了
上面的代码之所以看起浪费时间的地方(相对于来说) 个人觉得就是维护的数据是多个 当一段代码操作数据的时候脑中肯定会想 这个数据的来龙去脉 操作两个数据的时候就要在脑中想两个数据的环境 可惜大脑是单线程的 当在脑中关注的东西超过1个的时候 大脑就会增加负担 同时关注的东西越多大脑就越容易混乱
上述代码我要变的更易读,我需要把代码变少,代码变少我需要把功能变少。功能变少并不是说要去掉功能而是做一个通用的功能 后续的功能通过可扩展去添加 刚好此时的use函数 可以做为一个通用函数 get函数可以基于use去做 代码简化成
var route=(function(){ var p={ intes:[] }; function getPath(url){ var path=url.split("#")[1]; if(!path)return "/"; if(path.charAt(0)!="/")path="/"+path; return path; } function use(fn){ p.intes.push(fn); } function hashchange(path){ var req={path:path},hlen=p.intes.length; if(hlen==0){ doother(req); return; } //执行拦截器链 !function intec(i){ if(i==hlen){ return; } p.intes[i](req,function(){ intec(i+1); }); }(0); } function start(){ window.onhashchange=function(){ var path=getPath(location.href); hashchange(path); } var path=getPath(location.href); hashchange(path); } return { start:start, use:use } })();
当你的代码可读性自认为已经达标的情况下 此时如果增加代码很容易会对原先代码造成污染 久之可能自己都看不懂了(比较实际的情况是人离职留给新来的了 哈哈 …………)这个时候比较成熟的解决法方案就是通过扩展来做其它的东东了
我需要对库本身的API进行扩展 提供一个扩展API函数 这个函数的名字需要参考别的框架中的名字 使用别的流行的框架功能函数命名也可以提高阅读速度 js中扩展api基本上是两个函数 一个是以jquery为主的extends函数 一个是mix 个人比较有洁癖 喜欢用短名字 mix ~~~~~~
//前端路由 本来打算写成一个函数,但路由对象只需要全局存在一个即可 并没有发现需要多个对象存在的场景 var route=(function(){ var p={ intes:[] }; function getPath(url){ var path=url.split("#")[1]; if(!path)return "/"; if(path.charAt(0)!="/")path="/"+path; return path; } function use(fn){ p.intes.push(fn); } function hashchange(path){ var req={path:path},hlen=p.intes.length; if(hlen==0){ doother(req); return; } //执行拦截器链 !function intec(i){ if(i==hlen){ return; } p.intes[i](req,function(){ intec(i+1); }); }(0); } function start(){ window.onhashchange=function(){ var path=getPath(location.href); hashchange(path); } var path=getPath(location.href); hashchange(path); } //扩展API function mix(obj){ for(var key in obj){ result[key]=obj[key]; } } var result={ start:start, use:use, mix:mix } return result; })();
通过mix实现get函数功能
//get 路由实现 !function(mix){ //获取处理后的path 去掉问好 和多余的/ function pathfn(path){ var a=path.split('?'); if(a[1])path=a[0]; path=path.split('//').join('/'); return path; } //function interceptor function get1(fn,cb){ return function(req,next){ if(fn(pathfn(req.path)))cb(req,next); else next(); } } //regExp interceptor function get2(reg,cb){ return function(req,next){ var para=reg.exec(pathfn(req.path)); if(para){ req.para=para; cb(req,next); }else next(); } } //:xxx xxx是可以扩展的 function get3(ps,cb){ var reg=get.type[ps[1]]; return function(req,next){ var path=pathfn(req.path); var para=path.substr(ps[0].length); if(pathfn(req.path).indexOf(ps[0])==0&®.test(para)){ req.para=para; cb(req,next); }else next(); } } //通配符 /xxxx* *后面不允许有字符 function get4(os,cb){ return function(req,next){ if(pathfn(req.path).indexOf(os[0])==0)cb(req,next); else next(); } } //string interceptor function get5(path,cb){ return function(req,next){ if(path==pathfn(req.path))cb(req,next); else next(); } } function get(path,cb){ //匹配路径支持的5中情况 if(TYPE.isFunction(path))this.use(get1(path,cb)); if(TYPE.isRegExp(path))this.use(get2(path,cb)); if(!TYPE.isString(path))return; var a=path.split(':'); if(a.length!=1){ this.use(get3(a,cb)); return; } var b=path.split('*'); if(b.length!=1){ this.use(get4(b,cb)); return; } this.use(get5(path,cb)); } get.type={ 'number':/^\d+$/, 'string':/^[^\/]+$/, 'date':/^[0-9]{6,6}$/ }; mix({ get:get }); }(route.mix);
以上主要是支持了5中路由的匹配方式, 而这五中方式 函数取名 get1 get2 get3 get4 get5 但看函数名字就能知道属于统一类的 只是做不同的分支
以上所有代码地址 https://git.oschina.net/diqye/route.js
后续会扩展querystring功能 即对 url中的参数 和json对象互相转换