0

0

掌握 JavaScript:利用高阶流释放函数响应式编程的力量

花韻仙語

花韻仙語

发布时间:2024-11-30 09:27:17

|

623人浏览过

|

来源于dev.to

转载

掌握 javascript:利用高阶流释放函数响应式编程的力量

javascript 中使用高阶流的函数响应式编程 (frp) 是处理代码中复杂的、基于时间的交互的强大方法。这是一种将我们的程序视为一系列数据流,而不是一系列命令式命令的方式。

让我们首先了解什么是流。在 frp 中,流是随时间变化的值序列。它可以是从鼠标点击到 api 响应的任何内容。当我们开始在代码中将这些流视为一等公民时,奇迹就会发生。

高阶流将这个概念更进一步。它们是流的流,使我们能够对更复杂的场景进行建模。想象一下用户搜索流,其中每个搜索都会触发一个新的结果流。这是一个正在运行的高阶流。

我发现掌握这些概念的最佳方法之一是通过实际例子。让我们深入研究一些代码:

const { fromevent } = rxjs;
const { map, switchmap } = rxjs.operators;

const searchinput = document.getelementbyid('search-input');
const searchbutton = document.getelementbyid('search-button');

const searchstream = fromevent(searchbutton, 'click').pipe(
  map(() => searchinput.value),
  switchmap(query => fetchsearchresults(query))
);

searchstream.subscribe(results => {
  // display results
});

function fetchsearchresults(query) {
  // simulate api call
  return new promise(resolve => {
    settimeout(() => {
      resolve(`results for ${query}`);
    }, 1000);
  });
}

在此示例中,我们正在创建搜索查询流。每次单击搜索按钮时,我们都会将单击事件映射到搜索输入的当前值。然后,我们使用 switchmap 为每个搜索查询创建一个新流。

立即学习Java免费学习笔记(深入)”;

这种方法的美妙之处在于它如何处理快速事件。如果用户快速多次单击搜索按钮,switchmap 将取消任何正在进行的搜索,只向我们提供最新查询的结果。

frp 的主要优点之一是它如何帮助我们管理复杂性。通过从流的角度思考,我们可以将复杂的交互分解为更小、更易于管理的部分。

让我们看另一个例子。假设我们正在构建一个协作文档编辑器。我们想要将更改同步到服务器,但我们不想发送每次击键。我们可以使用 frp 创建去抖动的更改流:

const { fromevent } = rxjs;
const { debouncetime, map } = rxjs.operators;

const editor = document.getelementbyid('editor');

const changestream = fromevent(editor, 'input').pipe(
  debouncetime(300),
  map(event => event.target.value)
);

changestream.subscribe(content => {
  sendtoserver(content);
});

function sendtoserver(content) {
  // simulated server send
  console.log('sending to server:', content);
}

在这里,我们创建一个输入事件流,将它们消除 300 毫秒,然后映射到编辑器的内容。这意味着,只有当用户暂停输入至少 300 毫秒时,我们才会向服务器发送更新。

frp 的挑战之一是管理共享状态。函数范式鼓励我们避免可变状态,但有时我们需要跟踪事物。流为我们提供了一种干净地完成此操作的方法:

const { behaviorsubject } = rxjs;
const { scan } = rxjs.operators;

const initialstate = { count: 0 };
const state$ = new behaviorsubject(initialstate);

const increment$ = new behaviorsubject(1);
const decrement$ = new behaviorsubject(-1);

const counter$ = state$.pipe(
  scan((state, change) => ({ count: state.count + change }), initialstate)
);

increment$.subscribe(state$);
decrement$.subscribe(state$);

counter$.subscribe(state => console.log(state.count));

// increment
increment$.next(1);
// decrement
decrement$.next(-1);

在此示例中,我们使用behaviorsubject 来表示我们的应用程序状态。我们为递增和递减操作创建单独的流,然后使用扫描运算符将这些更改累积到新状态。

这种模式为我们提供了不可变状态更新的好处,同时仍然允许我们将应用程序建模为一系列流。

