282018.11

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

16:37:36 已有0条回复
242018.11

photopea:在线版photoshop

232018.11

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就是切换流的操作,切换的时候,前一个流会被丢掉。

23:40:49 已有0条回复
222018.11

日志 连续同源异步操作队列

问题的发现

来自同源的多个异步操作可能引起异步冲突问题,特别是在网络请求时。同源操作产生了两个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能力那个地方非常有借鉴意义。

23:56:06 已有0条回复

rxjs的Subject具有多播的特性。对于单播和多播怎么理解呢?

首先要了解一个概念“冷流和热流”。所谓“冷流”是指数据的变化是固定死的,举个例子:

let observable = Rx.Observable.create(function subscribe(obsever) {
observer.next(1)
observer.next(2)
})
observable.subscribe(v => console.log(v))

上面这段代码,subscribe的输出结果是可预知的,一定是先输出1,然后输出2.

“热流”是什么呢?就是它的数据变化是不可知的,随机的,随意的。例如通过fromEvent产生的observable。

现在回来说单播和多播。

单播是说,一个observable只能被一个观察者消费。还是用代码说话:

let observable = Rx.Observable.create(function subscribe(obsever) {
  observer.next(1)
  observer.next(2)
})
observable.subscribe(v => console.log(v))
observable.subscribe(v => console.log(v))

上面这段代码会输出两次1,2。简单的说,当调用observable.subscribe时,create的传入参数function subscribe会被调用一次。也就是说,一个subscribe只能被一个observer消费。这就是单播。这些observer之间相互不影响(不要有全局变量),它们虽然订阅同一个obserable,但是,它们仅仅是利用了observable的处理数据的能力,至于数据源、处理结果,都是独立的。

那什么是多播呢?就是一个有一个东西,可以被多个观察者同时订阅。这个时候要引进rxjs里面的Subject,它所创造的实例,能被多个观察者消费。代码说话:

let observable = Rx.Observable.create(function subscribe(obsever) {
  observer.next(1)
  observer.next(2)
})
let subject = new Rx.Subject() subject.subscribe(v => console.log(v)) subject.subscribe(v => console.log(v))
observable.subscribe(subject)

这个代码和前面不一样的地方在于,observable被subject订阅,当subject被两个不同的observer订阅时,subject执行一次function subscribe,但是同时通知这两个observer。所以,当你得到的结果是:1,1;2,2时,应该一点都不觉得奇怪。在第一次执行next(1)的时候,两个observer同时被执行。

18:13:17 已有0条回复

rxjs在编程实践中主要考虑三个部分:数据怎么来?数据怎么流动?怎么使用数据?

数据怎么来?rxjs有一大堆创建observable的东西,而这些东西,虽然创建的三observable,但实际上,它们就控制了数据是怎么来的。比如formEvent,表示数据是从事件中来的。比如bindCallback表示数据是从函数的运行结果中来的。总之,虽然rxjs不是我们传统的函数,没有主动调用函数时传入参数的概念,但是,它内部已经囊括了数据怎么来的各种方式,利用它的API就可以在特定的场景拿到你的数据。

数据怎么流动?完全靠rxjs的各种操作符,例如map、switch。除此之外,observable还能被组合。总之,observable虽然看上去是个形容词,但实际在rxjs代表那个用来装数据流的东西,是一个名词。而observable的形状(用了哪些操作符),决定了你或许数据的形式(时间上)和形态(空间上)。

数据怎么使用?实际上subscribe里面就决定了当数据流经observable之后,你怎么使用这个最后的数据。而且,rxjs可多值、多播。

 

11:22:30 已有0条回复
212018.11

RxJS中的概念梳理:

我们可以把一切输入都当做数据流来处理,比如说:

  • 用户操作
  • 网络响应
  • 定时器
  • Worker

