XDM,JS如何函数式编程?看这就够了!(六)

简介: 我们前篇谈了很多关于【闭包】的理解了,所以你应该会知道,我们现在将要谈的就是 ——【异步】

image.png

第六篇,我们首先再次重申那句经典的话:


如果要整体了解一个人的核心 JavaScript 技能,我最感兴趣的是他们会如何使用闭包以及如何充分利用异步。—— Jake Archibald


我们前篇谈了很多关于【闭包】的理解了,所以你应该会知道,我们现在将要谈的就是 ——【异步】。


  • 点赞富三代,关注美一生!👍👍👍👍👍👍


再看异步



我们为什么觉得“异步问题”复杂呢?

其中很重要的一个原因是 —— 时间!时间将我们对数据的操作、管理,变复杂了好几个量级!


(需要特别提出并明确的是:异步和同步之间是可以相互转化的! 我们使用异步或者同步取决于 —— 如何使代码更加可读!)


函数式编程给出了实现“代码更可读”的落地原则(已多次回顾):

  1. 严格控制显示的输入输出;
  2. 封装高级函数,比如偏函数、柯里化实现参数的时域分离;
  3. 封装高级函数,比如函数组装,形成黑盒;
  4. 对其它基础方法进行封装,比如数组操作;
  5. ......


所以我们可以期待,异步在函数式编程中的表现!


减少时间状态



上代码:

var customerId = 42;
var customer;
lookupCustomer( customerId, function onCustomer(customerRecord){ // 通过查询用户来查询订单
    var orders = customer ? customer.orders : null;
    customer = customerRecord;
    if (orders) {
        customer.orders = orders;
    }
} );
lookupOrders( customerId, function onOrders(customerOrders){ // 直接查询订单
    if (!customer) {
        customer = {};
    }
    customer.orders = customerOrders;
} );


onCustomer(..)onOrders(..) 是两个【回调函数】释义,两者执行的先后顺序并不能确定,所以它是一个基于时间的复杂状态。


释义:回调函数其实就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。


  • 怎样去确定它们在时间上执行的先后关系呢?


通常来说,我们最先想到的是:把 lookupOrders(..) 写到 onCustomer(..) 里面,那我们就可以确认 onOrders(..) 会在 onCustomer(..) 之后运行。

这样写,对吗?


不对!因为 onCustomer(..)onOrders(..) 这两个回调函数的关系更像是一种竞争关系(都是赋值 customer.orders),它们应该并行执行而不是串行执行

即:我不管你们谁先执行,谁先执行完,谁就赋值给 customer.orders


那我们的思路应该是:


用相应的 if-声明在各自的回调函数里来检查外部作用域的变量 customer。当各自的回调函数被执行,将会去检测 customer 的状态,从而确定各自的执行顺序,如果 customer 在回调函数里还没被定义,那他就是先运行的,否则则是第二个运行的。

不过,这样让代码又变得更加难阅读!!函数内部赋值依赖于外部变量、甚至受外部回调函数的影响。


那究竟怎么办呢?


最终,我们借用  JS promise 减少这个时间状态,将异步转成同步:


var customerId = 42;
var customerPromise = lookupCustomer( customerId );
var ordersPromise = lookupOrders( customerId );
customerPromise.then( function onCustomer(customer){
    ordersPromise.then( function onOrders(orders){
        customer.orders = orders;
    } );
} );


两个 .then(..) 运行之前,lookupCustomer(..) 和  lookupOrders(..) 已被同步调用,满足并行执行,谁先结束,谁赋值给 customer.orders,所以我们不需要知道谁先谁后!


在这样的实现下,不再需要时间先后的概念!减少了时间状态!!代码的可读性更高了!!


惰性的数组



var a = [1,2,3]
var b = a.map( v => v * 2 );
b;            // [2,4,6]


这是一个积极的数组,因为它们同步(即时)地操作着离散的即时值或值的列表/结构上的值。


什么意思?


a 映射到 b,再去修改 a ,b 不会收到影响。


var a = [];
var b = mapLazy( a, v => v * 2 );
a.push( 1 );
a[0];        // 1
b[0];        // 2
a.push( 2 );
a[1];        // 2
b[1];        // 4


而这,是一个惰性的数组mapLazy(..) 本质上 “监听” 了数组 a,只要一个新的值添加到数组的末端(push(..)),它都会运行映射函数 v => v * 2 并把改变后的值添加到数组 b 里。


什么意思?


a 映射到 b,再去修改 a ,b 也会修改。

  • 那么为什么第二种就是惰性的呢?


原来,后者存在异步的概念。

让我们来想象这样一个数组,它不只是简单地获得值,它还是一个懒惰地接受和响应(也就是“反应”)值的数组,比如:


// 发布者:
var a = new LazyArray();
setInterval( function everySecond(){
    a.push( Math.random() );
}, 1000 );
// **************************
// 订阅者:
var b = a.map( function double(v){
    return v * 2;
} );
b.listen( function onValue(v){
    console.log( v );
} );


设置“懒惰的数组” a 的过程是异步的!

b ,是 map 映射后的数组,但更重要的是,b 是反应性的,我们对 b 加了一个类似监听器的东西。


我们称前半部分为发布者,后半部分为订阅者

你一定会疑问:定义这个懒惰的数组,有何作用?这里发布者、订阅者,又是几个意思?


这里直接给出解答:


  1. 正如 promise 从单个异步操作中抽离出我们所担心的时间状态,发布订阅模式也能从一系列的值或操作中抽离(分割)时间状态;
  2. 我们分离 【发布者】 和 【订阅者】 的相关代码,让代码应该各司其职。这样的代码组织可以很大程度上提高代码的可读性和维护性。


