JavaScript – Pipeline Operator

介绍

Pipeline Operator (|>) 是一个很新的 JavaScript 语法,目前还在 TC39 stage 2

它有啥用呢?我们一起来了解一下 pipe 的前世今生。

 

参考

YouTube – Javascript's New Pipeline Operator Is Awesome!

 

Pipe 的前世今生

我们直接看例子。

Single function

这是一个 sum 函数

function sum(numbers) {
  return numbers.reduce((total, number) => total + number, 0);
}

没什么特别的,就是把 array 的 number 累加起来,返回一个总数,使用方式是这样

const numbers = [1, 2, 3, 4, 5, 6];
const total = sum(numbers); 
console.log(total); // 21

好,再一个 removeOdd 函数

function removeOdd(numbers) {
  return numbers.filter(number => number % 2 === 0);
}

也没什么特别的,就是把 array 里的单数删除,只留下双数,使用方式是这样

const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = removeOdd(numbers); 
console.log(evenNumbers); // [2, 4, 6]

Combine function

那如果我想要先 "删除单数 removeOdd" 接着 "累加 sum" 该怎么写呢?

const numbers = [1, 2, 3, 4, 5, 6];

const evenNumbers = removeOdd(numbers); // [2, 4, 6]
const total = sum(evenNumbers); 

console.log(total); // 12

简单,我们可以先调用 removeOdd 获得双数,接着再把双数拿去 sum,这样就获得总数了。

等等...这代码有点啰嗦丫。

让我们把它们连写在一起看看。

const numbers = [1, 2, 3, 4, 5, 6];
const total = sum(removeOdd(numbers)); // 12

呃...代码虽然是正确,但看上去不好理解,因为我们的逻辑是先 removeOdd 然后才 sum,但代码看上去的顺序却是颠倒的,先 sum 然后 removeOdd,一点都不直观。

Method chaining

我们回过头看看原生 Array 方法的调用方式

const numbers = [1, 2, 3, 4, 5, 6];

const total = numbers
  .filter(number => number % 2 === 0) // removeOdd
  .reduce((total, number) => total + number, 0); // sum

console.log(total); // 12

看到吗,代码的顺序和要执行的逻辑是一致的,这才符合直觉。

那我们有办法也写成这样吗?

有,扩展 Array prototype 就可以了。

复制代码
function sum(numbers) {
  return numbers.reduce((total, number) => total + number, 0);
}

function removeOdd(numbers) {
  return numbers.filter(number => number % 2 === 0);
}

Array.prototype.sum = function() {
  return sum(this);
}

Array.prototype.removeOdd = function() {
  return removeOdd(this);
}

const numbers = [1, 2, 3, 4, 5, 6];
const total = numbers.removeOdd().sum();
console.log(total); // 12
复制代码

RxJS 6.0 以前的写法就采用了 method chaining 方式,像这样

复制代码
// Import the necessary operators from RxJS 5
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/reduce';

// Create an observable that emits numbers from 1 to 5
const numbers$ = Observable.of(1, 2, 3, 4, 5);

// Chain operators: filter, map, reduce
numbers$
  .filter(num => num % 2 === 0)       // Keep even numbers
  .map(num => num * 2)                 // Double the numbers
  .reduce((acc, num) => acc + num, 0)  // Sum the numbers
  .subscribe(result => {
    console.log(result);  // Output will be 12 (i.e., (2*2) + (4*2) = 4 + 8)
  });
复制代码

Pipe function

method chaining 写法有一些致命的问题,比如它不支持 tree shaking 等等。

RxJS 在 v6.0 版本中,把 method chaining 换成了 Pipe function 写法。

这是一个非常严重的 breaking changes,对用户来说,项目中每一个使用到 RxJS 的地方都需要改代码😨。

从这一点也可以看出 method chaining 的问题很大,所以 RxJS 才不惜代价也要改成 Pipe function。

Pipe function 的写法是这样的:

首先需要一个通用的 pipe 函数

复制代码
function pipe(source, ...pipeFns) {
  let result = source;
  for (const pipeFn of pipeFns) {
    const fnReturn = pipeFn(result);
    if (fnReturn !== undefined) {
      result = fnReturn;
    }
  }
  return result;
}
复制代码

接着

const numbers = [1, 2, 3, 4, 5, 6];
const total = pipe(numbers, removeOdd, sum);
console.log(total); // 12

原理很简单,就是拿上一个函数的 return,传入下一个函数,以此类推。

pipe function 是独立的,它不需要和 prototype 有关联,所以完全支持 tree shaking。

pipe function with arguments

如果我们的函数需要参数也没问题,

