DEV Community

Functional Javascript
Functional Javascript

Posted on • Edited on

Remove null and undefined from array in JavaScript Code Snippet Series: Remove Junk from Array

Quiz

There are two key areas to increase the performance of this code without losing robustness; can you spot them?

/**
@func 
remove junk (non-value-based data) from an arr

@param {*[]} a
@return {*[]}
*/
export const removeNonVals = a => a.filter(v => {
  return v !== null && v !== undefined
    && !(v.constructor === String && v.trim() === "")
    && !(v.constructor === Object && Object.keys(v).length === 0)
    && !(Array.isArray(v) && v.length === 0)
});

//@tests

//@fixtureData
const aDirty = [
  "Casbah", "abcd",
  "", "p",
  "", "255",
  undefined, null,
  "", "     ",
  [], {}, "[]", "{}"
  [[]],
  [["nested"]],
  NaN, Infinity, 0, -0,
  5, -1, 1e30, BigInt(3145),
  n => n
  , , , , ,
];

const aClean = removeNonVals(aDirty);
console.log(aClean);
/*
@output
[
  'Casbah',               'abcd',
  'p',                    '255',
  'Warsaw',               '1855',
  '[]',                   [ [ 'nested' ] ],
  NaN,                    Infinity,
  0,                      -0,
  5,                      -1,
  1e+30,                  3145n,
  [Function (anonymous)]
]
*/

//@perftest
timeInLoop("removeNonVals", 1e6, () => removeNonVals(aDirty))
/*
removeNonVals: 1e+6: 2.601s
*/

TimeInLoop Source Code

https://gist.github.com/funfunction/91b5876a5f562e1e352aed0fcabc3858

Top comments (6)

Collapse
 
pentacular profile image
pentacular

I suggest that you start by defining a predicate to determine if thing is junk or not.

const isJunk = (thing) => ...;

And since you want to filter on the inverse.

const isNotJunk = (thing) => !isJunk(thing);

Now, you have something meaningful to use with things like filter.

stuff.filter(isNotJunk)
Collapse
 
beastea3 profile image
BeasTea

Do not know why, I changed the condition from v.constructor to v typeof and get a better performance.

Collapse
 
functional_js profile image
Functional Javascript • Edited

Good catch BeasTea,

I've replicated your findings.
It's impressively about 4 TIMES faster.

Interestingly, if you compare the v.constructor and v typeof independently, performance-wise they are effectively the same.

The reason I've preferred v.constructor over typeof is because of its robustness factor; the v typeof returns false for explicit instantiations of types, eg. "new String()"

So what's happening here is a compiler optimization for typeof; where the compiler fails to optimize for the v.constructor.

This would actually be a bug, or at least an optimization deficiency, in the V8 compiler.

Because of the vagaries of compilers, we must perf-test our code and hunt out performance bottlenecks.

//@perftest

//a. typeof
const isEmptyStr = s => typeof s === "string" && s.trim() === "";
timeInLoop("isEmptyStr", 100e6, () => isEmptyStr("asdf"))
//isEmptyStr: 1e+8: 1.019s

//b. s.constructor
const isEmptyStr2 = s => s !== null && s !== undefined && s.constructor === String && s.trim() === "";
timeInLoop("isEmptyStr2", 100e6, () => isEmptyStr2("asdf"))
////isEmptyStr2: 1e+8: 1.038s
Collapse
 
beastea3 profile image
BeasTea

Thx for ur explanation, that's impressive!!!

Collapse
 
hokanginfo profile image
HoKangInfo

I guess,

  1. v == null
  2. v is Array
  3. v is String but test v.length === 0 , then trim
  4. v is Object
Collapse
 
functional_js profile image
Functional Javascript

Quiz Hint:
How many loops are there?