RxJS提供了各种API来创建数据流:

  • 单值:of, empty, never
  • 多值:from
  • 定时:interval, timer
  • 从事件创建:fromEvent
  • 从Promise创建:fromPromise
  • 自定义创建:create

创建出来的数据流是一种可观察的序列,可以被订阅,也可以被用来做一些转换操作,比如:

  • 改变数据形态:map, mapTo, pluck
  • 过滤一些值:filter, skip, first, last, take
  • 时间轴上的操作:delay, timeout, throttle, debounce, audit, bufferTime
  • 累加:reduce, scan
  • 异常处理:throw, catch, retry, finally
  • 条件执行:takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
  • 转接:switch

也可以对若干个数据流进行组合:

  • concat,保持原来的序列顺序连接两个数据流
  • merge,合并序列
  • race,预设条件为其中一个数据流完成
  • forkJoin,预设条件为所有数据流都完成
  • zip,取各来源数据流最后一个值合并为对象
  • combineLatest,取各来源数据流最后一个值合并为数组

数据的管道是什么形状的?在RxJS中,存在这么几种东西:

  • Observable 可观察序列,只出不进
  • Observer 观察者,只进不出
  • Subject 可出可进的可观察序列,可作为观察者
    • ReplaySubject 带回放
  • Subscription 订阅关系

前三种东西,根据它们数据进出的可能性,可以通俗地理解他们的连接方式,这也就是所谓管道的“形状”,一端密闭一端开头,还是两端开口,都可以用来辅助记忆。

09:53:22 已有0条回复
172018.11

琢磨近10年前端领域出现的框架,它们几乎在干同一件事,那就是,怎么做到使“界面”“事件”“数据”三者可以更分离,但又合理串联在一起。界面用于呈现,事件用于交互,数据用于内容,这三者构成了前端开发的最底层核心要素。但是,就目前市面上的框架而言,仍然没有做到完美。举个例子,react算是最热门的框架之一,然而,它自己内部去实现了一整套事件系统,这样的坏处是,它不能和DOM原生事件同时使用,否则会出问题。而DOM原生事件和界面又完全绑定在一起,这就使得类似jquery这类框架无法实现数据响应。而virtual dom的出现,解决了数据和界面之间的响应关系,但是仍然无法解决数据和事件的双向响应(vue里面有较弱的实现)。因此,我觉得,更舒服的编程方式是,要进一步在现在已有的成果基础上,继续提炼事件这个维度,要让交互抽象后独立于界面和数据。当一个交互发生时,它不需要在事件系统中去写有关修改数据的操作(例如react里面要用setState),也不需要在事件系统中去写界面改动的代码(backbone就需要在事件发生后去操作DOM来实现界面变化,jquery也是)。未来新的框架应该是,抽象出“界面”“数据”“事件”,将它们单独分开放置,再由框架内部将它们串联起来。开发者应该关心使用什么模板去呈现界面,使用什么数据去渲染内容,使用什么事件去响应交互。而不需要关系,当用户进行了某个操作(交互)应该触发什么事件,然后去修改哪个数据,实现界面的变动。

这样的编程方式,和MVVM完全是两种思路,希望真的有机会看到这样的库可以出现。

17:11:45 已有0条回复
122018.11

日志 挣扎在逻辑抽象和业务环境的表单设计

这两天一直在写一个基于业务逻辑的表单抽象架构。听上去多么简单的一件事,不就是个表单么。然而,拥有这样想法的我果然还是太年轻。表单,可以说是web开发中最复杂的交互领域之一。单纯把表单理解为用户可以填写和提交的交互元素,那就太无知了,殊不知,一个表单除了填写和提交两个动作之外,还有一大堆可能需要的动作,例如数据验证、联动、按条件填写项、复原和暂存、创建和编辑表单公用等等。这些东西还完全没有考虑具体业务中的特殊逻辑,单纯从抽象层面去归纳而已。

