使用 jQuery 实现时间感应用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<div>测试对时间的感觉</div>
<button id="hold-me">按住 1s 后松开</button>
<div>你的时间:<span id="hold-time"></span>ms</div>
<div id="rank"></div>
</div>
<script
src="https://code.jquery.com/jquery-1.12.4.js"
integrity="sha256-Qw82+bXyGq6MydymqBxNPYTaUXXq7c8v3CwiYwLLNXU="
crossorigin="anonymous"></script>
<script>
let startTime;
$('#hold-me').mousedown(function() {
startTime = new Date();
});
$('#hold-me').mouseup(function () {
if (startTime) {
const elapsedTime = new Date() - startTime;
startTime = null;
$('#hold-time').text(elapsedTime);
$.ajax(`https://timing-sense-score-board.herokuapp.com/score/${elapsedTime}`)
.done(function (data) {
$('#rank').text(`你超过了 ${data.rank}% 的人`);
});
}
});
</script>
</body>
</html>
使用 RxJS 实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<div>测试对时间的感觉</div>
<button id="hold-me">按住 1s 后松开</button>
<div>你的时间:<span id="hold-time"></span>ms</div>
<div id="rank"></div>
</div>
<script src="https://unpkg.com/rxjs@5.4.2/bundles/Rx.min.js"></script>
<script>
const holdMeBtn = document.querySelector('#hold-me');
const mouseDown$ = Rx.Observable.fromEvent(holdMeBtn, 'mousedown');
const mouseUp$ = Rx.Observable.fromEvent(holdMeBtn, 'mouseup');
const holdTime$ = mouseUp$.timestamp().withLatestFrom(
mouseDown$.timestamp(),
(up, down) => up.timestamp - down.timestamp
);
holdTime$.subscribe(ms => {
document.querySelector('#hold-time').innerText = ms;
});
holdTime$.flatMap(ms => Rx.Observable.ajax(`https://timing-sense-score-board.herokuapp.com/score/${ms}`))
.map(e => e.response)
.subscribe(data => {
document.querySelector('#rank').innerText = `你超过了 ${data.rank}% 的人`;
});
</script>
</body>
</html>
RxJS 世界中有一种特殊对象——“流”,也可以叫“数据流”或“Observable对象”。
代表“流”的变量标识符,都是以
$
结尾。
上面 mouseDown 和 mouseUp 都是数据流,分别代表按钮上的 mousedown 事件和 mouseup 事件集合,不光包含已经发生的事件,还包含没有发生的鼠标事件。对数据流一视同仁,这就是数据流的妙处。
“流”可以通过多种方法创造出来,mouseDown 和 mouseUp 通过 fromEvent 函数从网页的 DOM 元素中获得,holdTime 这个流则是通过 mouseDown 和 mouseUp
流对象中“流淌”的是数据,而通过 subscribe
函数可以添加函数对数据进行操作,上面的代码中,对 holdTime$
对象有两个 subscribe
调用,一个用来更新 DOM,另一个用来调用 API 请求。
在 jQuery 的实现中,有被交叉访问的变量(startTime
),两个不同函数的逻辑相互关联,稍有不慎就会引发 bug ,代码看起来就是一串指令的组合;在RxJS的代码中,没有这样纠缠不清的变量,会发现所有的变量其实都没有“变”,赋值时是什么值,就会一直保持这些值,代码是一个一个函数,每个函数只是对输入的参数做了响应,然后返回结果。
RxJS 引用了两个重要的编程思想:
强调使用函数来解决问题的一种编程方式。函数式编程对函数的使用有一些特殊的要求,这些要求包括以下几点:
从语言角度讲,JavaScript 不算一个纯粹意义上的函数式编程语言,但是,JavaScript 中的函数有第一公民的身份,因为函数本身就是一个对象,可以被赋值给一个变量,可以作为参数传递,由此可以很方便地应用函数式编程的许多思想。
把函数式编程看作一种编程思想,即使语言本身不支持一些特性,依然可以应用这样的编程思想,用于提高代码的质量。
JavaScript 如何满足函数式编程的特性需要:
声明式
命名式编程
function double(arr) {
const results = [];
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] * 2);
}
return results;
}
function addOne (arr) {
const results = [];
for (let i = 0; i < arr.length; i++) {
results.push(arr[i] + 1);
}
return results;
}
声明式编程
把一个数组映射成另一个数组
function double (arr) {
return arr.map(function (item) {
return item * 2;
});
}
function addOne(arr) {
return arr.map(function (item) {
return item + 1;
});
}
进一步简化
const double = arr => arr.map(item => item * 2);
const addOne = arr => arr.map(item => item + 1);
纯函数
alert
或者 confirm
函数数据不可变
简单说来,面向对象的方法把状态的改变封装起来,以此达到让代码清晰的目的;而函数式编程则是尽量减少变化的部分,以此让代码逻辑更加清晰。
面向对象的思想是把数据封装在类的实例对象中,把数据藏起来,让外部不能直接操作这些对象,只能通过类提供的实例方法来读取和修改这些数据,这样就限制了对数据的访问方式。对于毫无节制任意修改数据的编程方式,面向对象无疑是巨大的进步,因为通过定义类的方法,可以控制对数据的操作。
但是,面向对象隐藏数据的特点,带来了一个先天的缺陷,就是数据的修改历史完全被隐藏了。有人说,面向对象编程提供了一种持续编写烂代码的方式,它让你通过一系列补丁来拼凑程序。
函数式编程中,倾向于数据就是数据,函数就是函数,函数可以处理数据,也是并不像面向对象的类概念一样把数据和函数封在一起,而是让每个函数都不要去修改原有数据(不可变性),而且通过产生新的数据来作为运算结果(纯函数)。
Reactive Extension,也叫 ReactiveX,或者简称 Rx,指的是实践响应式编程的一套工具。
An API for asynchronous programming with observable streams.
Rx(包括RxJS)诞生的主要目的虽然是解决异步处理的问题,但并不表示 Rx 不适合同步的数据处理,实际上,使用 RxJS 之后大部分代码不需要关心自己是被同步执行还是异步执行,所以处理起来会更加简单。
FRP 包含两个重要元素:
正统 FRP 认为,一个系统如果能被称为 FRP,除了要有 Functional
和 Reactive
的特点,还必须要能够支持两个事件可以“同时发生”,这就是指称性的要求。总之,按照正统 FRP 的说法,你的系统只有 Functional
和 Reactive
,不能说自己是 FRP。
包括 RxJS 在内的 Rx,到底算不算 FRP ?按照正统 FRP 的观点,Rx 不算,因为 Rx 不满足指称性的要求,在 Rx 的所有实现中,都存在一个局限,就是当两个“流”合并的时候,不能按照 FRP 那样严格处理同时发生的事件。
RxJS 模型的特点: