Functional programming can reduce the complexity of the program: a function looks like a mathematical formula. Learning functional programming can help you write simpler code with fewer bugs.
Pure function
A pure function can be understood as a function that must have the same output with the same input, without any observable side effects
//pure function add(a + b) { return a + b; }
The above is a pure function, it does not depend on or change any variables other than the function state, always returns the same output for the same input.
//impure var minimum = 21; var checkAge = function(age) { return age >= minimum; // 如果minimum改變,函數(shù)結(jié)果也會(huì)改變 }
This function is not a pure function because it relies on external mutable state
If we move the variable inside the function, then it becomes a pure function, so that we can ensure that the function can be compared correctly every time age.
var checkAge = function(age) { var minimum = 21; return age >= minimum; };
Pure functions have no side effects, some things you need to remember is that it does not:
Access system state outside the function
Modify objects passed as parameters
Initiate http requests
Preserve user input
Querying the DOM
Controlled mutation
You need to pay attention to some mutation methods that will change arrays and objects. For example, you need to know the difference between splice and slice.
//impure, splice 改變了原數(shù)組 var firstThree = function(arr) { return arr.splice(0,3); } //pure, slice 返回了一個(gè)新數(shù)組 var firstThree = function(arr) { return arr.slice(0,3); }
If we avoid using mutator methods on the objects passed into the function, our programs will be easier to understand, and we can reasonably expect that our functions will not change anything outside the function.
let items = ['a', 'b', 'c']; let newItems = pure(items); //對(duì)于純函數(shù)items始終應(yīng)該是['a', 'b', 'c']
Advantages of pure functions
Compared to impure functions, pure functions have the following advantages:
Easier to test, because their only responsibility is to calculate output based on input
The results can be cached, because the same Input will always get the same output
Self-documenting because the dependencies of the function are clear
Easier to call because you don’t have to worry about the side effects of the function
Because the results of pure functions can be cached, we can remember Keep them in place so that complex and expensive operations only need to be performed once when called. For example, caching the results of a large query index can greatly improve program performance.
Unreasonable pure function programming
Using pure functions can greatly reduce the complexity of the program. However, if we use too many abstract concepts of functional programming, our functional programming will also be very difficult to understand.
import _ from 'ramda'; import $ from 'jquery'; var Impure = { getJSON: _.curry(function(callback, url) { $.getJSON(url, callback); }), setHtml: _.curry(function(sel, html) { $(sel).html(html); }) }; var img = function (url) { return $('<img />', { src: url }); }; var url = function (t) { return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; }; var mediaUrl = _.compose(_.prop('m'), _.prop('media')); var mediaToImg = _.compose(img, mediaUrl); var images = _.compose(_.map(mediaToImg), _.prop('items')); var renderImages = _.compose(Impure.setHtml("body"), images); var app = _.compose(Impure.getJSON(renderImages), url); app("cats");
Take a minute to understand the above code.
Unless you are exposed to these concepts of functional programming (currying, composition and props), it will be difficult to understand the above code. Compared with the purely functional approach, the following code is easier to understand and modify, it describes the program more clearly and requires less code.
The parameter of the app function is a tag string
Get JSON data from Flickr
Extract urls from the returned data
Create node array
Insert them into the document
var app = (tags) => { let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?`; $.getJSON(url, (data) => { let urls = data.items.map((item) => item.media.m) let images = urls.map(url) => $('<img />', {src:url}) ); $(document.body).html(images); }) } app("cats");
Or you can use fetch and Promise to better perform asynchronous operations.
let flickr = (tags)=> { let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?` return fetch(url) .then((resp)=> resp.json()) .then((data)=> { let urls = data.items.map((item)=> item.media.m ) let images = urls.map((url)=> $('<img />', { src: url }) ) return images }) } flickr("cats").then((images)=> { $(document.body).html(images) })
Ajax requests and DOM operations are not pure, but we can form the remaining operations into pure functions and convert the returned JSON data into an array of image nodes.
let responseToImages = (resp) => { let urls = resp.items.map((item) => item.media.m) let images = urls.map((url) => $('<img />', {src:url})) return images }
Our function does 2 things:
Convert the returned data into urls
Convert urls into image nodes
The functional approach is to split the above 2 tasks, and then use compose to combine a function The result is passed as a parameter to another parameter.
let urls = (data) => { return data.items.map((item) => item.media.m) } let images = (urls) => { return urls.map((url) => $('<img />', {src: url})) } let responseToImages = _.compose(images, urls)
compose returns a combination of functions, each function will use the result of the latter function as its own input parameter
What compose does here is to pass the result of urls into the images function
let responseToImages = (data) => { return images(urls(data)) }
By changing the code into Making them pure functions gives us the opportunity to reuse them in the future, and they are easier to test and self-documenting. The bad thing is that when we overuse these functional abstractions (like in the first example), it complicates things, which is not what we want. The most important thing to ask yourself when we refactor code is:
Does this make the code easier to read and understand?
基本功能函數(shù)
我并不是要詆毀函數(shù)式編程。每個(gè)程序員都應(yīng)該齊心協(xié)力去學(xué)習(xí)基礎(chǔ)函數(shù),這些函數(shù)讓你在編程過(guò)程中使用一些抽象出的一般模式,寫(xiě)出更加簡(jiǎn)潔明了的代碼,或者像Marijn Haverbeke說(shuō)的
一個(gè)程序員能夠用常規(guī)的基礎(chǔ)函數(shù)武裝自己,更重要的是知道如何使用它們,要比那些苦思冥想的人高效的多。--Eloquent JavaScript, Marijn Haverbeke
這里列出了一些JavaScript開(kāi)發(fā)者應(yīng)該掌握的基礎(chǔ)函數(shù)
Arrays
-forEach
-map
-filter
-reduce
Functions
-debounce
-compose
-partial
-curry
Less is More
讓我們來(lái)通過(guò)實(shí)踐看一下函數(shù)式編程能如何改善下面的代碼
let items = ['a', 'b', 'c']; let upperCaseItems = () => { let arr = []; for (let i=0, ii= items.length; i<ii; i++) { let item = items[i]; arr.push(item.toUpperCase()); } items = arr; }
共享狀態(tài)來(lái)簡(jiǎn)化函數(shù)
這看起來(lái)很明顯且微不足道,但是我還是讓函數(shù)訪問(wèn)和修改了外部的狀態(tài),這讓函數(shù)難以測(cè)試且容易出錯(cuò)。
//pure let upperCaseItems = (items) => { let arr = []; for (let i =0, ii= items.length; i< ii; i++) { let item = items[i]; arr.push(item.toUpperCase()); } return arr; }
使用更加可讀的語(yǔ)言抽象forEach來(lái)迭代
let upperCaseItems = (items) => { let arr = []; items.forEach((item) => { arr.push(item.toUpperCase()); }) return arr; }
使用map進(jìn)一步簡(jiǎn)化代碼
let upperCaseItems = (items) => { return items.map((item) => item.toUpperCase()) }
進(jìn)一步簡(jiǎn)化代碼
let upperCase = (item) => item.toUpperCase() let upperCaseItems = (item) => items.map(upperCase)
刪除代碼直到它不能工作
我們不需要為這種簡(jiǎn)單的任務(wù)編寫(xiě)函數(shù),語(yǔ)言本身就提供了足夠的抽象來(lái)完成功能
let items = ['a', 'b', 'c'] let upperCaseItems = item.map((item) => item.toUpperCase())
測(cè)試
純函數(shù)的一個(gè)關(guān)鍵優(yōu)點(diǎn)是易于測(cè)試,所以在這一節(jié)我會(huì)為我們之前的Flicker模塊編寫(xiě)測(cè)試。
我們會(huì)使用Mocha來(lái)運(yùn)行測(cè)試,使用Babel來(lái)編譯ES6代碼。
mkdir test-harness cd test-harness npm init -y npm install mocha babel-register babel-preset-es2015 --save-dev echo '{ "presets": ["es2015"] }' > .babelrc mkdir test touch test/example.js
Mocha提供了一些好用的函數(shù)如describe和it來(lái)拆分測(cè)試和鉤子(例如before和after這種用來(lái)組裝和拆分任務(wù)的鉤子)。assert是用來(lái)進(jìn)行相等測(cè)試的斷言庫(kù),assert和assert.deepEqual是很有用且值得注意的函數(shù)。
讓我們來(lái)編寫(xiě)第一個(gè)測(cè)試test/example.js
import assert from 'assert'; describe('Math', () => { describe('.floor', () => { it('rounds down to the nearest whole number', () => { let value = Math.floor(4.24) assert(value === 4) }) }) })
打開(kāi)package.json文件,將"test"腳本修改如下
mocha --compilers js:babel-register --recursive
然后你就可以在命令行運(yùn)行npm test
Math .floor ? rounds down to the nearest whole number 1 passing (32ms)
Note:如果你想讓mocha監(jiān)視改變,并且自動(dòng)運(yùn)行測(cè)試,可以在上述命令后面加上-w選項(xiàng)。
mocha --compilers js:babel-register --recursive -w
測(cè)試我們的Flicker模塊
我們的模塊文件是lib/flickr.js
import $ from 'jquery'; import { compose } from 'underscore'; let urls = (data) => { return data.items.map((item) => item.media.m) } let images = (urls) => { return urls.map((url) => $('<img />', {src: url})[0] ) } let responseToImages = compose(images, urls) let flickr = (tags) => { let url = `http://api.flickr.com/services/feeds/photos_public.gne?tags=${tags}&format=json&jsoncallback=?` return fetch(url) .then((response) => reponse.json()) .then(responseToImages) } export default { _responseToImages: responseToImages, flickr: flickr }
我們的模塊暴露了2個(gè)方法:一個(gè)公有flickr和一個(gè)私有函數(shù)_responseToImages,這樣就可以獨(dú)立的測(cè)試他們。
我們使用了一組依賴:jquery,underscore和polyfill函數(shù)fetch和Promise。為了測(cè)試他們,我們使用jsdom來(lái)模擬DOM對(duì)象window和document,使用sinon包來(lái)測(cè)試fetch api。
npm install jquery underscore whatwg-fetch es6-promise jsdom sinon --save-dev touch test/_setup.js
打開(kāi)test/_setup.js,使用全局對(duì)象來(lái)配置jsdom
global.document = require('jsdom').jsdom('<html></html>'); global.window = document.defaultView; global.$ = require('jquery')(window); global.fetch = require('whatwg-fetch').fetch;
我們的測(cè)試代碼在test/flickr.js,我們將為函數(shù)的輸出設(shè)置斷言。我們"stub"或者覆蓋全局的fetch方法,來(lái)阻斷和模擬HTTP請(qǐng)求,這樣我們就可以在不直接訪問(wèn)Flickr api的情況下運(yùn)行我們的測(cè)試。
import assert from 'assert'; import Flickr from '../lib/flickr'; import sinon from 'sinon'; import { Promise } from 'es6-promise'; import { Response } from 'whatwg-fetch'; let sampleResponse = { items: [{ media: { m: 'lolcat.jpg' } }, { media: {m: 'dancing_pug.gif'} }] } //實(shí)際項(xiàng)目中我們會(huì)將這個(gè)test helper移到一個(gè)模塊里 let jsonResponse = (obj) => { let json = JSON.stringify(obj); var response = new Response(json, { status: 200, headers: {'Content-type': 'application/json'} }); return Promise.resolve(response); } describe('Flickr', () => { describe('._responseToImages', () => { it("maps response JSON to a NodeList of <img>", () => { let images = Flickr._responseToImages(sampleResponse); assert(images.length === 2); assert(images[0].nodeName === 'IMG'); assert(images[0].src === 'lolcat.jpg'); }) }) describe('.flickr', () => { //截?cái)鄁etch 請(qǐng)求,返回一個(gè)Promise對(duì)象 before(() => { sinon.stub(global, 'fetch', (url) => { return jsonResponse(sampleResponse) }) }) after(() => { global.fetch.restore(); }) it("returns a Promise that resolve with a NodeList of <img>", (done) => { Flickr.flickr('cats').then((images) => { assert(images.length === 2); assert(images[1].nodeName === 'IMG'); assert(images[1].src === 'dancing_pug.gif'); done(); }) }) }) })
運(yùn)行npm test,會(huì)得到如下結(jié)果:
Math .floor ? rounds down to the nearest whole number Flickr ._responseToImages ? maps response JSON to a NodeList of <img> .flickr ? returns a Promise that resolves with a NodeList of <img> 3 passing (67ms)
到這里,我們已經(jīng)成功的測(cè)試了我們的模塊以及組成它的函數(shù),學(xué)習(xí)到了純函數(shù)以及如何使用函數(shù)組合。我們知道了純函數(shù)與不純函數(shù)的區(qū)別,知道純函數(shù)更可讀,由小函數(shù)組成,更容易測(cè)試。相比于不太合理的純函數(shù)式編程,我們的代碼更加可讀、理解和修改,這也是我們重構(gòu)代碼的目的。

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)
