一个简单的函数消灭业务代码的低级错误

日常的前端开发业务代码中,我们经常都需要调试数据,所以要经常更改某些参数的初始化数据,或者更改过程中的数据。

但是很多时候,改了数据调试完之后就忘记改回去了,某个调试的场景是依赖一个参数的修改还好,但是需要依赖几个参数的修改,就很容易漏改回去了。

举两个例子:

一、我们有一个按钮来触发弹窗的打开,而控制弹窗显隐为变量showDialog,初始化值为false,但是产品说弹窗里面的样式有点问题,所以我们设置了showDialog的值为true然后修改内容(因为不可能每次都点击按钮来打开看弹窗效果),最后修改完再将showDialog设置回false。

//伪代码
var showDialog = false;  //控制窗口显隐,调试需要依赖其变量值
btn.onclick = ()=>{
  showDialog = true; 
}

二、我们有个抽奖的活动,逻辑是请求接口之后拿到抽奖的prize_id之后,对比prize_id的内容,然后决定在视图中显示出来,但是我们需要调试某个抽奖结果的内容,当然不会叫接口改返回的prize_id了,所以我们可能会改传入显示模块的值。

//伪代码
fetch().then(prize_id => {
  showResult(prize_id)  //显示抽奖结果,调试需要依赖其传入值
})

function showResult(prize_id){
  //显示抽奖结果的代码
}

问题就在于,很多时候我们最后忘了改回去,就会出现弹窗直接打开了、每次抽奖都抽中某个奖品的结果了,这种低级错误是不应该犯的,但是我也见过某些app真的直接这样上了测试的代码。

所以在这些业务代码中,我相信也没什么人会做构建前的校验脚本或者单元测试的,所以我们需要一个简单的函数来控制变量的赋值,来避免这种低级错误。

特意写了一个简单的包:https://github.com/ershing/dev-debugger

用法:

//引入包dev-debugger
import DevDebugger from 'dev-debugger'
//初始化dgb实例来控制变量的测试值
let dbg = new DevDebugger({ debug: true })
//绑定获取替换的方法,也可以直接调用dbg.debugVal
let _r = dbg.debugVal.bind(dbg)

实例有两个方法:debugVal和debugCaseTag

/*
  debugVal(pro, dev)
  @params 传入第一参数为生产值,第二参数为调试值
*/
//也可以绑定方便后面调用
let _r = dbg.debugVal.bind(dbg)


/*
  debugCaseTag(pro, tag)
  @params 传入第一参数为生产值,第二参数为自命名的唯一标签名称
*/
//前提需要配合初始化的传参
let dbg = new DevDebugger({ 
  debug: true,
  caseName: 'testPrize1',  //调试的用例
  cases: {  //用例参数集
    'testPrize1': {
      'myPrize': 3   //标签名称对应的调试值
    },
    'testPrize2': {
       'myPrize': 6   //标签名称对应的调试值
    }
  }
})

//也可以绑定方便后面调用 
let _rt = dbg.debugCaseTag.bind(dbg)

所以上面的例子可以这样写:

一、控制showDialog的变量值

//伪代码
var showDialog = _r(false, true);  //debug时值为true
btn.onclick = ()=>{
  showDialog = true; 
}

二、控制传入显隐函数的值

//伪代码
fetch().then(prize_id => {
  showResult(_r(prize_id, 3))  //debug时为3
})

function showResult(prize_id){
  //显示抽奖结果的代码
}

当然上面也可以用debugCaseTag方法来将调试的值放在初始化的函数当中。

然而,在我们构建代码的时候,当然不想有任何调试的代码和调试的值的,所以我又写了一个babel插件:https://github.com/ershing/babel-plugin-dev-debugger

用法:

//修改babel.config.js文件
module.exports = {
  "plugins": 
  process.env.NODE_ENV === "production" 
  ? ["babel-plugin-dev-debugger"] 
  : []
}

注意:

使用这个babel插件的话,需要在各自文件中import包dev-debugger(也利于单文件组件的独立调试),而且不要将实例方法赋值出去,可以直接dbg.debugVal或dbg.debugCaseTag使用,也可以bind之后_t或_rt使用,但不要再赋值给其他变量。

忙自己喜欢的事情

很多人会有时候觉得焦虑,不知道做什么事情又过了一天,又或者根本不知道自己想做什么就一天一天地过去了。

其实根本问题不是没有找到自己理想或者自己爱好,而是没有细化到一些计划上去,如果静下心来,想想,接下来的时间(一个月、一个季度、半年、一年),自己想成为一个怎样的人,或者争取去到怎样的一个高度,又或者掌握什么技能,然后再细化一下自己需要做的事情,从大到小,自自然然,就会给自己的时间安排上需要完成的事了。

当然,安排了就要严格去执行,就像完成一个产品,或者当做是自己工作的一部分,严格按照时间规定去落实执行。