而且,在现代SPA应用开发中,表单跟jquery时代也有巨大区别。SPA是数据驱动型开发模式,即界面如何展示,完全由数据(状态)驱动。这就会遇到很多问题,数据驱动的开发模式,不可避免的需要模板,模板中使用变量,并映射到实际提供的数据中。在模板中使用变量并不能方便的对应的数据层去,比如你在模板使用一个for in的循环,那么就有item和list。list好理解,但是,如果将item又传回给数据层呢?

除了数据层和模板映射的问题,表单的数据更改其实也是麻烦事。它依赖于框架内的事件系统,也就是说,现代前端框架,无一例外的要求你通过绑定数据,依靠自己内部的事件系统,当用户输入数据时,将数据的变化绑定到数据层去。这也就产生了一系列问题,几乎没有一个表单架构能够具备通用性。开发者无法在数据结构的便捷性、数据响应的便捷性、数据校验的便捷性之间得到平衡,它们一定是互斥的。我这两个星期都栽在这个问题上,无法自拔。

而且,当一些业务的特殊逻辑加入的时候,这个事情就更复杂。例如,表单中的某个(些)项目,仅在某个选项为true的时候才展示出来。这个听上去简单,但是,你要知道,当用户在true和false之前切换的时候,那些被显示/隐藏的被填写的数据要不要保留?另一个复杂的问题,就是数据源。一个下拉列表,它的选项列表可能是根据当前选中的某个项来决定,当未选中时,或重新选择时,要进行切换。这个问题听起来尚好解决,然而,当要求,其中某个选项为某值时,要求表单中其他某个项必须与它互斥……这样的特殊逻辑,不是不可能存在,我就遇到了,并且敲破了自己的脑袋。

真希望有人能够将表单这回事进行深度抽象,大老,靠你了……

23:56:24 已有0条回复
092018.11

日志 听听音箱究竟还有哪里不对?

腾讯的生日是11.11,按理双十一应该被腾讯注册商标才对,可惜……腾讯的性格温和,能够做成事真的很幸运。在中国,要做一家有温度的公司,要做一家按照创业时的初衷行事的公司,要做一家以普通人为用户群体,用产品去服务他们打动他们的公司,真的是难到登天,唯独,腾讯这样做了20年,或许,今年之后,这样的腾讯也要转型了,要像产业互联网转变,不是因为不想继续做以用户为目标的产品,而是它不得不在市场竞争中活下去而面临转型。我08年高中毕业才有机会开始用QQ,10年,从用户变成员工,从喜欢它的产品,到去实现产品。

早上就拿到了公司发的周年礼物,由一个精美的大箱子装起来,包装精美,而且设计感十足,和它的产品一样,力求让人觉得有温度。如此隆重的包装,我觉得也有点浪费材料,不环保。

礼物中有腾讯自主研发的智能音箱“听听”,晚上回家体验了一下。

腾讯听听音箱

首先是颜值,不能说是最顶级的,毕竟只600上下的价格。但是整体而言,在同价位智能音箱中,算得上中上乘,我鹅的设计在国内还是顶级的。白色的上身,塑料感足,灰色网布的下身,音箱感十足。开关、接口、指示灯都遵循完美的对称原则,为了使这种对称更不拘一格,顶面采用斜面设计,增加了亲切感。

其次是音质,也算不上顶级,可能和音乐版本也有关系,非常浓的低音,放在桌面低音炮效果明显。也是因为这种明显的低音,不适合听现场音乐,音乐的细部会被和音盖下去。它不是专业的音响,更适合休闲,生活气息。

它的重量有点重,不适合便携。另外,AI功能需要联网支持,因此也只能在wifi环境下工作。

作为鹅厂人自己推出的AI音箱,它的AI语音交互是一个亮点。开机之后,只要你说出“9420”就能唤起AI小姐姐,然后你就可以告诉她你要干的事情,查询天气、听音乐、听小说等等。如果她不能马上理解你说的话,还会重新问你。据说,之所以用“9420”作为唤醒口令,除了它是“就是爱你”的谐音,还因为这个句法语音识别起来更容易,容错率更高。但是,代价就是比较难记,因为它有四个音节。虽然说错了也没关系,但是总有一种尴尬症犯的感觉。

