3

JavaScript函数式编程入门

 2 years ago
source link: https://zyun.360.cn/blog/?p=1909
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
JavaScript函数式编程入门
2022-1-07 19:33
1096 字

函数式编程概念简介

函数与方法

函数式一段可以通过其名称被调用的代码

方法是一段必须通过其名称及其关联对象的名称被调用的代码

var bar = (a) => {return a}bar(1)
var obj = {	bar: (a) => {return a}}obj.bar(1)

函数的引用透明性

所有的函数对于相同的输入都会返回相同的值

可以用值替换函数

4 = double(2)Math.max(1,2,3,4) = 5

函数式编程主张声明式编程和编写抽象的代码

命令式遍历数组

声明式遍历数组

纯函数

纯函数产生可测试的代码

给定输入就会返回相同输出的函数,遵循引用透明性

不应该依赖外部变量,也不应该改变外部变量

管道与组合

纯函数应该只做一件事情

举一个linux命令的例子:我们想要在一个文件中找到一个字符串并统计它的出现次数:

cat jsBook | grep –i “composing” | wc

上面的命令通过组合多个函数解决了这个问题,组合不是linux命令独有的,但他是函数式编程的核心,也叫作函数式组合(Funtional Composition)

高阶函数是接收函数作为参数并且/或者返回函数作为输出的函数(Higher-Order functions HOC)

闭包和高阶函数

var fn = (arg) => {  let outer = "Visible"  let innerFn = () => {    console.log(outer)    console.log(arg)  }	return innerFn}var closureFn = fn(5)

当innerFn被return的时候为他设置作用域, 返回函数的引用被存储在closureFn里面

柯里化和偏应用

柯里化:吧一个多参数函数转换成一个嵌套的一元函数的过程

处理两个参数的柯里化函数

const add = (x,y) => x + y;const curry = (binaryFn) => {  return function (firstArg) {    return function (secondArg) {    	return binaryFn(firstArg, secondArg);    };  };};let autoCurriedAdd = curry(add)autoCurriedAdd(2)(2)=> 4

为什么需要柯里化呢?

柯里化实战:在数组中寻找数字

let match = curry(function(expr, str) {	return str.match(expr);}); let hasNumber = match(/[0-9]+/) let filter = curry(function(f, ary) {	return ary.filter(f);}); let findNumbersInArray = filter(hasNumber)	 findNumbersInArray(["js","number1"]) => ["number1"]

Partial Application  偏应用,颠倒参数的传递顺序

将回调函数和时间参数调换

setTimeout(() => console.log("Do X task"),10);setTimeout(() => console.log("Do Y task"),10);//柯里化之后const setTimeoutWrapper = (time,fn) => {setTimeout(fn,time);}const delayTenMs = curry(setTimeoutWrapper)(10)delayTenMs(() => console.log("Do X task"))delayTenMs(() => console.log("Do Y task"))

偏函数定义:

const partial = function (fn,...partialArgs){  let args = partialArgs;    return function(...fullArguments) {      let arg = 0;      for (let i = 0; i < args.length && arg < fullArguments.length; i++) {        if (args[i] === undefined) {          args[i] = fullArguments[arg++];        }    	}    	return fn.apply(null, args);  };};

组合(函数组合)和管道(Composition and Pipelines)

UNIX哲学:

  1. 每个程序只做好一件事情,如果要添加新功能,请重新构建而不是在复杂的旧程序中添加新功能
  2. 每个程序的输出应该是另一个尚未可知程序的输入

接收两个参数的compose函数的实现:

const compose = (a, b) => (c) => a(b(c))

接收n个参数:

const compose = (…fns) => (value) => reduce(fns.reverse(), (accumulator, fn) => fn(accumulator), value)

函子Functor

函子是一个普通对象,它实现了map函数,用map操作对象值的时候生成一个新对象

换句话说:函子是一个实现了map方法的对象

用Maybe和Either函子处理错误

Maybe函子

const MayBe = function(val) {	this.value = val}MayBe.of = function(val) {	return new MayBe(val)}MayBe.prototype.isNothing = function() {	return (this.value === null || this.value === undefined);}MayBe.prototype.map = function(fn) {	return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));}
MayBe.of("George").map((x) => x.toUpperCase()).map((x) => "Mr. " + x)//MayBe { value: 'Mr. GEORGE' } MayBe.of("George").map(() => undefined).map((x) => "Mr. " + x)//MayBe { value: null }
let getTopTenSubRedditData = (type) => {  let response = getTopTenSubRedditPosts(type);  return MayBe.of(response).map((arr) => arr['data'])    .map((arr) => arr['children'])    .map((arr) => arrayUtils.map(arr,      (x) => {        return {          title : x['data'].title,          url : x['data'].url        }    	}  ))}