frp 最强大的方面之一是它如何让我们从简单的构建块组成复杂的行为。让我们看一个如何实现拖放功能的示例:

const { fromevent, merge } = rxjs;
const { map, takeuntil, switchmap } = rxjs.operators;

const draggable = document.getelementbyid('draggable');

const mousedown$ = fromevent(draggable, 'mousedown');
const mousemove$ = fromevent(document, 'mousemove');
const mouseup$ = fromevent(document, 'mouseup');

const drag$ = mousedown$.pipe(
  switchmap(start => {
    const startx = start.clientx - draggable.offsetleft;
    const starty = start.clienty - draggable.offsettop;

    return mousemove$.pipe(
      map(move => ({
        x: move.clientx - startx,
        y: move.clienty - starty
      })),
      takeuntil(mouseup$)
    );
  })
);

drag$.subscribe(pos => {
  draggable.style.left = `${pos.x}px`;
  draggable.style.top = `${pos.y}px`;
});

在这里,我们组合多个事件流来创建一个代表拖动操作的高阶流。 switchmap 运算符让我们为每次拖动创建一个新的流,而 takeuntil 确保当用户释放鼠标按钮时我们停止跟踪鼠标移动。

frp 的挑战之一是处理背压 - 当我们的流产生值的速度比我们消耗它们的速度快时会发生什么? rxjs 为此提供了几种策略。让我们看一个使用 buffertime 运算符的示例:

const { interval } = rxjs;
const { buffertime } = rxjs.operators;

const faststream$ = interval(10); // emits every 10ms

const bufferedstream$ = faststream$.pipe(
  buffertime(1000) // collect values for 1 second
);

bufferedstream$.subscribe(buffer => {
  console.log(`received ${buffer.length} values`);
});

在此示例中,我们将快速流中的值缓冲到每秒发出一次的数组中。这对于处理高频事件(例如鼠标移动或传感器读数)非常有用。

随着我们深入研究 frp,我们经常发现自己想要创建自定义运算符。 rxjs 使这变得相对简单:

问小白
问小白

免费使用DeepSeek满血版

下载
const { observable } = rxjs;

function customoperator() {
  return (source$) => {
    return new observable(observer => {
      return source$.subscribe({
        next(value) {
          if (value % 2 === 0) {
            observer.next(value * 2);
          }
        },
        error(err) { observer.error(err); },
        complete() { observer.complete(); }
      });
    });
  };
}

const source$ = of(1, 2, 3, 4, 5);
const result$ = source$.pipe(customoperator());

result$.subscribe(x => console.log(x)); // outputs: 4, 8

这个自定义运算符将偶数加倍并过滤掉奇数。创建自定义运算符使我们能够封装复杂的流操作并在我们的应用程序中重用它们。

frp 真正发挥作用的一个领域是处理复杂的异步操作。让我们看一个示例,了解如何实现具有指数退避的重试机制:

const { of, throwerror } = rxjs;
const { mergemap, delay, retry } = rxjs.operators;

function fetchwithretry(url) {
  return of(url).pipe(
    mergemap(u => {
      // simulate a failing api call
      return math.random() < 0.5 ? throwerror('api error') : of(`response from ${u}`);
    }),
    retry({
      count: 3,
      delay: (error, retrycount) => {
        const delay = math.pow(2, retrycount) * 1000;
        console.log(`retrying in ${delay}ms`);
        return of(null).pipe(delay(delay));
      }
    })
  );
}

fetchwithretry('https://api.example.com')
  .subscribe(
    response => console.log(response),
    error => console.error('failed after 3 retries', error)
  );

在此示例中,我们将重试运算符与实现指数退避的自定义延迟函数结合使用。当表达为流时,这种复杂的异步行为变得更易于管理。

当我们使用 frp 构建更大的应用程序时,我们经常需要管理多个相互交互的流。 mergelatest 运算符对此非常有用:

const { combinelatest, behaviorsubject } = rxjs;

