Writing High‑Quality JavaScript Functions with Functional Programming Techniques
The article demonstrates how to write high‑quality JavaScript functions by applying functional‑programming concepts such as composition, higher‑order functions, immutability, declarative array methods, reusable utilities like a curried tap, and memoization, showing refactored examples that replace hard‑coded DOM manipulation and imperative loops with clean, reusable code.
This article is part of a series on writing high‑quality functions in JavaScript. It links to three previous articles that cover function execution mechanisms, naming, comments, robustness, and a theoretical introduction to functional programming.
The main body demonstrates how to apply functional‑programming ideas to refactor a simple demo, showing the benefits of composition, higher‑order functions, and immutability.
1. Variable type and scope
Example of hard‑coded DOM manipulation:
document.querySelector('#msg').innerHTML = '
Hello World'
'The author points out the problems: hard‑coding, poor reusability, and difficulty extending the logic.
Refactored version using composition:
// 使用到了组合函数,运用了函数的高阶性等
const compose = (...fns) => value => fns.reverse().reduce((acc, fn) => fn(acc), value)
const documentWrite = document.write.bind(document)
const createNode = function(text) { return '
' + text + '
' }
const setText = msg => msg
const printMessage = compose(
documentWrite,
createNode,
setText
)
printMessage('hi~ godkun')The result is shown in an image (omitted here).
2. Reference types – immutability
Demonstrates that mutating an array passed by reference causes side effects:
let arr = [1,3,2,4,5]
function fun(arr) {
let result = arr.sort()
console.log('result', result)
console.log('arr', arr)
}
fun(arr)Solution: create a shallow copy before sorting:
let arr = [1,3,2,4,5]
function fun(arr) {
let arrNew = arr.slice()
let result = arrNew.sort()
console.log('result', result)
console.log('arr', arr)
}
fun(arr)3. Reducing imperative constructs
The article lists common pitfalls such as excessive for loops, many if/else branches, and suggests replacing them with declarative array methods (map, filter, reduce) or functional abstractions.
4. Practical work – building a reusable tap utility
First version (debug‑only):
/** * 多功能函数 * @param {Mixed} value 传入的数据 * @param {Function} predicate 谓词,用来进行断言 * @param {Mixed} tip 默认值是 value */
function tap(value, predicate, tip = value) {
if (predicate(value)) {
log('log', `{type: ${typeof value}, value: ${value} }`, `额外信息:${tip}`)
}
}
const is = {
undef: v => v === null || v === undefined,
notUndef: v => v !== null && v !== undefined,
noString: f => typeof f !== 'string',
noFunc: f => typeof f !== 'function',
noNumber: n => typeof n !== 'number',
noArray: !Array.isArray,
};
function log(level, message, tip) {
console[level].call(console, message, tip)
}
const res1 = {data: {age: '', name: 'godkun'}}
const res2 = {data: {age: 66, name: 'godkun'}}
tap(res1.data.age, is.noNumber)
tap(res2.data.age, is.noNumber)Second version uses Ramda’s currying to create two variants: one that only logs, another that throws on error.
const R = require('ramda')
const tapThrow = R.curry(_tap)('throw', 'log')
const tapLog = R.curry(_tap)(null, 'log')
function _tap(stop, level, value, predicate, error = value) {
if (predicate(value)) {
if (stop === 'throw') {
log(`${level}`, 'uncaught at check', error)
throw new Error(error)
}
log(`${level}`, `{type: ${typeof value}, value: ${value} }`, `额外信息:${error}`)
}
}
function log(level, message, error) {
console[level].call(console, message, error)
}
const is = {
undef: v => v === null || v === undefined,
notUndef: v => v !== null && v !== undefined,
noString: f => typeof f !== 'string',
noFunc: f => typeof f !== 'function',
noNumber: n => typeof n !== 'number',
noArray: !Array.isArray,
};
const res = {data: {age: '66', name: 'godkun'}}
function main() {
// tapLog(res.data.age, is.noNumber)
tapThrow(res.data.age, is.noNumber)
console.log('能不能走到这')
}
main()Running the above shows that tapThrow stops execution when the type check fails.
5. Removing for loops
The author explains why loops are hard to reuse and advocates using higher‑order array methods (map, filter, reduce) to hide the imperative control flow.
6. Caching with pure functions
Shows a simple memoization pattern and extends Function.prototype with memorized and memorize helpers:
Function.prototype.memorized = () => {
let key = JSON.stringify(arguments)
this._cache = this._cache || {}
this._cache[key] = this._cache[key] || this.apply(this, arguments)
return this._cache[key]
}
Function.prototype.memorize = () => {
let fn = this
if (fn.length === 0 || fn.length > 1) return fn
return () => fn.memorized.apply(fn, arguments)
}References to Ramda source code, GitHub gists, and CodePen examples are provided throughout.
The article concludes with a reminder to read the theoretical part together with the practical part, and lists several reference links and books on functional programming, monads, and related topics.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.