Syntax and Data Structures of ES6: Logical Operators, Array.prototype.reduce, and Deep Copy Index Rules

Dean Gladish
8 min readMay 8, 2022

ECMAScript 2015, the second major revision to JavaScript, is also known as ES6 and ECMAScript 6 which introduces several key features while maintaining the existing Array.prototype methods inherent in JavaScript.¹

For the application of .reduce():

let arr = [1,0,9,8,2,5,0,9,1];
let mean = arr.reduce((curr, next) => {
return curr + next;
}, 0) / arr.length || null;

Array.prototype.reduce() adds the return value curr + next to the accumulator curr, which accumulates the return value for each element in the array as seen here with the running sum and initial value set to 0. There are so many ways to reduce an array into some easily recognizable form, like average value.²

arr.reduce is typically returned because it’s usually a truthy value. If the array length is 0, then arr.length does not exist -> False. The second option null will be returned. This is a linear function which tries each provided option sequentially.

Instead of having the same value at different keys in an Array, you could have different values at the same key. We can be given a Map instead of an Array. Then if we’re not satisfied with the value we can iterate over and access over multiple maps at once.³

const multipleMaps = (input, maps) => {
return input.map(key => {
for (const map of maps) {
if (key in map) return map[key];
}
return key;
});
};

The maps variable is, for two examples,

