在项目中基于objext写了一个form-model,用以借鉴在提交表单时,对表单数据的管理。表单数据非常复杂,而且表单本身在前端领域也是非常复杂,因为表单不仅包含了数据,也包含了交互,还包含了数据与交互之间的逻辑联系(例如某个值为何时,其中某些选项不展示)。可以说,至今没有一个可以抽象到最完美状态的表单组件甚至是框架。更麻烦,我的项目中,一个表单是分步骤填写的,它在几个页面之间进行了跳转,这要求数据管理更强大,可以保持在不同界面之间的一致性。另外,表单还有一些变体,例如下拉列表变体:用一个modal打开来选。还有表单某个步骤可撤销的问题,在这个步骤填了一些值之后,想撤销,回到它们的初始值。最后,还要把表单数据在提交之前进行格式化,例如去除无用的字段,例如某些字段在前端是一个值,但传给后端的时候要转换为另外的值。而这些,我都通过对objext进行扩展得以实现,它不可能全,但在我的项目中非常实用。
判断一个字符串是否为数字形式字符串的正则表达式
首先要判断这个变量是否为字符串类型,typeof为string.接着通过正则表达式进行判断:
/^\-{0,1}[0-9]+(\.{0,1}[0-9]+){0,1}$/
这里比判断纯数字稍微复杂一点,要考虑小数点。
angular的作用域破坏
angular里面,有两个内置指令非常危险一个是ng-if一个是ng-repeat,这两个指令会让你在写模板的时候,输出结果不一定按照预期显示。angular里面的作用域遵循js的原型继承模型,子作用域继承父作用域,那么相当于子作用域的原型链是它父作用域的一个节点。而我们在创建一个directive的时候,scope参数有三种方式:
- true:共享父作用域的scope,在directive内对scope的任何修改都会同步给父作用域,也就是内外scope完全是同一个
- false:创建一个隔离作用域,和父作用域完全没有任何关系,directive内部的任何修改都不会影响父作用域的scope上的数据
- { ...props }:创建一个有条件的隔离作用域,虽然这个作用域基本面是和父作用域隔离的,但是对象内的参数可以实现绑定,仅这些props可以产生影响,其中又有3种形式,@和&仅接收外部作用域的变化,而=可以实现内部的改动同步到外部去。也就是说,只有=的那部分是共享的。
这些知识可以随便查资料了解。
但是,ng-if和ng-repeat会打破这种共享关系。如果你在一个directive中使用了另外一个directive,而想让它们拥有同样的数据绑定(大部分情况下我们都这样希望),那么一旦你在子diretive上使用了ng-if或ng-repeat,那么情况就发生了变化。这两个内置指令会创建一个隔离作用域,而非共享父作用域。
上面这张图示意了这种令人抓狂场景产生的原因。原本,父子指令共享了一个作用域,但是当你在子指令上使用了ng-if的时候,angular内部会创建一个隔离作用域,ng-if作为指令,仅仅接收父作用域传过来的一个值,但它不会回写数据,只是控制了渲染效果。而这个时候,my-child-directive被放在ng-if作用域的内部,这时,它共享的,是ng-if的作用域,而这个时候,你把child scope打印出来看,会发现,啥也没有,父作用域的东西完全没有传过来。但是,不要心急,由于angualr作用域的继承性,你仍然可以读到一些scope上的值,但是,你不能反写,一旦你开始反写,你发现,父作用域接收不到你的反写内容。反写的内容被直接写在了ng-if scope上,而如果你精通js的原型链模型,就知道,如果一个对象的原型链上有某属性,这个时候,你再对这个对象的同名属性赋值,那么值被覆盖了,它永远无法操作原型链的上游。不过,angular可以,因为它的scope提供了父级作用域的引用$parent,所以,当你在child scope上反写不成功时,往往在前面加$parent.就行了。而如果,你的child scope本身也就是一个隔离作用域,那反写是不可能了,但你还可以通过$parent.$parent.来操作父级作用域。
遇到ng-repeat无法获取到数据时,往往是因为ng-repeat比ng-if更狠,它创建了一个更封闭的隔离作用域,但你按照上述进行思考,也可以实现自己的目的。
React的懒加载方案
https://meddium.com/@pomber/lazy-loading-and-preloading-components-in-react-16-6-804de091c82d
React.lazy(() => import('./myComponent')) 这种方式虽然我感觉不算完美,但是也正是我想要的哦~
将objext的文档写成了英文,让它更国际化了。自从在morningstar工作后,给自己的库写英文文档简直就是标配,但是任何文档,写起来都花时间,虽然就那些东西,眼睛一看就知道,但是真写起来,也挺累的。用中文写的好处是,能把一些细节问题说清楚,所以,写英文文档的策略就是,不告诉老外那些细枝末节的东西,就只给你最简单的用法,你爱用不用吧。
As indexedDB can't be used normally in local which open local html file directly, we can't use as expected in a hybrid app……
https://stackoverflow.com/questions/15694334/indexeddb-over-local-html-file
rxjs中可以做到在流之间转换,其中一个就是switch,它的意思和英文意思一样,即从一个流切换到另外一个流。但是,单纯从一个流切换到另外一个流是没有意义的,需要将前一个流中的数据在切换时,要将它的数据传给下一个流中去使用。rxjs中的switchMap就是要做这样一件事,它接收一个函数,这个函数的参数是上一个流遗留下来的数据,而它需要返回一个新的流,这个流中你可以使用上一个流遗留下来的数据。
Rx.Observable.fromEvent(document, ‘mousedown’) .map(e => e.target) .switchMap(target => Rx.Observable.fromEvent(document, ‘mousemove’).map(e => { return { startX: target.offsetX, startY: target.offsetY, moveX: e.pageX, moveY: e.pageY, } })) .subscribe(pos => console.log(pos))
你可以看到,switchMap里面的函数接收mousedown这个流给的target,然后再返回一个新的关于mousemove的流,并且对这个新的流作map处理,过程中把前面的target也用上了。在switch发生的时候,mousedown的事件监听就给丢掉了。后面就全是跟mousemove相关的监听。
总结一下,rxjs里面的switch就是切换流的操作,切换的时候,前一个流会被丢掉。
连续同源异步操作队列
问题的发现
来自同源的多个异步操作可能引起异步冲突问题,特别是在网络请求时。同源操作产生了两个ajax请求,它们的请求结果将用于渲染同一个区域,然而由于网络问题,先发出的请求后返回,导致最终得到的界面是错误的。
解决这个问题的最好办法,是利用原生XHR的abort方法,在后一次操作时,将前一次操作引起的ajax请求给cancel掉。
但是在现实条件下,异步操作并非都有cancel操作。js原生的Promise没有,原生的fetch基于Promise也没有。基于Promise的很多工具都没有cancel操作。这种情况下怎么解决这个问题呢?
其实方法是有的,就是直接丢弃Promise的推送,不执行它的resolve回调即可。这样,虽然异步操作已经执行了,但不会对现有的环境造成任何副作用。(虽然这样看上去浪费了异步操作这个资源。)
问题的思考
如何来判断是否要丢掉它的回调?我们可以创建一个队列,每次产生一个异步操作时,就将它加入到队列中,当队列中存在操作对象时,每次只取最后一个,等待它推送结果,执行它的回调,排在它前面的操作全部丢弃掉。
基于这样的想法,我写了一个工具deferer-queue。它为异步操作创建队列,并根据不同的场景实现不同的队列操作形式。你可以通过这里阅读它的源码和文档。它提供了4种可供选择的场景:
并联
异步操作进入队列的时候立即开始执行,它们的回调将按照它们进入队列的顺序执行。
这个图中有这些元素:
- 时间轴
- 圆圈:一个异步操作被push进队列
- 向下箭头:队列中开始执行某个异步操作
- 虚线:执行顺序(执行流)
- 方框:每个异步操作的回调
- 竖线:队列周期(从开始到停止的过程)分割线
上图表现的是,1-4这4个异步操作在不同的事件点被加入到队列中。它们在一个周期内,也就是从1开始执行,到队列中所有的异步操作执行完毕,进入停滞状态。1-4这些操作,一进入队列就开始执行。它们执行完之后,就会告知队列执行完毕状态,回调就会执行。但是由于回调一定是按照异步操作进入队列的顺序执行的,因此,即使4已经完成了,但它的回调也必须等到3结束后才会执行。
在deferer-queue中,所有的回调一定遵循先进队列先执行,后进队列后执行的顺序规则。
串联
异步操作进入队列后并不立即执行,它从队列中取出第一个,执行,等待这个异步操作完成(成功或失败都算完成)并执行它的回调,然后再取出下一个,继续执行。
上图表现的是,1-4这4个异步操作,进入队列后,按照顺序依次执行。但是,在一个周期内,只有1是立即执行的,其他操作必须等到上一个操作结束(promise resolved或rejected)之后,才会开始执行。
尾调
异步操作进入队列后并不立即执行,它从队列中取出最后一个,执行,并把前面所有的操作丢弃,这个操作执行完后直接进入回调,队列结束。
这张图表现的是,1-4这个4个异步操作被按顺序加入到队列中,但是,在一个周期内,只有4(最后一个)被执行。1-3全部被丢掉了。你可能会问,假如4进入队列的时候,1已经在执行了怎么办?答案是,1会被丢掉,只要在一个周期内,只会有一个执行的回调会被执行。也就是说,假如4开始执行之后,突然5插入进来了,那么4会被直接丢掉,这一个周期以最后一个回调函数被执行而结束。
连续
异步操作进入队列后并不立即执行,它从队列中取出第一个,执行,等待这个异步操作完成(成功或失败都算完成)并执行它的回调,之后取出最新的一个执行,执行完之后再看队列中是否有新的,如果有新的,再取出最新的一个,继续执行,如此往复下去。
这幅图表面的是一种连续不断的执行队列的模式,当1-4被push进队列之后,1被执行,2-4被丢掉。当1完成之后,检查队列是否加入了新的异步操作,如果检查到5-6,那么取6进行执行,把5丢掉。进入后续过程后,感觉上和尾调非常像,但是,尾调是在一个周期内,只会执行最后一个回调,而连续不是,它会不断的检查队列是否已经被执行完,如果没有,那么就会执行一个类似尾调的过程,这个过程结束,又会再做一次。队列开始的时候,取第一个异步操作执行,当第一个操作结束后,后面之后去取最后一个执行。这些执行之间是串联关系,只有正在执行的异步操作已经完成的情况下,新的操作才会被执行。这是一种比较奇葩的场景,但是它可以解决一些需要连续不断做某些事情的应用。
注意:当一个周期结束后,队列处于停滞状态,但是,一旦有新的defer被push进队列,它又会开始一个新的周期。
问题的解决
基于上述的思考,我最终发布了deferer-queue,你可以通过npm安装这个包,在自己的项目中使用它。它的操作模式超级简单,首先实例化一个queue对象,然后往这个queue push异步操作,异步操作被装在一个函数中被push进队列,它的回调函数一定是按照push的顺序执行。
import DefererQueue from 'deferer-queue' const queue = new DefererQueue() const defer1 = () => new Promise((resolve, reject) => { ... }) const defer2 = () => axios.get(...) const defer3 = async () => ... queue.push(defer1).then(() => { console.log(1) }) queue.push(defer2).then(() => { console.log(2) }) queue.push(defer3).then(() => { console.log(3) })
无论defer1-3中的谁,执行后谁先返回结果,控制台输出的结果永远是1 2 3(defer都成功的前提下)。
要使用上述的四种模式,只需要在实例化的时候,传入一个options对象,将mode设置为parallel/serial/switch/shift中的一个即可:
const queue = new DefererQueue({ mode: 'switch', })
其他的用法一样。这样,你的队列就会按照“尾调”的模式运行。
关于具体的API使用细则,你可以阅读它的文档。其中,利用axios的cancel能力那个地方非常有借鉴意义。