Fundamentals 22 min read

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.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Writing High‑Quality JavaScript Functions with Functional Programming Techniques

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.

JavaScriptcode refactoringbest practicesfunctional programmingHigher-order FunctionsCurrying
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.