Heim  >  Artikel  >  Web-Frontend  >  JavaScript-Funktionsprogrammierung (3)

JavaScript-Funktionsprogrammierung (3)

黄舟
黄舟Original
2017-03-06 14:02:541042Durchsuche

Im zweiten Artikel haben wir mehrere gängige Funktoren vorgestellt, wie z. B. Vielleicht, Entweder, IO, vielleicht Viele Leute, die lesen Der zweite Artikel wird Zweifel haben:

„Was nützen diese Dinge?“ 』

Tatsächlich reicht es aus, den ersten Artikel zu lesen, wenn Sie nur lernen möchten, funktionalen Code mit kleinen Nebenwirkungen zu schreiben. Der zweite und der dritte Artikel hier konzentrieren sich auf einige funktionale Theoriepraktiken, und ja, diese sind in der tatsächlichen Produktion nur schwer (aber nicht unmöglich) anzuwenden, da viele Räder bereits gebaut wurden und sehr einfach zu implementieren sind. Beispielsweise ist die asynchrone Aufrufspezifikation von Promise, die mittlerweile im Frontend weit verbreitet ist, tatsächlich eine Art Monad (wird später besprochen); das zunehmend ausgereifte Redux wird als Variante von Flux implementiert , und das Kernkonzept sind auch Zustandsmaschinen und funktionale Programmierung.

1. Monad

Es gibt unzählige Einführungen und Tutorials zu Monad im Internet, daher verwende ich hier einfach einen einfacheren Artikel Der Nachteil der einfachen Verständlichkeit besteht natürlich darin, dass sie nicht streng ist, also verzeihen Sie mir/w

Wenn Sie die Spezifikation von Promise kennen, sollten Sie sich an eine erstaunliche Funktion erinnern of Promise:

doSomething()
    .then(result => {
        // 你可以return一个Promise链!
        return fetch('url').then(result => parseBody(result));
    })
    .then(result => {
        // 这里的result是上面那个Promise的终值
    })

doSomething()
    .then(result => {
        // 也可以直接return一个具体的值!
        return 123;
    })
    .then(result => {
        // result === 123
    })


Für eine Callback-Funktion von Promise kann sie entweder direkt einen Wert zurückgeben oder ein neues Promise zurückgeben, aber für ihre nachfolgenden Callback-Funktionen Mit anderen Worten , beide sind gleichwertig, was die seit langem kritisierte Verschachtelungshölle in NodeJS sauber löst.

Tatsächlich ist Promise eine Art Monade. Ja, Sie müssen vielleicht jeden Tag viele Promises schreiben, aber bis jetzt wussten Sie nicht, dass dieses Ding, das Sie jeden Tag verwenden, tatsächlich eine Funktion ist Konzept, das sehr hoch klingt.

Lassen Sie uns nun tatsächlich eine Monade implementieren. Wenn Sie sie nicht lesen möchten, denken Sie einfach an „Versprechen ist eine Art Monade“ und überspringen Sie einfach dieses Kapitel.

Schreiben wir eine Funktion cat. Die Funktion dieser Funktion ist die gleiche wie cat unter der Linux-Befehlszeile. Sie liest eine Datei und gibt dann den Inhalt aus Informationen zur Implementierung von IO finden Sie im vorherigen Artikel:

import fs from 'fs';
import _ from 'lodash';

var map = _.curry((f, x) => x.map(f));
var compose = _.flowRight;

var readFile = function(filename) {
    return new IO(_ => fs.readFileSync(filename, 'utf-8'));
};

var print = function(x) {
    return new IO(_ => {
        console.log(x);
        return x;
    });
}

var cat = compose(map(print), readFile);

cat("file")
//=> IO(IO("file的内容"))

Da hier zwei IOs beteiligt sind: Lesen von Dateien und Drucken, ist das Endergebnis, dass wir Holen Sie sich zwei Ebenen von IO. Wenn Sie es ausführen möchten, können Sie nur Folgendes aufrufen:

cat("file").__value().__value();
//=> 读取文件并打印到控制台

Es ist peinlich, oder? , müssen wir dann kontinuierlich 100 __value() s schreiben?