然后,每天就会过得很充实。

当你成果一天天地积累的时候,回头你会看到自己翻越了一座又一座以前想都不敢想的大山,便会觉得,生活是如此有趣,人生是如此有趣。

写了一个个人资产管理的后台系统

关于个人记账的,本来想写一个app来着,后来觉得记账这种事应该是自己定时回归的,不应该是app那种经常可以打开的方式,过于便捷反而过于随便,所以需要一种有仪式感的事情,就好像以前会打开记账本,一条一条记账那种感觉。

为了记账,最后统一为一个个人资产管理的后台,也挺直观,可以看到各项资产组成和趋势,最后就快速地写了一个项目,没有考虑代码的太多东西,只考虑功能的快速实现,而且只能记当前年的账,还特意参考了spendee这个app的可以计划记账的功能,特别喜欢。

项目地址:https://github.com/ershing/asset-admin

附上部分图:

准备写一个软件

最近在想着财务管理类的事情,想着做一个记账本类的小app,看了一下比较多人用的几个,发觉都没太大的特色,而且杂乱的东西太多,广告也不少,重点还是那种让你投钱购买它什么理财产品的模块。

发觉这个社会其实比较浮躁,造出来的产品也是比较浮躁,很多产品人说,做的产品要将客户放第一位的,将客户体验放第一位,其实很多时候,却是将能引导用户花钱放在第一位,想法设法引流、导向,让客户心甘情愿掏钱,导致好的产品不多。当然了,这个社会讲求生存,好的产品吸引用户最后也是要想办法赚钱啊,这也是产品的使命,也是无可厚非的。打造一个好的产品需要时间和金钱,所以很多产品根本来不及真正做一个好产品就要被淘汰了。

看不到好的记账类app,然后就自己构思设计,想法还挺好的,然后自己找了个本子画原型,然后用软件开始设计,做着做着,发觉和自己想象的又出入有点大,可能设计和审美的能力还不足,不过准备以后花时间学一下,设计、画画什么的。

最后不得而还是继续找,还是找到了一款符合心水的一个app,spendee,功能比较集中,分类比较明确,页面简洁,确实是在做一个记账的事情,分享个截图:

最后决定了,基本按照这个app来做一个,然后按照想法修改其中的东西,因为刚开始还是觉得不同模块之间耦合还是比较多,有些东西又重复了,不过总体还是非常不错的app,好了,有空就开始做。

step by step


一段时间忙项目没有更新博客了,没有坚持一直写技术的博客,每次回头,发觉最想写的都是感受的内容。

发觉也是,写了技术博客当时是加深了了解了,但是过了一段时间,技术迭代了,或者记忆差了,之前辛苦写的内容就好像没什么用了,但是起码留了一个痕迹。

有时候想的是,生活其实还是剩下不少时间的,其实想做成一件事,就看你怎么把自己的时间过程是公司的时间了。

怎么说?

工作中面对某个需求,你可能觉得不难,花点时间就能搞定了,然后你就能在被安排好的时间内完成你的需求,但是生活中呢,假如你想到做同样的一个需求,但是你不会将自己的这样一个想法的需求落实起来,或者说落实起来,要花远比公司那里的需求多很多倍的时间和代价,因为你没有认真将它执行起来。

所以如果有想法,将它作为工作那样去执行,去安排时间,去规划,我相信,在自己的空余时间内,还是能做成很多事的。

有时候人会觉得,这样不会很累吗?

其实如果都是你喜欢的,都会乐此不彼了,怎么会累呢?

用代码的思想看待生活

一段时间没写博客,今天看了还有一遍之前的草稿还没放出来,然后就放出来了。

睡了个中午觉,起来没睡醒的状态都特别感性,突然认真审视了自己的生活方式,发觉自己还没好好掌握到生活的方式,很多东西做的不好。

我觉得,我需要用代码的思想看待生活,不要让重复的东西还花时间去重复,不要让麻烦的东西一直纠缠解不开,不要让记得的东西在关键时刻就忘记。

准备做一个详细的规划脑图,做出来各种辅助的工具,让生活中的每一样事情都有条不紊,重复的事情丢进去机器来运转,复杂的事情抽象出每一块的小工具,按照逻辑流程流转,最后出来最终的答案。生活很精彩,生命很短暂,如果做到每一个自己做的事情都是新鲜的,这是一个巨大的挑战,但我觉得这却是需要努力去做到的,才不枉这么短的一生!

写一个throttle函数

上一篇写了(写一个debounce函数),这篇就写一下throttle函数。

throttle是怎样的一个函数呢?和debounce函数又有什么区别呢?

throttle就是节流阀,控制在一定时间范围内只执行一次函数,其实根据节流阀的名字也可以很容易理解到,水不断来的时候,通过你节流阀,就可以控制你自己的实际流量,单位时间内怎样流出。

