Skip to content

Latest commit

 

History

History
274 lines (208 loc) · 10.1 KB

2019-02-24-array-prototype.md

File metadata and controls

274 lines (208 loc) · 10.1 KB
layout author title date desc img categories tags extras interesting toc
post
Wouter Van Schandevijl
Array.prototype for .NET developers
2019-02-23 16:00:00 -0800
A guide on learning JavaScript `Array.prototype` by comparing it to C# Linq.
Array.prototype.png
javascript
cheat-sheet
tutorial
githubproject githubtext
Github source code as UnitTests
git desc
msn0/mdn-polyfills
Include polyfills for your favourite functions that are not (yet) implemented by your browser(s).
url desc
2ality.com: Blogging on ES proposals
url desc
es6-features.org: Overview and Comparison
url desc
StackOverflow: Jon Skeet on why there is no forEach in Linq
title icon
Array.prototype
icon-javascript

The Array.prototype functions have been available for a long time but it's since Arrow functions that "Linq lambda syntax"-like functionality is available in JavaScript.

This blog post explains the most common functions by comparing them to their C# equivalents.

A basic example:

// JavaScript
const result = [0, 1, 2, 3, null]
    .filter(x => x !== null)
    .map(x => x * 10)
    .sort((a, b) => b - a);

expect(result).toEqual([30, 20, 10, 0]);

// C#
var result = new int?[] {0, 1, 2, 3, null}
    .Where(x => x != null)
    .Select(x => x * 10)
    .OrderByDescending(x => x)
    .ToArray();

Whenever you want to write a for to loop over an array, the Array.prototype functions probably allow you to achieve the same result but in more functional and succinct manner.

Do note that the deferred execution we know from Linq does not apply to Array.prototype!

Comparison Table

| C# | JavaScript | MDN Link |---------------------------------|---------------------------------------------------------- | Select() | map((cur, index, array): any) | map | Where() | filter((cur): boolean) | filter | Contains() | includes(value, fromIndex) | includes | FirstOrDefault() | find((cur): boolean): undefined | any | find | All() | every((cur): boolean): boolean | every | Any() | some((cur): boolean): boolean | some | Concat() | concat(arr: any[]): any[] | concat | Skip(start).Take(start - end) | slice(start = 0, end = length-1) | slice | string.Join() | join(separator = ',') | join | Array.IndexOf() | findIndex((cur): boolean): -1 | number | findIndex | Count() | length: number | length | Extension method | forEach((cur): void): void | forEach | | Mutating in JS | These are in place operations | | OrderBy() | sort((a, b): number) | sort | Reverse() | reverse() | reverse |---------------------------------|------------------------------------------------|---------- {: .table-code}

Commonly used methods

Select = map

// C#
var result = enumerable.Select(x => x);

// JavaScript
let result = array.map(x => x);

// Using spread to avoid mutation
const input = [{k: 1, v: true}, {k: 2, v: false}];
const result = input.map(x => ({...x, v: !x.v}))

When mapping to an object without code block, you need to wrap your object between extra parentheses like

[0, 1].map(x => ({value: x}));

// Because without the extra parentheses
[0, 1].map(x => {value: x});
// --> [undefined, undefined]

// is the same as writing:
[0, 1].map(x => {
    value: x; // No error, because JavaScript?
    return undefined;
});

Where, Distinct = filter

Where and filter behave pretty much exactly alike. Linq's Distinct we'll need to implement ourselves.

const input = [0, 0, 1, 5, 5];
// Equals true when the first occurence of the value is the current value
const result = input.filter((element, index, array) => array.indexOf(element) === index);
// Or: [...new Set(input)];
expect(result).toEqual([0, 1, 5]);

Aggregate, GroupBy, ... = reduce

Linq has Sum, Min, Max, Average, GroupBy, etc. While JavaScript doesn't have them, they can all be achieved trivially with reduce

Sum, Min, Max, Average:

const input = [0, 1];
const sum = input.reduce((total, cur) => total + cur, 0);
const average = sum / input.length;
const min = Math.min.apply(Math, input); // Old school
const min = Math.min(...input); // Using spread

Aggregate, GroupBy:

const input = [0, 1, 2, 3];

const result = input.reduce((acc, cur) => {
  if (cur % 2 === 0) {
    acc.even.push(cur);
  } else {
    acc.odd.push(cur);
  }
  return acc;
}, {even: [], odd: []);

expect(result).toEqual({even: [0, 2], odd: [1, 3]});

slice

Linq has First, Last, Skip, SkipLast, SkipWhile, Take, TakeLast, TakeWhile. JavaScript has slice.

// Shallow copy
const input = [0, 1, 2, 3];
expect(input.slice()).toEqual([...input]);

// Signature
slice(startIndex = 0, endIndex = length-1);

Mutations

These functions operate in place.

OrderBy = sort

  • Do a slice() first if you need a different array reference for the result of the sort.
  • Without compareFn the array is sorted according to each character's Unicode code point value.
  • The compareFn should return a number:
    • -1 (or any negative number): a comes before b
    • 0: Equal
    • 1 (or any positive number): a comes after b
// Numbers
[10, 5].sort(); // [10, 5]: each element is converted to a string
[10, 5].sort((a, b) => a - b); // [5, 10]

// Strings
['z', 'e', 'é'].sort(); // ['e', 'z', 'é']
['z', 'e', 'é'].sort((a, b) => a.localeCompare(b)); // ['e', 'é', 'z']

// Dates
[d1, d2, d3].sort((a, b) => a.getTime() - b.getTime());

C#

  • The signature is a bit different: OrderBy(Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
  • OrderBy vs OrderByDescending: switch a and b...

forEach

While forEach is not really doing any mutation by itself, it is often what it's used for. Mutations are especially dangerous in for example a Redux environment where UI changes might lag. The same can usually be achieved with map.

const input = [{value: 1, visited: 0}, {value: 2, visited: 0}];

// Potentially dangerous
input.forEach(el => {
    el.visited++;
});

// ...el will create a shallow copy only!
const result = input.map(el => ({...el, visited: el.visited + 1}));

// Or use the good old loop?
for (let itm of input) {
    console.log(itm.value);
}

Other mutators