Natürlich kann es nicht unelegant sein. Wir implementieren eine Join-Methode. Ihre Funktion besteht darin, eine Schicht Functor abzuziehen und uns den Inhalt zugänglich zu machen:

var join = x => x.join();
IO.prototype.join = function() {
  return this.__value ? IO.of(null) : this.__value();
}

// 试试看
var foo = IO.of(IO.of('123'));

foo.join();
//=> IO('123')

Mit dem Join Methode Danach wird es etwas eleganter:

var cat = compose(join, map(print), readFile);
cat("file").__value();
//=> 读取文件并打印到控制台


join method can be-Funktoren werden abgeflacht, und wir bezeichnen Funktoren mit dieser Fähigkeit im Allgemeinen als Monaden.

Hier ist nur eine sehr einfache Entfernung einer Schicht der Functor-Verpackung, aber als elegante Programmierer können wir nicht immer manuellmap 🎜>join um überflüssige Verpackungen zu entfernen, andernfalls sieht der Code so aus:

Wir benötigen also eine Methode namens Chain, um den von uns erwarteten Kettenaufruf zu erreichen. Sie wird aufgerufen. Nach der Zuordnung wird automatisch Join aufgerufen um überflüssige Verpackungen zu entfernen. Dies ist auch ein Hauptmerkmal von Monad:
var doSomething = compose(join, map(f), join, map(g), join, map(h));

var chain = _.curry((f, functor) => functor.chain(f));
IO.prototype.chain = function(f) {
  return this.map(f).join();
}

// 现在可以这样调用了
var doSomething = compose(chain(f), chain(g), chain(h));

// 当然,也可以这样
someMonad.chain(f).chain(g).chain(h)

// 写成这样是不是很熟悉呢?
readFile('file')
    .chain(x => new IO(_ => {
        console.log(x);
        return x;
    }))
    .chain(x => new IO(_ => {
        // 对x做一些事情,然后返回
    }))

Haha, Sie haben es vielleicht bemerkt , ist

chain
nicht ähnlich zu

then

in Promise? Ja, sie verhalten sich tatsächlich konsistent (dann wird etwas mehr Logik haben, die Anzahl der Verschachtelungsebenen aufzeichnen und zwischen Promise und gewöhnlichen Rückgabewerten unterscheiden), und Promise ist tatsächlich ein funktionaler Gedanke. (Ursprünglich wollte ich Promise als Beispiel verwenden, um unten einige Beispiele zu schreiben, aber ich denke, jeder, der hier sehen kann, sollte in der Lage sein, verschiedene Promise-Ketten kompetent zu schreiben, daher werde ich nicht 0w0 schreiben) Kurz gesagt, Monad ermöglicht es uns, die Verschachtelungshölle zu vermeiden und kann problemlos tief verschachtelte funktionale Programmierung wie E/A und andere asynchrone Aufgaben durchführen.

2. Anwendung der funktionalen Programmierung

Okay, das ist alles für die Einführung einiger grundlegender Theorien der funktionalen Programmierung. Wenn Sie mehr wissen möchten, ist es tatsächlich empfehlenswert, Haskell oder Lisp zu lernen eine orthodoxere funktionale Sprache. Beantworten wir eine Frage: Welchen Nutzen hat funktionale Programmierung in praktischen Anwendungen?

1. React

React ist mittlerweile überall zu sehen, und manche Leute sagen vielleicht, dass es „gute Leistung“ und „cool“ hat „, „Reichhaltige Komponenten von Drittanbietern“, „Neuartig“ usw., aber diese sind nicht die kritischsten. Das Wichtigste ist, dass React neue Konzepte in die Front-End-Entwicklung einbringt: Funktions- und Zustandsmaschinen. Sehen wir uns an, wie man eine „reine Komponente“ in React schreibt:

var Text = props => (
    <p style={props.style}>{props.text}</p>
)

咦这不就是纯函数吗?对于任意的 text 输入,都会产生唯一的固定输出,只不过这个输出是一个 virtual DOM 的元素罢了。配合状态机,就大大简化了前端开发的复杂度:

state => virtual DOM => 真实 DOM