const userprofile$ = new behaviorsubject({ name: 'john' });
const userpreferences$ = new behaviorsubject({ theme: 'light' });
const currentroute$ = new behaviorsubject('/home');

const appstate$ = combinelatest([
  userprofile$,
  userpreferences$,
  currentroute$
]).pipe(
  map(([profile, preferences, route]) => ({
    profile,
    preferences,
    route
  }))
);

appstate$.subscribe(state => {
  console.log('app state updated:', state);
});

// update individual streams
userpreferences$.next({ theme: 'dark' });
currentroute$.next('/settings');

这种模式允许我们为应用程序状态的不同方面维护单独的流,同时仍然能够对整体状态的变化做出反应。

frp 最强大的方面之一是它如何改变我们思考代码的方式。我们不是命令式地逐步描述我们的程序应该做什么,而是声明式地描述数据流和转换。这通常会导致代码更容易推理和测试。

说到测试,frp 可以让我们的测试更加稳健、不那么脆弱。我们可以直接测试我们的流,而不是依赖复杂的模拟和存根:

const { TestScheduler } = require('rxjs/testing');

describe('My Observable', () => {
  let testScheduler;

  beforeEach(() => {
    testScheduler = new TestScheduler((actual, expected) => {
      expect(actual).toEqual(expected);
    });
  });

  it('should filter even numbers', () => {
    testScheduler.run(({ cold, expectObservable }) => {
      const source$ = cold('a-b-c-d-e-|', { a: 1, b: 2, c: 3, d: 4, e: 5 });
      const expected = '---b---d-|';

      const result$ = source$.pipe(filter(x => x % 2 === 0));

      expectObservable(result$).toBe(expected, { b: 2, d: 4 });
    });
  });
});

这个例子使用rxjs的testscheduler来测试一个简单的过滤操作。这种方法的优点在于我们可以以同步、确定性的方式测试复杂的异步行为。

正如我们所见,具有高阶流的 frp 提供了一个强大的工具包来管理 javascript 应用程序的复杂性。它使我们能够以声明性方式表达复杂的、基于时间的交互,从而使代码通常更易于维护且更易于推理。

然而,这并不是灵丹妙药。与任何范例一样,frp 也有其学习曲线和潜在陷阱。明智地使用它并了解何时更传统的命令式方法可能更简单非常重要。

随着我们继续构建日益复杂的反应式系统,frp 为我们提供了一套强大的工具和模式。通过以流的方式思考,我们可以创建更具弹性、响应更快且可维护的应用程序。无论我们是处理用户输入、管理应用程序状态,还是编排复杂的异步操作,frp 都使我们能够清晰、简洁地表达我们的意图。

frp 之旅可能充满挑战,但也非常有价值。当我们对这些概念越来越熟悉时,我们会发现自己能够解决曾经看似棘手的问题。我们将编写更具声明性、更具可组合性并且最终更强大的代码。

所以让我们拥抱直播吧。让我们思考流动和转换。让我们构建真正响应式的应用程序,以优雅地响应复杂、不断变化的用户交互和数据流世界。借助 frp 和高阶流,我们拥有了创建下一代响应式、弹性 javascript 应用程序的工具。


我们的创作

一定要看看我们的创作:

投资者中心 | 智能生活 | 时代与回响 | 令人费解的谜团 | 印度教 | 精英开发 | js学校


我们在媒体上

科技考拉洞察 | 时代与回响世界 | 投资者中央媒体 | 令人费解的谜团 | 科学与时代媒介 | 现代印度教

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

754

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

478

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

434

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1031

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

658

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

553

2023.09.20

excel表格操作技巧大全 表格制作excel教程
excel表格操作技巧大全 表格制作excel教程

Excel表格操作的核心技巧在于 熟练使用快捷键、数据处理函数及视图工具,如Ctrl+C/V(复制粘贴)、Alt+=(自动求和)、条件格式、数据验证及数据透视表。掌握这些可大幅提升数据分析与办公效率,实现快速录入、查找、筛选和汇总。

0

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号