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 使这变得相对简单:

腾讯交互翻译
腾讯交互翻译

腾讯AI Lab发布的一款AI辅助翻译产品

下载
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学校


我们在媒体上

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1566

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

241

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

150

2025.10.17

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

530

2023.06.20

js获取当前时间
js获取当前时间

JS全称JavaScript,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言;它是一种属于网络的高级脚本语言,主要用于Web,常用来为网页添加各式各样的动态功能。js怎么获取当前时间呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

576

2023.07.28

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js是什么意思
js是什么意思

JS是JavaScript的缩写,它是一种广泛应用于网页开发的脚本语言。JavaScript是一种解释性的、基于对象和事件驱动的编程语言,通常用于为网页增加交互性和动态性。它可以在网页上实现复杂的功能和效果,如表单验证、页面元素操作、动画效果、数据交互等。

6207

2023.08.17

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

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

492

2023.09.01

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
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号