不足:不知道map函数在哪一步出错了

Either函子

const Nothing = function(val) {	this.value = val;};Nothing.of = function(val) {	return new Nothing(val);};Nothing.prototype.map = function(f) {	return this;};const Some = function(val) {	this.value = val;};Some.of = function(val) {	return new Some(val);};Some.prototype.map = function(fn) {	return Some.of(fn(this.value));}const Either = {  Some : Some,  Nothing: Nothing}let getTopTenSubRedditPostsEither = (type) => {  let response  //对成功和失败用不同的函数处理,就可以捕获错误的信息  try{    response = Some.of(JSON.parse(request('GET',"https://www.reddit.    com/r/subreddits/" + type + ".json?limit=10").getBody('utf8')))  }catch(err) {    response = Nothing.of({ message: "Something went wrong" , errorCode:    err['statusCode'] })  }  return response}let getTopTenSubRedditDataEither = (type) => {  let response = getTopTenSubRedditPostsEither(type);  return response.map((arr) => arr['data'])    .map((arr) => arr['children'])    .map((arr) => arrayUtils.map(arr,    (x) => {    return {      title : x['data'].title,      url : x['data'].url    }  }  ))}getTopTenSubRedditDataEither('wrong')Nothing { value: { message: 'Something went wrong', errorCode: 404 } }

单子Monad(也是一种函子)

const MayBe = function(val) {	this.value = val}MayBe.of = function(val) {	return new MayBe(val)}MayBe.prototype.isNothing = function() {	return (this.value === null || this.value === undefined);}MayBe.prototype.map = function(fn) {	return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this.value));}MayBe.prototype.join = function() {	return this.isNothing() ? MayBe.of(null) : this.value;}MayBe.prototype.chain = function(f){	return this.map(f).join()}

通过join返回maybe对象里面的value值,脱掉了Maybe对象的外衣

异步编程Generator

Generator基础

创建一个Generator

function* gen() {	return 'first generator';}
let generatorResult = gen()console.log(generatorResult)gen {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}

那么如何得到里面的value呢?可以使用next()方法

generator.next().value=> 'first generator'

如果我们调用两次next方法呢?

let generatorResult = gen()//第一次generatorResult.next().value=> 'first generator'//第二次generatorResult.next().value=> undefined

yield关键字

function* generatorSequence() {  yield 'first';  yield 'second';  yield 'third';}
let generatorSequence = generatorSequence();generatorSequence.next().value=> firstgeneratorSequence.next().value=> second

带有yield关键字的G都会以惰性求值的方式进行,也就是说,代码直到调用的时候才会执行

Generator的值:

Generator里面有很多值,我们如何知道何时停止调用next方法呢?

每次对next函数的调用都会返回一个对象

{value: 'value', done: false}

done是一个可以判断Generator序列是否被完全消费的属性

//get generator instance variablelet generatorSequenceResult = generatorSequence();console.log('done value for the first time',generatorSequenceResult.next())console.log('done value for the second time',generatorSequenceResult.next())console.log('done value for the third time',generatorSequenceResult.next()) done value for the first time { value: 'first', done: false }done value for the second time { value: 'second', done: false }done value for the third time { value: 'third', done: false } console.log(generatorSequenceResult.next())=> { value: undefined, done: true }

用循环遍历Generator

for(let value of generatorSequence())console.log("for of value of generatorSequence is",value)which is going to print:for of value of generatorSequence is firstfor of value of generatorSequence is secondfor of value of generatorSequence is third

给 Generator传递值:

function* sayFullName() {  var firstName = yield;  var secondName = yield;  console.log(firstName + secondName);}let fullName = sayFullName()fullName.next() //代码会暂停在这一行var firstName = yield;fullName.next('anto') //yield会被auto取代,并赋值给firstname// yield 会暂停在 var secondName = yield 这一步fullName.next('aravinth')=> anto aravinth
使用Generator处理异步的代码

使用setTimeout的例子

