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

关于个人记账的,本来想写一个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了,基本实现了防抖的效果。

js创建对象的7种模式

其实我们在新建一个对象的时候,通常会用什么方法呢?我觉得,大多数的人都会这样新建,简单直接:

var myObject = {
  name:'test',
  content:'hello world',
  do:function(){console.log('nothing')}
}

在这里,以上面例子来详细说说js创建对象的7种模式:

第一种:工厂模式

function createObject(name, content){
  return {
    name,
    content,
    do:function(){console.log('nothing')}
  }
}

var myObject = createObject('test', 'hello world')

这种模式非常直观,丢进去,丢出来。

第二种:构造函数模式

function createObject(name, content){
  this.name = name
  this.content = content
  this.do = function(){console.log('nothing')}
}

var myObject = new createObject('test', 'hello world')

这种模式也很常用,定义了一个构造函数,然后对象都是new出来的。

第三种:原型模式

function createObject(){
}
createObject.prototype.name = 'test'
createObject.prototype.content = 'hello world'
createObject.do = function(){console.log('nothing')}
var myObject = new createObject()

这种模式相对构造函数模式,是在它的原型上直接创建属性,后面new出来的对象就会继承下来这些属性和方法。

第四种:构造函数模式和原型模型组合

function createObject(name, content){
  this.name = name
  this.content = content
}
createObject.prototype = {
  constructor: createObject,
  do:function(){console.log('nothing')}
}
var myObject = new createObject('test', 'hello world')

构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性,结果,每个实例都会有一个自己的一份实例属性和方法,但同时共享着对方法的引用,最大限度地节省了内存,是目前使用最广泛的一种创建自定义类型的方法。

第五种:动态原型模式

function createObject(name, content){
  this.name = name
  this.content = content
  if(typeof this.do != "function"){
    createObject.prototype.do = function(){console.log('nothing')}
  }
}

var myObject = new createObject('test', 'hello world')

这里就是判断是否存在do这个方法前提下动态在原型上添加,而且只在调用函数的时候才会执行,可以说是非常好的一种模式。

第六种:寄生构造函数模式

function SpectialArray(){
  var values = new Array()
  values.push().apply(values, arguments)
  values.toPipedString = function(){
    return this.join('|')
  }
  return values
}

var colors = new SpecialArray('red', 'blue', 'green')
alert(colors.toPipedString())  //"red|blue|green"

其实这种模式也很容易理解,我们不能直接修改Array构造函数,就借用来寄生一下,添加我们需要的属性。

第七种:稳妥构造函数模式

function createObject(name){
  var o = new Object()
  o.do = function(){console.log(name)}
  return o
}
var myObject = createObject('name')

看起来有点熟对吧,其实不完全熟,它其实是通过myObject的方法才能访问到数据成员的这样一种方式,没有其他方式能够访问,myObject就是保存着一个稳妥的对象。

好了,关于js创建对象的7种模式,大抵如上。

混杂模式与标准模式的一些区别

关于文档模式,已在上一篇文章(文档模式)中说过,这里就说说混杂模式和标准模式的一些区别。

众所周知的一个区别,就是CSS盒模型了,我们都知道标准模式中,CSS盒模型是w3c标准盒模型,也就是它的height和width就是里面内容(content)的高和宽。

那混杂模式呢?它会按照IE5.5的CSS盒模型,就是它的height和width是包含padding和border的宽度的,这个就是非常明显的区别,当然了,IE8及之前只支持IE盒模型,到IE8+之后,就可以通过设置box-sizing的值来切换,默认值是content-box,就是w3c标准盒模型,而设置为border-box,就是回到IE盒模型了。

基本来说,就如图:

一个div元素里面只包含图片的时候,混杂模式和标准模式也是不一样的,在标准模式下,不管IE还是标准浏览器,在图片底部都有3像素的空白。但在混杂模式下,标准浏览器(Chrome)中div距图片底部默认没有空白。

如果是在IE的混杂模式下,给行内元素是可以设置高度和宽度的,标准模式下肯定是无效的。

在混杂模式下,IE和其他浏览器对百分比宽度的解析是不一样的。如果父级是行内块或者浮动或者有定位的元素,给子元素设置百分比宽度100%时,IE的混杂会以父级的100%算(父级没有设置宽度,再往上一层),而标准浏览器是取决于内容的宽度,所以关键还是看父级是否行内块或者浮动或者有定位的元素。

在混杂模式下,当我们给一个元素设百分比高度,其他浏览器(正常,inline高度无变化,inline-block和block都会按百分比),而IE是自适应到内容高度。有点跟上面设置宽度相反的感觉。

overflow溢出默认值的问题。标准模式下,溢出元素是溢出可见的,超出部分的内容呈现在它的包含元素外。在混杂模式下,IE浏览器的溢出元素会自适应内容的尺寸。其实这个联想上面也很容易理解,标准模式是定好了的,而混杂模式是靠内容来撑开的感觉。

这里主要参考了这篇总结得不错的文章:标准模式与混杂模式