在 Redux 中更是可以把核心逻辑抽象成一个纯函数 reducer:

reducer(currentState, action) => newState


关于 React+Redux(或者其它FLUX架构)就不在这里介绍太多了,有兴趣的可以参考相关的教程。

2、Rxjs

Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。

在 Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:

--- 用户点击了按钮 --> 网络请求成功 --> 用户键盘输入 --> 某个定时事件发生 --> ......

举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:

var clicks = Rx.Observable
    .fromEvent(document, &#39;click&#39;)
    .bufferCount(2)
    .subscribe(x => console.log(x)); // 打印出前2次点击事件


其中 bufferCount 对于事件流的作用是这样的:

是不是很神奇呢?Rxjs 非常适合游戏、编辑器这种外部输入极多的应用,比如有的游戏可能有『搓大招』这个功能,即监听用户一系列连续的键盘、鼠标输入,比如上上下下左右左右BABA,不用事件流的思想的话,实现会非常困难且不优雅,但用 Rxjs 的话,就只是维护一个定长队列的问题而已:

var inputs = [];
var clicks = Rx.Observable
    .fromEvent(document, &#39;keydown&#39;)
    .scan((acc, cur) => {
        acc.push(cur.keyCode);
        var start = acc.length - 12 < 0 ? 0 : acc.length - 12;
        return acc.slice(start);
    }, inputs)
    .filter(x => x.join(&#39;,&#39;) == [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 66, 65].join(&#39;,&#39;))// 上上下下左右左右BABA,这里用了比较奇技淫巧的数组对比方法
    .subscribe(x => console.log(&#39;!!!!!!ACE!!!!!!&#39;));


当然,Rxjs 的作用远不止于此,但可以从这个范例里看出函数响应式编程的一些优良的特性。

3、Cycle.js

Cycle.js 是一个基于 Rxjs 的框架,它是一个彻彻底底的 FRP 理念的框架,和 React 一样支持 virtual DOM、JSX 语法,但现在似乎还没有看到大型的应用经验。

本质的讲,它就是在 Rxjs 的基础上加入了对 virtual DOM、容器和组件的支持,比如下面就是一个简单的『开关』按钮:

import xs from &#39;xstream&#39;;
import {run} from &#39;@cycle/xstream-run&#39;;
import {makeDOMDriver} from &#39;@cycle/dom&#39;;
import {html} from &#39;snabbdom-jsx&#39;;

function main(sources) {
  const sinks = {
    DOM: sources.DOM.select(&#39;input&#39;).events(&#39;click&#39;)
      .map(ev => ev.target.checked)
      .startWith(false)
      .map(toggled =>
        <p>
          <input type="checkbox" /> Toggle me
          <p>{toggled ? &#39;ON&#39; : &#39;off&#39;}</p>
        </p>
      )
  };
  return sinks;
}

const drivers = {
  DOM: makeDOMDriver(&#39;#app&#39;)
};

run(main, drivers);


当然,Cycle.js 这种『侵入式』的框架适用性不是太广,因为使用它就意味着应用中必须全部或者大部分都要围绕它的理念设计,这对于大规模应用来说反而是负担。

三、总结

既然是完结篇,那我们来总结一下这三篇文章究竟讲了些啥?

第一篇文章里,介绍了纯函数、柯里化、Point Free、声明式代码和命令式代码的区别,你可能忘记得差不多了,但只要记住『函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因』以及『让函数尽可能地纯净』就行了。

第二篇文章,或许是最没有也或许是最有干货的一篇,里面介绍了『容器』的概念和 MaybeEitherIO 这三个强大的 Functor。是的,大多数人或许都没有机会在生产环境中自己去实现这样的玩具级 Functor,但通过了解它们的特性会让你产生对于函数式编程的意识。

软件工程上讲『没有银弹』,函数式编程同样也不是万能的,它与烂大街的 OOP 一样,只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择 OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言人,这可能是大势所趋,也或许是昙花一现,但不妨碍我们去多掌握一种编程范式嘛0w0

以上就是JavaScript函数式编程(三)的内容,更多相关内容请关注PHP中文网(www.php.cn)!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn