刚刚在腾讯云技术社区前端专栏中看到一篇腾讯高级前端工程师写的《一个只有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上,下载暂时不放上去。

flowJS源码个人分析的更多相关文章

  1. MapReduce的ReduceTask任务的运行源码级分析

    MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...

  2. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  3. MapReduce的MapTask任务的运行源码级分析

    TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...

  4. TaskTracker任务初始化及启动task源码级分析

    在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...

  5. MongoDB源码分析——mongod程序源码入口分析

    Edit 说明:第一次写笔记,之前都是看别人写的,觉得很简单,开始写了之后才发现真的很难,不知道该怎么分析,这篇文章也参考了很多前辈对MongoDB源码的分析,也有一些自己的理解,后续将会继续分析其他 ...

  6. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  7. FFmpeg源码简单分析:libswscale的sws_scale()

    ===================================================== FFmpeg的库函数源码分析文章列表: [架构图] FFmpeg源码结构图 - 解码 FFm ...

  8. LinkedHashMap 源码详细分析(JDK1.8)

    1. 概述 LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题.除此之外,Linke ...

  9. [Java] LinkedHashMap 源码简要分析

    特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...

随机推荐

  1. 每天一个Linux命令(09)--touch命令

    linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件. 1.命令格式: touch  [选项]··· 文件··· 2.命令参数: -a 或 ...

  2. 移动H5开发入门知识,CSS的单位汇总与用法

    说到css的单位,大家应该首先想到的是px,也就是像素,我们在网页布局中一般都是用px,但是近年来自适应网页布局越来越多,em和百分比也经常用到了.然后随着手机的流行,web app和hybrid a ...

  3. 辗转相除法求H.C.F小结

    辗转相除法 大纲: 问题 原理 反思 1.     问题 一个试题,请完成以下填空 下列程序是利用辗转相除法求H.C.F(最大公约数) include <stdio.h> int main ...

  4. 转换器3:手写PHP转Python编译器,词法部分

    上周写了<ThinkPhp模板转Flask.Django模板> 一时技痒,自然而然地想搞个大家伙,把整个PHP程序转成Python.不比模板,可以用正则匹配偷懒,这次非写一个Php编译器不 ...

  5. 1620: [Usaco2008 Nov]Time Management 时间管理

    1620: [Usaco2008 Nov]Time Management 时间管理 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 506  Solved: ...

  6. ST HW1 An Error

    曾经使用ThinkPHP完成一个网上电子银行的项目,凡是涉及到金钱的,需要注意的问题很多,比如使用float类型,比如金钱的数额不能为负数等等.最初没有考虑到剩余金额不能为负数或者消费的金额不能为负, ...

  7. OnsenUI和AngularJS配合搭建混合应用的基本步骤

    混合开发的热潮已经掀起,实现混合开发的方式很多.今天给大家介绍一个实现混合开发的基本方法-OnsenUI和AngularJS配合. OnsenUI是一个可以实现混合开发的前端框架,包含了很多前端设计中 ...

  8. SignalR指定用户推送消息

    一.首先,在MVC项目中安装SingalR包(SingalR2.0需要.net4.5以上,VS2010可以安装1.1.3版本,本例为VS2010+SignalR1.1.3). 打开工具-NuGet程序 ...

  9. JavaScript重新介绍

    本文转载自 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript 引言 为什么 ...

  10. [复习]java中hashCode的作用

    1.HashCode的官方文档定义 (1)hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如java.util.HashTable提供的哈希表. (2)hashCode的 ...