JS tips and tricks

4 minute read

Named functions

Named JS functions as callback or predicates make code more readable and stack trace more clear:

// I know, this case is too trivial but it reveals the main idea 
ajax((data) => template.bind(data));
// vs
ajax(function bindToTemplate(data) {
  template.bind();
});

Function parameters aggregation

Destructive/spread operators make arguments passing more declarative and do not bring any complexity

function foo({ x, y } = {}) {
  ...
};
function foo([ x, y ] = []) {
  ...
};
function foo(x, y, ...args) {
  ...
};

Named arguments/parameters

In case a function has a lot of arguments or, for example, most of them are optional, making a function unary and passing a single object is an option for such cases.

 function foo({ x, y, z }) {
  ...
 };
 
 foo({ y: 'y' });

Constants

const identifier does not make a variable constant, it just tells that it can not be reassiggned, but a variable mutation is allowed.

const a = { b: 1 };
a.b = 2;
console.log(a.b); // 2

And Object.freeze(..) can help you in this approach:

const a = Object.freeze({ b: 1 });
a.b = 2; // Error

Object.freeze(..) makes an object immutable at its first root level, nested objects should be freezed as well in case you need this. This behavior can be achieved with some custom recursion function or with a third party library.

Purity and mutations

The best choise for developer that wants to create a readable and clean code is immutability. Utilities like [...arr] and Object.assign({}, obj) may help you do achieve this goal but this approach leads to extra CPU and memory consumption. So that the best choise it using more sophisticated structures like ones from Immutable.js. It introduces structures like Map and List that can help you in real immutability.

Recursion

Everybody knows about recursion and its side effects that touch call stack size and memory consumption. But there are a couple of techniques on the field that can elimitate them and make your code more readable and declarative.

Proper Tail Calls (PTC)

These are not PTC:

foo();
return;
// or
var x = foo( .. );
return x;
// or
return 1 + foo( .. );

However, this is PTC:

return x ? foo( .. ) : bar( .. );

Continuation Passing Style (CPS)

"use strict";

function fib(n,cont = identity) {
    if (n <= 1) return cont( n );
    return fib(
        n - 2,
        n2 => fib(
            n - 1,
            n1 => cont( n2 + n1 )
        )
    );
}

Trampolines

function trampoline(fn) {
    return function trampolined(...args) {
        var result = fn( ...args );

        while (typeof result == "function") {
            result = result();
        }

        return result;
    };
}

// and then
var sum = trampoline(
    function sum(num1,num2,...nums) {
        num1 = num1 + num2;
        if (nums.length == 0) return num1;
        return () => sum( num1, ...nums );
    }
);

var xs = [];
for (let i = 0; i < 20000; i++) {
    xs.push( i );
}

True functional filter()

The most interesting question about filter() is how predicate function should be named? For example:

[1, 2, 3, 4, 5].filter( isOdd );
// and
[1, 2, 3, 4, 5].filter( isEven );

It is not clear what exactly we want to do with our input: filter out or filter in, do we want to GET or SKIP some array elements. Functional programming patterns and most libraries promote next implementation:

filterIn( isOdd, [1, 2, 3, 4, 5] );         // [1,3,5]
filterOut( isEven, [1, 2, 3, 4, 5] );       // [1,3,5]

To clear up all this confusion, let’s define a filterOut(..) that actually filters out values by internally negating the predicate check. While we’re at it, we’ll alias filterIn(..) to the existing filter(..):

Fusion

Imagine we have some array filter / map chain like this:

someList
  .filter(..)
  .filter(..)
  .map(..)
  .map(..)
  .map(..)
  .reduce(..);

It looks pretty nice but its performance is not hte best - we do one extra filter forEach and twu extra map forEach. To avoid this percormance suffering we can combine these filter and map predicate calls into one function per operator. For example:

var removeInvalidChars = str => str.replace( /[^\w]*/g, "" );

var upper = str => str.toUpperCase();

var elide = str =>
    str.length > 10 ?
        str.substr( 0, 7 ) + "..." :
        str;

var words = "Mr. Jones isn't responsible for this disaster!"
    .split( /\s/ );

words;
// ["Mr.","Jones","isn't","responsible","for","this","disaster!"]

words
.map( removeInvalidChars )
.map( upper )
.map( elide );
// ["MR","JONES","ISNT","RESPONS...","FOR","THIS","DISASTER"]

Every array item goes through next chain:

elide( upper( removeInvalidChars( "Mr." ) ) );
// "MR"

elide( upper( removeInvalidChars( "responsible" ) ) );
// "RESPONS..."

You may already catched the point that we can pipe all this map() predicates with pipe, do not like compose personally, so let’s do it with pipe:

words
.map(
    pipe( removeInvalidChars, upper, elide )
);
// ["MR","JONES","ISNT","RESPONS...","FOR","THIS","DISASTER"]

We got the same result by more efficient, declarative and performance-wise way.

Comments