举个实际例子,例如我们需要监听窗口的大小变化来改变另一个东西的样式,那么我们肯定监听resize来触发我们的函数,但是实际上,resize触发函数会在一秒内触发很多次,难道一秒内我们就要执行函数这么多次了吗?显示,这不是我们想要的,可能我们想要的只是300ms执行一次就够了。

好了,这就不难理解这个函数做了什么工作了。

废话少说,直接开写,首先肯定需要传入执行的函数,还有我们知道需要控制某个时间内只执行一次,那会有一个interval参数,那先写个开头:

function throttle(fn, interval) {
  let _fn = fn  //保存函数的引用
  return function(){
    let _self = this
    let args = arguments
    setTimeout(function(){
      _fn.apply(_self, args)
    }, interval)
  }
}

这个开头和debounce开头一样,关键就是后面的逻辑了,也是和debounce的区别,我们想要这个函数在某个时间段只执行一次,如果在这个时间段内继续触发函数,则不执行,那么就可以增加代码为:

function throttle(fn, interval) {
  let _fn = fn  //保存函数的引用
  let timer
  return function(){
    //函数还没执行,则直接返回
    if(timer){
      return
    }
    let _self = this
    let args = arguments
    timer = setTimeout(function(){
      _fn.apply(_self, args)
      clearTimeout(timer)
      timer = null  //注意执行完要清空timer
    }, interval)
  }
}

好了,我们发觉,那如果第一次函数被触发呢?不应该直接返回吧,第一次那肯定立即要执行,不用延时吧,ok,再加个标识符:

function throttle(fn, interval) {
  let _fn = fn  //保存函数的引用
  let timer,
      firstTime = true
  return function(){
    let _self = this
    let args = arguments
    //判断第一次进来就立即执行
    if(firstTime){
      _fn.apply(_self, args)
      firstTime = false
      return
    }
    //函数还没执行,则直接返回
    if(timer){
      return
    }
    timer = setTimeout(function(){
      _fn.apply(_self, args)
      clearTimeout(timer)
      timer = null  //注意执行完要清空timer
    }, interval || 500)
  }
}

其实具体的debounce和throttle函数实现起来有很多种写法,但是都大同小异,因为基本原理都是一样的,做了各自不同扩充而已。其实理解debounce和throttle的区别很容易,debounce就相当于以触发动作为参考,延时了设定的时间就执行一次函数,如果动作在设定的时间内仍然继续触发,那么继续以新触发的动作为参考;而throttle就是第一次触发就立即执行了,然后以执行的时间为参考,延时了设定的时间之后再触发就再执行,但是如果下一次触发仍在前一次执行的设定延时时间内,则不执行了,大概,它们就是如此。

写一个debounce函数

这里尝试写一个debounce函数的过程。

debounce是什么?就是防抖动,假设我们监听了输入框的change事件,绑定了一个函数来验证,那么我们在输入的过程中就会不断触发这个函数,很多时候我们其实不想这个函数触发得这么频繁,因为用户都是不断输入的,没必要不断验证,我们真实希望的是,用户停止的时候才触发这个函数,所以我们希望给一个delay的时间,在这个时间内,不断触发的函数,也只按最后一次来触发。

就好像上图那样,只要你触发后面,如果不超过delay时间再次触发的话,也会继续等,直至最后一次触发超过delay的时间,函数才执行一次,很好理解。

当然了,如果希望的是立即执行函数的话,情况就会是下面这种情况:

只要你触发的时间比上次函数执行时间超过了delay的时间,就会被立即执行。

这里写的debounce函数就不考虑立即执行这种情况,按前面的情况来写一下。

首先,这个函数名称就叫做debounce,这个函数接受两个参数,一个是防抖执行的函数,一个是延时delay的时间,所以我们会用到setTimeout这个方法,而且我们执行了debounce之后需要返回一个被触发的函数,考虑到我们需要,会先有:

function debounce(fn, delay) {
  let _fn = fn  //保存函数的引用
  return function(){
    let _self = this
    let args = arguments
    setTimeout(function(){
      _fn.apply(_self, args)
    }, delay)
  }
}

这样执行下来,也只不过每个函数都执行一次延时而已,还没达到我们想要的效果,所以我们还需要做的是,这个函数在delay时间内,也就是还没执行前,再次触发的话,就将上一次设定的延时器清除,那这就容易增加代码为:

function debounce(fn, delay) {
  let _fn = fn  //保存函数的引用  
  let timer
  return function(){
    clearTimeout(timer)
    let _self = this
    let args = arguments
    timer = setTimeout(function(){
      _fn.apply(_self, args)
    }, delay)
  }
}

这就基本实现了debounce函数了,其实就是利用了一个闭包的形式,将原本的timer保存起来,只要函数还没执行(也就是delay时间内)再次触发的话,就会先清除了前一个的timer了,基本实现了防抖的效果。