在自然语言交流这个环节上,就目前整个行业的水平来看,听听算得上顶尖,响应速度快,识别准确,交流自然,非常流畅。当然,在情绪识别这个点上,还有待未来整个行业的提升。比如我很不耐烦的说下一首,它可能还无法意识到,我不喜欢当前这种音乐的类型。切换音乐类型,我能很明显感觉是后台的一个强制算法,比如连续切了两首之后,它就给我换了一个音乐类型。把玩之后,发现其实它也不过如此,主要还是依赖后台的AI算法。

当然,智能音箱市面上也很多,算不上独特的地方。而且,甚至在性价比上,还不如其他音箱,毕竟600RMB的价格不算是亲民,作为一款商业产品,注定会面临比较大的风险,甚至有可能卖不动都有可能。

但是,我认为听听是腾讯应该继续做的项目,主要从几个方面考虑:

AI落地产品。就目前整个市场而言,虽然AI话题火的足以燃烧市面上的所有现金,但是真正要把AI落地很难,因为一套算法只适用一个场景,而即使一个场景,算法也需要非常长的时间去学习和修正,特别是诸如自动驾驶这样庞大的AI系统,就连走在最前面的百度,也无法拍着胸脯说自己在自动驾驶上已经完全成熟,可以量产。而智能音箱则是一个非常好的切入口,它立足于非常小的场景,而且不要求非常准确,这就有了空间去逐渐打磨和完善。如果不落地一款AI产品,那么很难给市场信息,AI研究在公司内部也会被很多人质疑。所以,我觉得把这款产品做好,落地,打磨,非常重要,就像其他产品一样,前期不一定是市场上最有前景的,但公司有资源和钱推进它,让它在时间中逐渐淘汰那些资本推进的产品。

算法的完善。在AI领域,算法为王。就以同声传译为例子,这么简单的一个场景,AI仍然无法做到精确。要让AI越来越聪明,必须经过非常长周期的学习和调整,算法的优化。而通过智能音箱这样一个小场景,就可以达到这样一个目的。而且,一个用户,一旦拥有一个智能音箱,基本上不会再同时使用另外一个智能音箱,而且一个音箱的最低寿命也在半年以上,因此,对于长时间的针对单用户的学习和算法打磨,有非常好的作用。

平台。腾讯是一个做软件起家,以整合用户资源、内容资源为核心竞争力的公司。对于其他应用商而言,他们很难像腾讯一样,打通用户、音乐、视频、文学,而对于腾讯而言,文娱已经是整个公司的支柱之一,(我认为今天的腾讯,分为游戏、内容、投资三大块,这个话题我们以后讨论。)可以说能为用户提供最完善、最智能、最有效的视听服务。就拿音乐来说,QQ音乐经历了低谷期,慢慢积累沉淀,如今已经在国内市场没有任何竞争对手,准备独立上市,提供更优质的音乐服务,打通版权和商业,突破自己成为它最大的瓶颈。将这些平台整合起来,集公司各个事业群资源于一个小小的音箱上。在未来,这样的整合,可以被移植到车载系统、智能家具等其他设备上。音箱只是一个先行者,探路者,一旦这一平台模式成熟,那么面对未来各种可能,都可以心中不慌,从容面对。

总而言之,如果说听听是一款终极产品,我是不认同,毕竟它本身的缺陷很明显。它要做的有两个方面,一方面是继续打磨自身,像QQ音乐一样,慢慢沉淀,打磨出腾讯自己的硬件设备;另一方面,则是在AI算法上不断和公司整体层面的资源进行整合,把庞大的体系,浓缩为半尺大小的精华,为将来在其他领域的深入打下基础。

23:59:44 已有0条回复