这里再多小结一句:时间让异步更加复杂,函数式编程在异步下的运用就是减少或直接干掉时间状态。


想象下 a 还可以被绑定上一些其他的事件上,比如说用户的鼠标点击事件和键盘按键事件,服务端来的 websocket 消息等。


在这些情况下,a 没必要关注自己的时间状态。


// 发布者:
var a = {
    onValue(v){
        b.onValue( v );
    }
};
setInterval( function everySecond(){
    a.onValue( Math.random() );
}, 1000 );
// **************************
// 订阅者:
var b = {
    map(v){
        return v * 2;
    },
    onValue(v){
        v = this.map( v );
        console.log( v );
    }
};


这里,【时间】 与 【a、b】 之间的关系是声明式的,不是命令式的。

我们进一步,把 b = a.map(..) 替换成 b.onValue(v),尽量避免将 b 的逻辑夹杂在 a 中,让关注点更加分离!


上述的 LazyArray 又可叫做 observable!(当然,它不止用在 map 方法中)

现在已经有各种各样的 Observables 的库类,最出名的是 RxJSMost

以 RxJS 为例:


// 发布者:
var a = new Rx.Subject();
setInterval( function everySecond(){
    a.next( Math.random() );
}, 1000 );
// **************************
// 订阅者:
var b = a.map( function double(v){
    return v * 2;
} );
b.subscribe( function onValue(v){
    console.log( v );
} );


不仅如此,RxJS 还定义了超过 100 个可以在有新值添加时才触发的方法。就像数组一样。每个 Observable 的方法都会返回一个新的 Observable,意味着他们是链式的。如果一个方法被调用,则它的返回值应该由输入的 Observable 去返回,然后触发到输出的 Observable里,否则抛弃。


比如:

var b =
    a
    .filter( v => v % 2 == 1 )        // 过滤掉偶数
    .distinctUntilChanged()            // 过滤连续相同的流
    .throttle( 100 )                // 函数节流(合并100毫秒内的流)
    .map( v = v * 2 );                // 变2倍
b.subscribe( function onValue(v){
    console.log( "Next:", v );
} );
  • 通常,subscribe(..) 方法都会在链式写法的最后被调用


更多关于:RxJS


阶段小结



本篇介绍了【异步】在函数式编程中的表现。


原则是:对于那些异步中有时态的操作,基础的函数式编程原理就是将它们变为无时态的应用。即减少时间状态


就像 promise 创建了一个单一的未来值,我们可以创建一个积极的列表的值来代替像惰性的observable(事件)流的值。


我们介绍了 RxJS 库,后续我们还会介绍更多优美的 JS 函数式编程库!

(俗话说的好,三方库选的好,下班都很早!!)


现在本瓜有点明白那句话了:看一门语言是不是函数式编程,取决于它的核心库是不是函数式编程。


也许我们还不熟悉像 RxJS 这类库,但我们慢慢就会越来越重视它们,越来越使用它们,越来越领会到它们!!


异步,以上。

预告:第七篇(系列完结篇) —— 实践 + 库推荐!

我是掘金安东尼,公众号【掘金安东尼】,输入暴露输出,技术洞见生活!!!


相关文章
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
87 0
|
5月前
|
存储 JavaScript 前端开发
JavaScript——函数式编程Functor(函子)
JavaScript——函数式编程Functor(函子)
32 0
|
7月前
|
前端开发 JavaScript 开发者
函数式编程在JavaScript中的应用
【6月更文挑战第10天】本文探讨了函数式编程在JavaScript中的应用,介绍了函数式编程的基本概念,如纯函数和不可变数据。文中通过示例展示了高阶函数、不可变数据的使用,以及如何编写纯函数。此外,还讨论了函数组合和柯里化技术,它们能提升代码的灵活性和可重用性。掌握这些函数式编程技术能帮助开发者编写更简洁、可预测的JavaScript代码。
|
8月前
|
JavaScript 前端开发
JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式
【5月更文挑战第11天】JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式。map() 用于创建新数组,其中元素是原数组元素经过指定函数转换后的结果;filter() 则筛选出通过特定条件的元素生成新数组;reduce() 将数组元素累计为单一值。这三个方法使代码更简洁易读,例如:map() 可用于数组元素乘以 2,filter() 用于选取偶数,reduce() 计算数组元素之和。
56 2
|
8月前
|
JavaScript 前端开发 测试技术
JavaScript中的函数式编程:纯函数与高阶函数的概念解析
【4月更文挑战第22天】了解JavaScript中的函数式编程,关键在于纯函数和高阶函数。纯函数有确定输出和无副作用,利于预测、测试和维护。例如,`add(a, b)`函数即为纯函数。高阶函数接受或返回函数,用于抽象、复用和组合,如`map`、`filter`。函数式编程能提升代码可读性、可维护性和测试性,帮助构建高效应用。
|
8月前
|
JavaScript 前端开发 索引
JavaScript函数式编程【进阶】
JavaScript函数式编程【进阶】
62 1
|
8月前
|
存储 JavaScript 前端开发
JavaScript函数式编程[入门]
JavaScript函数式编程[入门]
61 1
|
8月前
|
前端开发 JavaScript 数据处理
深入学习JavaScript ES8函数式编程:特性与实践指南
深入学习JavaScript ES8函数式编程:特性与实践指南
127 0
|
缓存 JavaScript 前端开发
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(1)
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(1)
|
JavaScript 前端开发 测试技术
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(2)
带你读《现代Javascript高级教程》十四、JavaScript函数式编程(2)