let generator;let getDataOne = () => {  setTimeout(function(){  //call the generator and  //pass data via next    console.log(333)  generator.next('dummy data one')  }, 1000);}let getDataTwo = () => {  setTimeout(function(){  //call the generator and  //pass data via next  generator.next('dummy data two')  }, 1000);}function* main() {  console.log('1111')  let dataOne = yield getDataOne();  console.log('2222')  let dataTwo = yield getDataTwo();  console.log("data one",dataOne)  console.log("data two",dataTwo)  console.log('4444')}generator = main()generator.next()
generator = main()generator.next()

调用main函数,并遇到第一个yield语句,进入了暂停模式,但在暂停之前,他调用了getDataOne函数,

一秒钟之后setTimeout的回调会执行generator.next(‘dummy data one’),yield语句将会返回’dummy data one’,dataOneb变成’dummy data one’

然后会执行
let dataTwo = yield getDataTwo();
执行方式和之前一样

真实的例子:

let https = require('https');function httpGetAsync(url,callback) {  return https.get(url,    function(response) {      var body = '';      response.on('data', function(d) {      	body += d;      });      response.on('end', function() {        let parsed = JSON.parse(body)        callback(parsed)      })    }  );}
httpGetAsync('https://www.reddit.com/r/pics/.json',(data)=> {	console.log(data)}){    modhash: '',  children:    [ { kind: 't3', data: [Object] },    { kind: 't3', data: [Object] },    { kind: 't3', data: [Object] },    . . .    { kind: 't3', data: [Object] } ],  after: 't3_5bzyli',  before: null }
httpGetAsync('https://www.reddit.com/r/pics/.json',(picJson)=> {  httpGetAsync(picJson.data.children[0].data.url+".    json",(firstPicRedditData) => {    console.log(firstPicRedditData)  })})

上面的例子会造成回调地狱,用Generator来改写可以简化一下:

let generator function request(url) {  httpGetAsync( url, function(response){    generator.next( response );  });}
function *main() {  let picturesJson = yield request( "https://www.reddit.com/r/pics/.json" );  let firstPictureData = yield request(picturesJson.data.children[0].data.  url+".json")  console.log(firstPictureData)}generator = main()generator.next()

Recommend

  • 88

    买一送一:本系列可以让你掌握函数式编程,并且附赠 underscore 技能 日常BB 面对日新月异的编程语言都展开了函数式编程大战,灵活古老的javascript怎么不参战?javascript可是天然支持函数基础的元老人物。想要成为一名高逼格的程序员不管

  • 112

    找不到页面

  • 31
    • segmentfault.com 5 years ago
    • Cache

    编程范式 —— 函数式编程入门

    该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注我的

  • 80

    该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注我的 'blog' 命令式编程和声明式编程 拿泡茶这个事例进行区分命令式编程和声明式

  • 49
    • 掘金 juejin.im 5 years ago
    • Cache

    函数式编程入门实践(一)

    什么是函数式编程 在文章之前,先和大家讲一下对于函数式编程(Functional Programming, aka. FP)的理解(下文我会用FP指代函数式编程): FP需要保证函数都是纯净的,既不依赖外部的状态变量,也不产生副作用。基于此前提下,那么纯函数

  • 41
    • segmentfault.com 5 years ago
    • Cache

    JavaScript函数式编程入门经典

    一个持续更新的github笔记,链接地址: Front-End-Basics ,可以watch,也可以star。 此篇文章的地址:

  • 24
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    纯函数:函数式编程入门

    纯函数是所有函数式编程语言中使用的概念,这是一个非常重要的概念,因为它是函数式编程的基础,它允许你创建简单和复杂的组合模式。...

  • 9

    这本书和之前讲的《JavaScript函数式编程指南》内容上有很大的重叠...

  • 2
    • jelly.jd.com 2 years ago
    • Cache

    JavaScript 函数式编程-入门篇

    JavaScript 函数式编程-入门篇上传日期:2022.03.14学习函数式编程只是为了让你在开发过程中多一个编程范式的选择,大多时候我们无法将所有函数写成纯函数的形式,但是我们仍可以学习函数式编程的不依赖外部状态、不改写数...

  • 8
    • muyunyun.cn 1 year ago
    • Cache

    函数式编程入门

    命令式编程和声明式编程拿泡茶这个事例进行区分命令式编程和声明式编程命令式编程1.烧开水(为第一人称) 2.拿个茶杯 3.放茶叶 4.冲水声明式编程1.给我泡杯茶(为第二人称)举个 demo

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK