flowJS源码个人分析
刚刚在腾讯云技术社区前端专栏中看到一篇腾讯高级前端工程师写的《一个只有99行代码的js流程框架》觉得很屌,感觉是将后台的简单的工作流思维搬到了前端js实现,本人不才在这里拜读解析下源码,而且经常有新手问我的很多问题其实是不懂如何调试一段js代码,在这这里就详细说明下我是怎么调试flowJS的源码思路的。前端大神见笑小弟的总结
首先第一段:
var obj = arguments[0], flow = {init: function(){}}, flowData = {}, noop = function(){}, init = 'init', trace = flowJS.trace = flowJS.trace||[init];
这段代码就是把要用到的变量在开始声明用逗号隔开,为了代码的整洁和节省代码的语句数量。
第二段:
if(({}).toString.call(obj) === '[object Object]'){//判断第一个参数是否是对象,这个方法最准确 extend(flow, obj); flow.init.call(extend({getCurr:function(){ return init; }, stepData:function(dataName){ return dataName?undefined:{}; }, getPrev:noop, fail:noop, success:noop}, new Step(init))); }
读到extend(flow,obj);我们应该猜到是将参数和flow合并,我们在来看extend方法:
function prop(obj, fun){ for(var p in obj) { obj.hasOwnProperty(p) && fun(p);//判断这个属性是原始属性还是原型上的属性 } } /*对象的合并扩展*/ function extend(des, src){ prop(src, function(p){ des[p] = src[p]; }); return des; }
extend的方法是将src的原生属性添加到des对象中。然后我们接着主线看:
flow.init.call(extend({getCurr:function(){ return init; }, stepData:function(dataName){ return dataName?undefined:{}; }, getPrev:noop, fail:noop, success:noop}, new Step(init)));
这段代码就是要执行参数中的init方法并且将一个自定义添加Step(init)返回的对象中的属性的对象作为init中的this对象。那重点在Step方法,但由于里面有点复杂刚开始接触你看不到他在里什么的各种方法的用处,在这里我们最后结合《一个只有99行代码的js流程框架》文章提供的api例子来调试里面各个方法的作用这样更好的梳理整个功能的运行逻辑,在这里就体现了张镇圳大神的所说的一个模块的功能中代码的逻辑梳理、可读性、语义化多么重要,但flowJS这个框架可以理解为项目的在js原生上封装了一层脱了业务层的框架,这种框架一般是组长或是高级前端工程来维护,而且一般随着时间的积累慢慢成熟成型后期的改动会很小,所以这里只要相对用面向对象梳理下逻辑就可以,而业务层随客户的不断增多,需求的改动频繁出现,而且项目的大部分改动工作量都在业务层上,所以业务层代码逻辑梳理、可读性、语义化尤为重要,关系到后期维护的成本。
我们先从文章中第一个例子来调试;
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>flowJS demo1</title> </head> <body> </body> <script type="text/javascript" src="./flowJS.js"></script> <script type="text/javascript"> flowJS({ init:function(){ this.setNext('步骤A').setNext('步骤B').setNext('步骤C'); //预先设置好需要经过的每个步骤 this.next(); }, '步骤A':function(){ console.log('执行 步骤A'); this.next(); //告诉流程执行下一步 }, '步骤B':function(){ console.log('执行 步骤B'); this.next(); }, '步骤C':function(){ console.log('执行 步骤C'); } }); </script> </html>
第一例子中就两个函数一个setNext,next;我们先来看setNext方法:
this.setNext = function(stepName, s, f){ success = s||success;//获取参数中成功回调函数 fail = f||fail;//获取参数中失败回调函数 nextStepName = stepName||nextStepName;//判断stepName是否为空,为空将赋值原来的 nextStep = new Step(nextStepName);//实例化对象 return nextStep; };
比较简单,就是实例化step对象。之后我们在看next方法代码:
this.next = function(stepName, s, f){ nextStepName = stepName||nextStepName; success = s||success; fail = f||fail; nextStep = stepName?new Step(stepName):nextStep; if(nextStepName){ nextStep.stepData = function(dataName){ return dataName?nextData[dataName]:nextData; }; nextStep.getPrev = function(){ return name; }; nextStep.fail = function(){ fail.apply(this, arguments); }; nextStep.success = function(){ success.apply(this, arguments); }; stepMapping[this.getCurr()] = true; if(allDone()){ trace.push(nextStepName); typeof nextStepName == 'string' && proxy(nextStepName); if(({}).toString.call(nextStepName) === '[object Array]'){ for(var i=0; i<nextStepName.length; i++){ proxy(nextStepName[i]); } } function proxy(stepName, fn){ if(typeof (fn = flow[stepName]) == 'function'){ (function(fn, context){ return function(){ debugger; return fn.apply(context, arguments); }; })(fn, extend({getCurr:function(){ return stepName; }}, nextStep))(); }else{ throw new Error("step not found: "+stepName); } } } return nextStep; } };
一开始懵逼看不懂很正常,我们在里面加断点跟着demo一步一步走对整个逻辑的理解会更清晰一点。
this.next = function(stepName, s, f){ debugger; nextStepName = stepName||nextStepName; success = s||success; fail = f||fail; nextStep = stepName?new Step(stepName):nextStep; if(nextStepName){ nextStep.stepData = function(dataName){
在里面加了debugger,然后我们一步一步的调试:
开始我觉得遗憾的是为什么nextStepName里的值是“步骤A”,整个方法是在这个地方调用的。
而nextStepName的变量是在setNext复制的
按道理应该里面的值是最后一次调用setNext的参数也就是“步骤C”,那为什么运行结果里面的值是步骤A呢,这里要看你的理解领悟能了,算法有了,结果有了,你能强行理解算法而经历往结果靠拢么?这个问题的重点是在setNext的返回值,setNext返回的是新的step实例对象。在结合
this.setNext('步骤A').setNext('步骤B').setNext('步骤C'); //预先设置好需要经过的每个步骤
用图来解析这个方法的输出和执行会更好理解:
this.next();
这段代码执行的this对象中的nextStepName私有变量的值为步骤A。
然后我们接着往下看next方法里的代码:
success = s||success;//赋值参数的成功回调函数 fail = f||fail;//赋值参数的失败回调函数 nextStep = stepName?new Step(stepName):nextStep;//如果第一参数有值的话返回这个值得step实例对象,没有的话返回对象的nextStep私有变量 if(nextStepName){ /*对象上添加一系列方法 暂时忽略*/ nextStep.stepData = function(dataName){ return dataName?nextData[dataName]:nextData; }; nextStep.getPrev = function(){ return name; }; nextStep.fail = function(){ fail.apply(this, arguments); }; nextStep.success = function(){ success.apply(this, arguments); }; /*对象上添加一系列方法 暂时忽略*/ stepMapping[this.getCurr()] = true;//标记上一步步骤被执行了
代码加了注释基本很好理解就不多说了。下一个重点是这句
if(allDone()){
下回分解。
源码可以在腾讯云技术社区上下载,我把源码都上加注释在放到Github上,下载暂时不放上去。