function removeOddOrEven(numbers, oddOrEven) {
  return numbers.filter(num => oddOrEven === 'odd' ? num % 2 === 0 : num % 2);
}

调用时需要传入参数 oddOrEven。

调用时 wrap 一层箭头函数即可

const total = pipe(numbers, numbers => removeOddOrEven(numbers, 'odd'), sum);

或者写一个通用的 wrapper 

复制代码
export function wrapToPipeFn(pipeFn) {
  return (...args) => value => pipeFn(value, ...args)
}

const removeOddOrEvenPipe = wrapToPipeFn(removeOddOrEven);

const numbers = [1, 2, 3, 4, 5, 6];
const total = pipe(numbers, removeOddOrEvenPipe('odd'), sum);
console.log(total); // 12
复制代码

RxJS > v6.0

上一 part 我们看到的是 RxJS 5.0 的语法,采用的是 method chaining,而 v6.0 后就改成 pipe function 了。

复制代码
// Import the necessary operators from RxJS 7
import { of } from 'rxjs';
import { filter, map, reduce } from 'rxjs/operators';

// Create an observable that emits numbers from 1 to 5
const numbers$ = of(1, 2, 3, 4, 5);

// Chain operators using pipe
numbers$
  .pipe(
    filter(num => num % 2 === 0),      // Keep even numbers
    map(num => num * 2),                // Double the numbers
    reduce((acc, num) => acc + num, 0)  // Sum the numbers
  )
  .subscribe(result => {
    console.log(result);  // Output will be 12 (i.e., (2*2) + (4*2) = 4 + 8)
  });
复制代码

 

Pipeline Operator (|>)

主角登场🎉🎉

了解了 pipe function,再来看 Pipeline Operator 就很简单了。

这一句

const total = pipe(numbers, removeOdd, sum);

改成这样

const total = numbers |> removeOdd |> sum;

效果一模一样,这就是 JS 的 Pipeline Operator 语法。

去掉 pipe 函数调用,然后把逗号 (,) 换成 pipe 箭头 (|>)。

这一句

const total = pipe(numbers, (numbers) => removeOddOrEven(numbers, 'odd'), sum);

改成这样

const total = numbers |> numbers => removeOddOrEven(numbers, 'odd') |> sum;

效果一模一样。

此外,Pipeline Operator 还支持 async return 等等,我目前还没有用到就不给例子了,有兴趣的读友自己玩玩呗。

 

Babel for Pipeline Operator

Pipeline Operator 语法还很新,需要 Babel 做转译。(TypeScript 还不支持,说是要等到 stage 3)

这里简单演示一下 Babel setup。

创建项目

yarn init

安装 Babel

yarn add @babel/cli @babel/core @babel/preset-env --dev

安装插件 for Pipeline Operator

yarn add @babel/plugin-proposal-pipeline-operator --dev

创建 Babel config file -- babel.config.json

复制代码
{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-pipeline-operator",
      {
        "topicToken": "^^",
        "proposal": "fsharp"
      }
    ]
  ]
}
复制代码

src/main.js

复制代码
function sum(numbers) {
  return numbers.reduce((total, number) => total + number, 0);
}

function removeOdd(numbers) {
  return numbers.filter(number => number % 2 === 0);
}

function removeOddOrEven(numbers, oddOrEven) {
  return numbers.filter(num => oddOrEven === 'odd' ? num % 2 === 0 : num % 2);
}

function pipe(source, ...pipeFns) {
  let result = source;
  for (const pipeFn of pipeFns) {
    const fnReturn = pipeFn(result);
    if (fnReturn !== undefined) {
      result = fnReturn;
    }
  }
  return result;
}

function wrapToPipeFn(pipeFn) {
  return (...args) =>value => pipeFn(value, ...args)
}

const removeOddOrEvenPipe = wrapToPipeFn(removeOddOrEven);

const numbers = [1, 2, 3, 4, 5, 6];
// const total = pipe(numbers, (numbers) => removeOddOrEven(numbers, 'odd'), sum);
const total = numbers |> numbers => removeOddOrEven(numbers, 'odd') |> sum;
console.log('total', total); // 12
View Code
复制代码

src/index.html

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="/lib/main.js"></script>
</body>
</html>
View Code
复制代码

执行 command

yarn run babel src -d lib --watch

Live Server 打开 src/index.html 就可以了

以上。

 

posted @   兴杰  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体
历史上的今天:
2024-02-02 Angular 18+ 高级教程 – Angular Configuration (angular.json)
2024-02-02 JavaScript – XMLHttpRequest
2017-02-02 angular2 学习笔记 ( angular cli & npm version manage npm 版本管理 )
点击右上角即可分享
微信分享提示