[{ a: 0, b: 1 }, { c: 2 }, { d: 3 }] where input is [‘a’, ‘b’, ‘c’, ‘d’] because a, b, c, and d are keys, or [{ 1: ‘!’, 2: ‘@’ }, { 3: ‘#’, 4: ‘$’ }] with input[1, 2, 3, 4].

const maps = [{ 1: '!', 2: '@' }, { 3: '#', 4: '$' }];for (const map of maps) {
console.log(map);
};

will return

{
1:”!”,
2:”@”
} {
3:”#”,
4:”$”
}

So a collection of two different maps can be reduced into each map, and then onto the corresponding value; it’s really analogous to a for-loop in an array in which that array’s objects are not editable. The documentation for logical operators mentions some short-circuit, left to right evaluation of expressions.⁴ These are the logical operators that can be used in return statements:

  • true || true => true
  • true || false => true
  • false || true => true
  • false || false => false

If value 1 is true, then value 1 || value 2 doesn’t depend on value 2.

Everybody knows there are good ways to deep copy an array. How do you deep copy an array? For functional programming, Array.prototype.slice() as a built-in function has some unique characteristics: the first parameter, which is the beginning of the selected array indices, has no lower bound; it defaults to 0 while the beginning has an upper bound at array.length. On the other hand, the second parameter, which is the end of the selected array indices, has no upper bound; it defaults to the array.length while the end of the input array has a lower bound at -array.length. These rules govern what .slice() then returns.

.slice() creates a shallow copy,⁵ which means the copy shares the same object references:

const arr = [1, [1], {a:1}];const shallowCopy = arr.slice();shallowCopy[0] = 2;shallowCopy[1][0] = 2;shallowCopy[2]['a'] = 2;console.log(arr);

[1, [2], {a: 2} ]

Modifying those objects using the shallow copy may inadvertently change the original array. Instead, we may handle a deep clone in JavaScript, depending on the use case, using 5 different approaches to deep cloning objects/arrays; the _lodash library and its _.cloneDeep function allow us to prioritize simplicity over minor gains in efficiency.⁶

const arr = [0, [0], {a:0}, []];const copyEquals = arr;const copySpread = [...arr];const copySlice = arr.slice();const copyAssign = []; Object.assign(copyAssign, arr);const copyFrom = Array.from(arr);const deepCopyWithLodashCloneDeep = _.cloneDeep(arr);const deepCopyFunction = (obj) => {
// base case; ex. typeof(0) === 'number' and [][0] === undefined.
if (typeof obj !== "object" || obj === undefined) {
return obj;
}
let res = Array.isArray(obj) ? [] : {};
// to handle objects like {a:1}
for (let key in obj) {
res[key] = deepCopyFunction(obj[key]);
}
return res;
};
const deepCopyCustom = deepCopyFunction(arr);arr[0] = 1;arr[1][0] = 1;arr[2]['a'] = 1;arr[3][0] = 1;console.log(copyEquals,
copySpread,
copySlice,
copyAssign,
copyFrom,
deepCopyWithLodashCloneDeep,
deepCopyCustom);

_lodash has a lot of methods which simplify the code greatly,⁷ and based on the article about shallow/deep cloning and some⁸ benchmarks⁹, Ramda deep clones at 1.6 times the speed of Lodash, and rfdc (and custom methods) at 4.5 times the speed of Lodash. These differences are almost always minor.

There are so many features of ES 2020 such as async functions, and there’s such a big difference between false value (empty string, number 0, undefined, null, false, NaN) and nullish, which allows us to check for null or undefined truthy values.¹⁰ “0” is true in an if statement, while “0” === false, plus once null values merge if statements are no longer required.¹¹

Null values play a part in taking the place of buttons which refer to non-existent scripts for example, for ORMs like Sequelize which can permit null values to be allowed to take the place of unspecified values within the SQL statement, or specify the value within the SQL statement when selecting columns and inserting values, for defining the undefined number type on a React component’s state, and for initializing linked list references to nodes.

ES6 has some special operators for incrementation.

Given a number num, num + 1 will return the value that num holds plus 1. ++num will increment num by 1 then return num. num++ will return num then increment it by one.

console.log(num === num++);    // expect trueconsole.log(num === ++num);    // expect falseconsole.log(num === num + 1);    // expect false¹²// Let's count up to zero!let n1 = -3;while (n1) { console.log(n1++) } n1 = -3;while (n1) { console.log(++n1) } n1 = -3;while (n1++) { console.log(n1) } n1 = -3;while (++n1) { console.log(n1) }

-3
-2
-1

-2
-1
0

-2
-1
0

-2
-1

Here, the middle two counts are the same because those while-loops have the same left-to-right order: check that n1 isn’t false,¹³ increment it by 1, and then log it to the console.

The arguments object …args

Given that passing a value to a function, and declaring the value within the function are the same as far as the scoping of the value goes (it is scoped to the function), we find ourselves at the same starting point at the beginning of the function.

const fn1 = (arg) => { console.log(arg); }fn1('passing');const fn2 = (arg = 'in') => { console.log(arg); }fn2();const fn3 = (arg = 'in') => { console.log(arg); }let arg = 'the argument';fn3('the argument');const fn4 = () => {   
let arg = 'is the same as declaring it within the scope of the function!';
console.log(arg);
}
fn4();console.log(`Also, ${arg}'s value in the global scope does not change.`);

It’s pretty clear that because arg is a primitive (string), modifications to it inside the function won’t have any effect outside of the function.

//console.log([1,2,3],
// ...[1,2,3],
// typeof([1,2,3]));
console.log(typeof(...[1,2,3]));

error: unknown: Unexpected token and Expression expected.

While args is an instance of Array, the data type of (known as spread syntax) args such that functions can read any number of arguments is that is used to expand an array (or more generally, objects) into its elements.¹⁴ When we’re trying to find typeof (…[1,2,3]) we’re actually attempting to find typeof multiple primitive types, which is why it’s throwing an error.¹⁵

The spread syntax is the equivalent of removing the brackets on an array:

fn(...[1,2,3]) <–> fn(1,2,3).

console.log(typeof(...[1])); still returns an error because the object generated by ...[1] does not exist!

One way to make our code more concise is to decrease the overuse of if/else statements by using switch statements,

let txt;switch (num) {
case (num < 1):
txt = "less than 1";
break;
case (num < 2):
txt = "less than 2";
break;
default:
txt = "catch statement";
};

ternary operators,

txt = (num < 1) ? "less than 1" : 
(num < 2) ? "less than 2" : "catch statement";

and/or operators,

txt = num < 1 && "less than 1" || 
num < 2 && "less than 2" : "catch statement";

and lookup maps.

let num = 1;let txt = {1: "equals 1", 
2: "equals 2",
}[num];

For the lookup map shown, we need to use a function to convert the key by flooring it to the key that represents the range.¹⁶

To clarify the use of &&/||,

let a = 1, b = 2, c = 3;console.log(a && b && c);

3

console.log(a || b || c);

1

In this type of evaluation,

  • && returns the first Falsy value. If all values evaluate to true, then the last expression is returned.
  • || returns the first Truthy value (if all values evaluate to false, then the last expression is returned).

We can convert to Boolean using the negative operator, !:

let convert0toTrue = !0, convert0toFalse = !!0, convert1toFalse = !1;console.log(convert0toTrue, convert0toFalse, convert1toFalse);

true false false

For lookup map comparisons which are not equal but rather less than or greater than, we can use the | operator to do float-to-integer rounding:

console.log(10.7 | 0);

10

These data structures are built into ES6, which also introduces two new data structures: Maps and Sets.

[1]: w3schools.com. JavaScript ES6
https://www.w3schools.com/js/js_es6.asp

[2]: Dirask. ❤ 💻 JavaScript — average value calculation using array reduce()
https://dirask.com/posts/JavaScript-average-value-calculation-using-array-reduce-DkB5G1

[3]: Stack Overflow. Iterating over and accessing multiple maps at once (JavaScript)
https://stackoverflow.com/questions/52226694/iterating-over-and-accessing-multiple-maps-at-once-javascript

[4]: MDN. Expressions and operators — JavaScript
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators

[5]: Andre Ye. Shallow & Deep Copies — Stop Making These Common Slicing Mistakes. Towards Data Science.
https://towardsdatascience.com/shallow-deep-copies-stop-making-these-slicing-mistakes-12d02ffa2f7f

[6]: Dr. Derek Austin 🥳. JavaScript in Plain English. How to Deep Copy Objects and Arrays in JavaScript
https://javascript.plainenglish.io/how-to-deep-copy-objects-and-arrays-in-javascript-7c911359b089. My friend Erin Shaw says there are other use cases, other programmers, different approaches.

[7]: Lodash Documentation
https://lodash.com/docs/4.17.15

[8]: MeasureThat.net. Benchmark: Lodash cloneDeep vs JSON Clone vs Ramda Clone vs Pvorb Clone
https://www.measurethat.net/Benchmarks/Show/3041/0/lodash-clonedeep-vs-json-clone-vs-ramda-clone-vs-pvorb

[9]: GitHub. davidmarkclements/rfdc: Really Fast Deep Clone
https://github.com/davidmarkclements/rfdc

[10]: Develop Paper. These new features of es2020 are expected
https://developpaper.com/these-new-features-of-es2020-are-expected/.

[11]: Stack Overflow. boolean — In JavaScript, why is “0” equal to false, but when tested by ‘if’ it is not false by itself?
https://stackoverflow.com/questions/7615214/in-javascript-why-is-0-equal-to-false-but-when-tested-by-if-it-is-not-fals. My friend Jonathan Arreola always wanted to check for an undefined value and forget about the truthiness of zero. We really love the nullish coalescing operator.

[12]: Michael Koshakow conceives the dispassionate operator return, which is linear with ++.

[13]: MDN. Falsy — MDN Web Docs Glossary: Definitions of Web-related terms
https://developer.mozilla.org/en-US/docs/Glossary/Falsy

[14]: MDN. Spread syntax (…) — JavaScript
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

[15]: It’s your favorite Aleks Mitrovic when he says we need not assume anything other than the typeof multiple primitive types from the spread operator ….

[16]: Adam Reid recommends for large scale categorization with many sub-ranges, instead of look-up maps it is common to use a range tree. Here is a hash function which performs reduction on the string at hash(diff=25), returning the last seen value of el instead of acc, which is 0 by default. When acc and el are strings, the comparison is in Unicode.

let txt = map[hash(diff)]; 

function hash(num) {
return Object.keys(map)
.reduce((acc, el) => {
return el < num && el > acc ? el : acc;
});
};

--

--