Intro
mofun.js is special because it's just a collection of stand-alone functions. Most of the functions use this to avoid hard-coding a one-off anonymous function for a simple sub-task. Think of this in JS as a function argument that gets called in a different way than the formal parameters (since that's exactly what this is). Using this to send one extra customization to any of the hundreds of functions makes the generic functions become viable in a whole lot of places that used to require customized anonymous functions. In short, the literal "function" will appear a lot less in code using mofun.
mofun.js is not intended to be a functional framework like Prototype.js, Oliver Steele's functional, Underscore.js, or jQuery. To use most of the functions in mofun.js, you'll need to pass them to existing higher-order functions like vanilla's [].map() and [].filter(), or to an existing framework method like _.map(), or even $.map(). However, mofun does define several higher-order functions including faster version of map, reduce, filter, as well as functional helpers like (r)Partial, bind, guard, and more. Most of the included functions are pure functions, which execute quickly, have no side-effects or dependencies, and are inherently testable. None of mofun's methods have any internal dependencies. Cut and paste them ad-hoc from the listing below if you don't want/need them all. Since it's all based on pure vanilla, you can rip the library apart and like the T-1000, each little piece survives and works independently.
mofun.js compliments shortcomings in other-wise great libraries like Prototype.js and Underscore by providing common and highly-recyclable low-level helpers that eliminate many of the specialized anonymous functions typical of functional javascript programming. Those libraries provide a framework to build, mofun.js provides pre-fabricated modules to attach to that framework. These days, you don't really need underscore or prototype to underpin the handy functional style; it's built into any browser since IE9, and IE8 can be easily polyfilled to support mofun. If vanilla is too plain, mofun.js plays well with virtually any framework ex:_.reduce([1,2,3], F.sum)===6. Run fast, pack light, don't pollute, and never ever repeat yourself: mofun.js.
Download
You can peruse the official source on GitHub, examine the current source view, downloaded, drag this bookmarklet (+mofun) to your bookmarks bar to use mofun on any page you visit, or you can make yourself a compressed copy using:see also: Compiler
Examples
These aren't the most exciting, but keep in mind none of the examples use any hand-written functions, only a few use vars, and you can see every bit of code that runs by hovering over non-native function calls. The idea is that anything here that's related to what you might have used an anon (or even a loop) for in the past can now be written more declarative and re-usable.
- Show a Deck of Playing Cards
- [].map.call( "♦♥♣♠", // take every suit [].map.bind("234567890JQKA", F.add) // add it to every rank ).join("\n") // show each suit on a row
- Merge an array of arrays into a flat array
- [[1,2], [3,4], [5,6]].reduce(F.concat)
- Count letters in a string
- "Hello World Wide Web!".match(/\w/g).reduce(F.count)
- Group an array of strings by length
- F.groupBy( F.$w("now is the time for all good men"), "length")
- Sort an array of strings by length, then by value
- F.sortBy( F.$c("now,is,the,time,for,all,good,men").sort(), "length")
- Make Dates Prettier
- // (javascript dates, not your friday evening) [ new Date(), new Date(2000,0,1), new Date(2001,8,11,9) ] .map(F.dateParts) // extract date info .map(F.template, "date: {{3}}.{{2}}.{{1}} ({{0}}) @{{4}}:{{5}}") // view date info
- Render many objects with one template
- [{a:1, b:2}, {a:11, b:22}, {a:111, b:222}] .map(F.template, "a: {{a}}")
- Flip arguments to render many templates with one object
- ['a: {{a}}', '{{a}}/{{b}}', 'b is {{b}}' ] .map( F.flip(F.template), {a:1, b:2} )
- Lookup customer city by id #
- var customers=[ {id:3, name:"Jeff", last:"Stevenson", city:56324}, {id:2, name:"Kelly", last:"Ross", city:90210}, {id:1, name:"John", last:"Smith", city:23522} ]; F.xVLOOKUP( // use excel-like VLOOKUP to 2, // look for this value customers, // in this collection "id", // under this key "city" // and if matching, return this key's value )
- Lookup customers by city using a LUT
- var customers=[ {id:3, name:"Jeff", last:"Stevenson", city:56324}, {id:2, name:"Kelly", last:"Ross", city:90210}, {id:1, name:"John", last:"Smith", city:56324} ], lut= F.lutKey(customers, "city", true); // builds a table to lookup by city lut[56324] // find all customers in specified city without looping
- Get the HEX value of numbers in an array
- [10,20,30,40,50].map(F.method, ["toString", 16])
- Get the OCT value of numbers in an array
- [5,20,30,40,50] // take a list of base 10 numbers and .map( // on each number, F.arg( // freeze the octal radix using F.arg so we can F.call(Number.prototype.toString), // re-use existing toString() method 1, // while freezing the 2nd argument (starting from 0) 8 // with a value of 8 before we ) // create the custom toString method and ); // apply it to each number in the list
- Get the ASCII Codes of a string
- F._("ABCDEFG").map(F.char)
- Chart a few numbers using 'ASCII art'
- [1,2,7,4,9].map( F.partial( F.repeat, "|") )
- Square the result of adding 2 numbers
- var addThenSquare = F.compose.call(F.square, F.sum); addThenSquare(2, 3); // math version: (2+3)^2
- Filtering via property of element
- F.$("section") .map(F.pluck, "textContent") // grab text of all sections .filter( F.subMethod( F.gt, "length"), 3000 ) // filter out those under 3000 chars .map(F.left, 40) // summarize
- Filtering via sub-property compare
- F.$("section") // does same thing as previous example but differently .map(F.extract, "textContent") // grab text of all sections .filter( F.propIsMore, ["length", 3000] ) // filter out those under 3000 chars .map(F.substr, [0, 40]) // summarize
- Make a list of the first ten numbers
- F.range(10)
- Make a range of 10 random numbers 0-1
- F.range(10).map(F.random)
- Make a range of 10 random pretty numbers
- F.range(10) // array from 0 -9 .map(F.random) // filled with numbers from 0 - 1 .map(F.thisOverA, 10000) // transform to 10000 / n .map(F.formatNumber, 2) // format to 2 decimal places
- Sort Numbers
- [4523, 99, -23, 12, 0, 5 ].sort(F.sortNumber)
- Sort Numerical Integers
- ["5px", "2px", "21px", "0px"].sort(F.sortInt)
- Benchmark array iteration
- F.populate( Array(6), 0) // fill a 6-slot array with zeros .map(F.timer) // fill array with hires timestamps .map(F.delta) // calc difference between elements .map(F.abs) // convert to positive number .map(F.add, "ms") // indicate units
- Use Simple argument pickers in map()
- var r=[1,2,3];[ r.map(F.k), // 1st argument r.map(F.a), // 1st argument r.map(F.b), // 2nd argument r.map(F.c) // 3rd argument ].join("\n")
- Make a range from 100-109
- F.range(10).map(F.addNumber, 100)
- Get the even numbers from 1-10
- [1,2,3,4,5,6,7,8,9,10].filter(F.even)
- Get the numbers from 1-10 that are less than 5 (_ style)
- F.filter([1,2,3,4,5,6,7,8,9,10], F.lt, 5)
- Get a randomized uniform list of numbers from 1-10
- [1,2,3,4,5,6,7,8,9,10].map(F.shuffle)
- Choose colors based on even-ness
- [1,2,3,4,5].map(F.If( F.even, "red", "black" ))
- Take Array elements until triggered
- F.takeWhile( [1,2,3,7,4,2], F.lte, 5 )
- Skip Array elements until triggered
- F.skipWhile( [1,2,3,7,4,2], F.lte, 5 )
- Remove gaps in an array
- var arr=new Array(7); arr[1]=1; arr[3]=2; arr[4]=3; arr.filter(F.compact)
- Show the tag name of the first four tags in this document
- F.slice.call(4, F.$("*").map(F.pluck, "tagName")) .map(F.lcase)
- Find the sum of numbers in an array
- [1,2,3,4,5].reduce(F.sum)
- Generate 10 random integers from 0 to 100
- F.range(10).map(F.random).map(F.times, 100).map(F.floor)
- Find instances of letter in a string
- F.chars("Hello World").filter(F.matches, "l")
- Find elements in an array by simple criterion
- var r=[1,2,3,4,5]; 0||{ firstGreaterThan3: F.find(r, F.gt, 3), indexOfGreaterThan3: F.findIndex(r, F.gt, 3), indicesOfGreaterThan3: F.findIndices(r, F.gt, 3), allGreaterThan3: F.all(r, F.gt, 3), anyGreaterThan3: F.any(r, F.gt, 3), anyGreaterThan5: F.any(r, F.gt, 5), everyGreaterThan0: F.every(r, F.gt, 0), someGreaterThan0: F.some(r, F.gt, 0) }
- Remove some letters (vowels) from a string
- [].filter.call("Hello World", F.without, "aeiou").join("")
- Remove a letter from a string
- [].filter.call("Hello World", F.unequiv, "l").join("")
- Exclude some letters (case-insensitive vowels) in a string using regexp
- [].filter.call("Hello World", F.negate(F.regexp) , /[aeiou]/i)
- Exclude odd positions in a string using regexp
- F.delMatch.call( /[13579]$/, F.chars("Hello World") ).join("")
- Select odd positions in a string using regexp
- F.selMatch.call( /[13579]$/, F.chars("Hello World") ).join("")
- Find odd integers from 1-20 using regexp
- F.range(20).filter(F.regexp, /[13579]$/)
- Make un-ordered list html from an array
- F.tag.call("ul", [1,2,3].map(F.tag, "li").join("\n"))
- Trim each element of an array using native trim method
- F.$c(" a, b , c").map(F.invoke, "trim")
- Trim each element of an array using F.trim
- " a, b , c".split(",").map(F.trim)
- Chart a few numbers w/legend
- [1,2,7,4,9] .map(F.guard(Array,1)) // get array of length==element .map(F.arg([].join, 1, "|", true)) // join array using "|" .map(F.replace, [/^/, " "]) // add some space .map(F.prepend, F.property("length")) // add an axis label .map(F.append, "|") // add one more bar to each
- Get an array of properties from a single object
- ["tagName", "childElementCount", "className" ] .map( F.extractThis, document.body )
- Get an array of objects of properties from the links in the nav
- F.$(".nav .item a") .map(F.extractObject, ["tagName", "textContent", "href" ])
- Get an array of arrays of properties from the links in the nav
- F.$(".nav .item a") .map(F.extractList, ["tagName", "textContent", "href" ])
- Grab several attributes from the nav
- F.first( F.$("#nav") .map(F.methods, ["getAttribute", ["id","class","target"]]))
- Grab several base versions of a single Number
- F.methods.call(["toString", [2,10,16]], 123 )
- Grab objects of properties of tags in the document head
- F.$("head>*:not(style)").map(F.pick, ["tagName", "textContent", "href" ])
- Count of enumerable own properties on window
- F.keys(window).length
- Count of enumerable own and inherited properties on window
- F.obKeysx(window).length
- Count of own properties on window
- F.properties(window).length
- Count of all properties on window
- F.allProps(window).length
- All used html elements without any child elements
- F.$("*") // grab all elements in the document .filter( F.negate( F.pluck ), "childElementCount" ) // filter out elements w/o children .map(F.extract, "tagName") // grab tag name .filter(F.unique) // drop duplicates .sort(F.sortNoCase); // sort text without regard to capitalization
- Getting un-escaped text from a JavaScript function
- F.getComments(function(){ // note raw quotes and line breaks are cool in comments: /* body .modal-header { font-family:"helvetica new", verdana, tahoma, arial; background: #333; color:#ddd; } */ })
- change object property on timer
- var titler=F.defer.bind(this, F.assign(document, "title")); F.range(24) // 0..11 .map(F.times, 333) // becomes 0,333,..(333*12) .map(titler); // sets the title to the index when it fires titler(10e3, document.title); // reset title in 10s "(watch the window title and close this box)"
- find things equivalent to one
- [1, 0, "1", -1, /1/, [1], true].filter(F.equiv, 1)
- encode a url param
- F.encode("does right=wrong ?")
- length of text in this page's sections
- F.$("section").map(F.resolve, ["textContent", "length", "toLocaleString"])
- Specializing and map()-izing functions
- function isDivisibleBy(a, b) { return a % b === 0; }; var isEven = F.guard( // guard blocks extra call-time arguments F.rPartial(isDivisibleBy, 2) // rPartial locks in 2 as the denominator (b) , 1 ); // guard all but the first [].map argument (a) [1,2,3,4,5].map(isEven) // see it in action
- Rewrite function output
- [1,2,3,4,5,6,7,8,9].map( F.finnally(F.even, {false:"no", true:"yes"}) );
- Clone an Object
- var a={a:1, b:2}, // an object b=F.clone(a); // a dupe of the object b.c=3; // modify the dupe [a, b] // view them both
- Sort Object Keys
- F.sortObjectKeys({ z:1, a:2, n:5, t:3, c:2}) // note: use to view or pre-sort, but don't rely on key order for code operation
- Avoid repeted calls to indexOf()
- var data="abcdefg"; // strings work as well as arrays lut=F.lut(data); // this object is keyed by char and valued by index F.map("acf", F.extractThis, lut);// quickly finds pos of some values
- Merge Object values
- F.extend({c:3}, {a:1, b:2}, {d:4});
- show source of string made after decrementing array
- F.pretty([0,1,2].map(F.dec).join("|"))
- stop an error from breaking the whole stack
- F.catch(F.f("self.madeUpName()")) // causes error .toString().bold() // prove stuff is still happening
- Create a TOC of downloadable mofun methods
- F.obMap(F) // grab [key,value] pairs from F's properties .filter( F.negate( F.bind(/./.test, /native code/)) ) // filter out native methods .map(F.modifyProperty, [1, F.dataURL.bind("text/plain")]) // turn value into a dataURL .map(F.template, "<a target=_blank download='{{0}}.js' href='{{1}}'>{{0}}") .join("\n")
- Get a blank promise for chaining async actions
- F.later() .then(F.randomString) .then( F.bind( F.invokeSelf, "big")) .then(alert2);
- use assert to run unit tests
- F.k({// Label Test Message (optional) okValue: F.assert( [1,3,5].filter(F.odd).length===3 , "odd filter1 broke"), // ok, direct value okString: F.assert( [1,3,5].filter(F.odd).length===3 , "odd filter2 broke"), // ok, string test noMsg1: F.assert( [1,2,3].filter(F.odd).length===3 , "odd filter3 broke"), // should fail w/message noMesg2: F.assert("[1,2,3].filter(F.odd).length===3", "odd filter4 broke"), // should fail w/message noFalse: F.assert( [1,2,3].filter(F.odd).length===3 ), // should fail w/false noError: F.assert("[1,2,3].filter(Z.odd).length===3" ), // should fail w/Error message noString: F.assert("[1,2,3].filter(F.odd).length===3" ) // should fail w/code })
- find numbers using several criteria
- [1,2,3,4,5,6,7,8,9,10] .filter(F.or, [ F.lt.bind(3), // less than 3 F.gte.bind(8), // greater than or equiv to 8 5 // or simply equal to 5 ]);
- Find the largest numerical value in array
- [1,2,99,3,4,5].reduce(F.largest)
- Find the last alphabetical word in a sentence
- F.split.call(" ", "hello joe what do you know?").reduce(F.largest)
- Find the first alphabetical word in a sentence
- F.reduce(F.$w("hello joe what do you know?"), F.smallest)
- Make HTML safe for Display
- F.escapeHTML( new Option("jello is good").outerHTML )
- Remove HTML for Display
- F.stripTags( new Option("jello is good").outerHTML )
- Fill an array with values
- F.range(5).fill("hello")
- Run a function against an array
- var r= [1,2,3]; F.forEach(r, function(a){a=a*=10;} ); F.pretty(r);
- Make an array of random booleans
- [1,2,3,4,5,6,7].map(F.random).map(F.round).map(F.isTrue)
- Make an array of small random positive floats
- [1,2,3,4,5,6,7].map(F.random).map(F.divide, 1000)
- Make an array of random numbers from 0 - 100
- [1,2,3,4,5,6,7].map(F.random).map(F.multiply, 1000)
- Remove properties from an array of objects
- [{a:1,b:2,c:3,d:4,e:5}, {a:11,b:22}, {b:1,c:3,d:5}] .map(F.censor, ["a","c"])
- Find sequentially repeated numbers
- F.filter([1,2,2,3,3,4,5,7,7,9], F.repeated)
- Exclude sequentially repeated numbers
- F.filter([1,2,2,3,3,4,5,7,7,9], F.changed)
- Redact text info
- F.redact("Now is the time, for [all good] men...");
- Cleanup text info
- F.clean(" \t Now is the time, for [all good] men ");
- Reverse and quote text
- F.quote( F.reverse("Now is the time, for [all good] men..."));
- Replace all instances of a string with another string (no /regexp/g required)
- F.replaceAll("Hello World", "l", "L");
- Find stats about an array of numbers
- var r=[1,2,2,3,3,4,5,7,7,9]; 0||{ mode: F.mode(r), median: F.median(r), avg: F.reduce(r, F.avg), max: F.max(r), min: F.min(r) }
- Zip two arrays together
- a=[1,2,3]; b=[5,3,1]; a.map(F.zip, b);
- Find the intersect of two arrays
- a=[1,2,3]; b=[5,3,1]; a.filter(F.intersect, b);
- Find the union of two arrays
- a=[1,2,3]; b=[5,3,1]; F.union.call(a, b);
- Spy on a higher-order function
- [60, 90].reduce(F.arguments, 21)
- Grab a specific argument
- [11,22,33].map(F.argument, 1) // grab 2nd arg
- Intercept intermediate chain values
- var r=[], r2=[10,26,37] .map(F.subtract, 1) // remove 1 from each number .map(F.tee, F.unmethod(r,"push")) // copy results thus far .map(F.sqrt); // get square root [r2, r] // view output and intercept
- Find elements the same as a value
- var r=[1,2,3,4,"3","2","1"]; [ r.filter(F.sameAs(3)), // faster performance r.filter(F.is.bind(null, 1)), // strict types, native function r.filter(F.equal, 2), // simpler usage, strict r.filter(F.same, 2) // simpler usage ].join("\n")
- Map an object instead of an array
- F.visit({a:1,b:2,c:3}, F.capture);
- Ignore 2nd and 3rd params when using [].map
- ["hi bob","a>b","a=b"].map(F.run, escape) // normally the index arg breaks escape()
- Apply many functions to an array
- F.define("jTab", ["join","\t"]); // define a new shortcut helper var ns=[-1, 0, 1, "0", "1", null ], // data to compare with these methods: methods=[F.isNeg, F.isPos, F.isNumber, F.isNumerical, F.isBoolean, F.isSet, F.isFalse, F.isInt, F.isNaN, F.isNull, F.ok, F.isEmpty, F.isDefined, F.isUndefined ], names=methods.map(F.getKey, F) // get names of above methods .map(F.append, F.repeat(" ", 5)) // add padding space onto end .map(F.method, ["slice",0,11]), // keep a consistent text width results=methods.map([].map, ns) // run each method on each datum .map(F.method, F.jTab ); // turn into tab-separated line // format results into text table: " \t \t"+ns.join("\t")+"null\n"+// build header as tab-separated names.map(F.zip, results ) // prepend method names onto result rows .map(F.method, F.jTab) // put tab between name and results .join("\n") // put lines together
- Generate IDs
- [ F.uniqueId(), F.uniqueId(), F.uuid(), F.randomString(16), F.uniqueId(), ]
- Determining Object Possession
- [ F.thisAt.call({a:1}, "a"), F.thisAt.call("a", "toString"), F.hasOwn.call("a", {a:1}), F.hasOwn.call("toString", "a"), "______________", F.hasOwnThis.call({a:1}, "a"), F.hasOwnThis.call({a:1}, "toString"), F.has.call("a", {a:1}), F.has.call("toString", "a") ]
- Modify Poperties
- [{a:4}, {c:2}, {a:1}, {c:6}, {a:5,c:7}] // an array of simple objects .map(F.addProperty, ["b", "ello"]) // add a new b prop, set to "ello" .map(F.appendProperty, ["b", " world"])// append " world" onto the new prop .map(F.prependProperty, ["b", "h"]) // prepend "ello" with an "h" .map(F.defaults, {a: 0 }) // make sure each object has an "a" property .map(F.delProperty, "c") // remove the sporadic "c" property (if defined) .map(F.addProperties, {d: 3}) // add a "d" property .filter(F.propIsLess, ["a", 5]) // filter out any object with an "a" property more than 4.9999 .sort(F.bind( F.sortProperty, "a" )) // sort remaining list by "a" property
- Handle Dates
- [ Date(), new Date(), F.date( F.now()), F.date([2000,0,1,5,44]) ] .filter(F.isDate) // filters out non date objects .sort(F.sortDate) // sort dates past to future .map(F.time) // pluck the time part out
Methods
You can highlight and copy several working JavaScript functions at once from the listing below.-
{{#items}}
-
//{{key}} - {{#exp}} (expects: {{exp}}) {{/exp}} {{#isBeta}}[ BETA ]{{/isBeta}} {{#native}}[ native ]{{/native}} {{#isMap}}[ map/filter ]{{/isMap}} {{#isReduce}}[ reduce ]{{/isReduce}} {{#isMono}}[ unary ]{{/isMono}} {{#isThis}}[ this ]{{/isThis}} {{#isConst}}[ special ]{{/isConst}} {{#isVari}}[ variadic ]{{/isVari}} {{#isStrict}}[ strict ]{{/isStrict}} {{#isDynamic}}[ dynamic ]{{/isDynamic}} //{{desc}}
{{src}}
//
{{/items}}
Performance
Mofun's own iteration methods are very fast.
Compare the performance of mofun to underscore, lodash, and vanilla :