Why JavaScript needs the method ‘last()’ and the proper way to add it yourself

By Panu Viljamaa

I have a set of file-paths. I want to extract the file-names from them. Perhaps I have an Array of URLs and am creating hyperlinks, and want to show file-name as the link-name instead of the whole long URL. Keep it short.

Here’s my array of paths:

let paths =
[ `/a/b/file.txt`
, `/a/b/c/file2.txt`
];

I can get the last element of above array with the standard way it is done in JavaScript:

let lastPath = paths [paths.length - 1];

But that is lengthy. See, you must refer to the variable ’paths’ twice. The more you type more typos you make. It requires you to know many things. It requires an explanation of why it works: Arrays have property ‘length’ and index of the last element of an array is its length - 1 . Therefore the above gives you the last element of the array.

The real reason why the above is undesirable in ES6 is it doesn’t work well with Arrow-expressions. We’ll explain shortly.

There is another way of getting the last element, advocated by some:

let lastPath = paths.slice(-1)[0];

That works but needs even more explanation. I won’t go into why it works since that is not the purpose of this article. Why it works is left as an exercise to the reader — and that is the problem, with code like above.

Now back to the problem at hand. I don’t actually need the last path. I need the last part of each path after its last ‘/’.

To get closer to having the file-names, I split the paths into arrays, with path-separator ‘/’:

let pathsAsArrays 
= paths.map
( e => e.split ('/')
);

I now have an array of arrays such that last element of each array is the file-name I want. Pretty close. I then just need to collect the last elements with another map() -call:

let fileNames 
= pathsAsArrays.map
( e => e [e.length — 1]
);

The problem is I needed to write two map() -calls. And I needed to deal with arrays of arrays. That requires multi-dimensional thinking. That’s a bit like multi-dimensional chess, which is not my favorite game.

If JavaScript’s Array.prototype had the method last(), I wouldn’t need two map-calls. I would need only one:

let fileNames = paths.map
( e => e.split ('/').last()
)

Instead of two map()-calls there would be only one map() -call. That’s 50% reduction in map() -calls needed. With no need to deal with multi-dimensions. That would be good.

So why can’t we simply use the standard JavaScript way of accessing last element of array as [arr.length-1] and somehow inject that into the ES6 arrow -function above?

The answer is that expression “e.split (‘/’)” creates an array of path-parts dynamically. That array is not stored into any variable, unless we do that with a separate map() -call. And we can not refer to the “array just created”, to get its length so we could get its last element. Too bad. We could do that if we could ask the array for its last element.

JavaScript currently does not have a method for accessing the last element of arrays. One thing you could think of is adding such a simple method to Array.prototype yourself. Implement it as slice(-1)[0] for instance. But that would be bad practice.

If we all start adding our methods to JavaScript base-classes it becomes unsafe when we combine code written by multiple authors. Someone else’s last() might override yours, and their last() might do things you don’t want. Do not unto others what you don’t want them doing to you. Do not modify built-in base-classes.

The problem of accessing last array-element in JavaScsript has been discussed at length on Stack OverFlow https://stackoverflow.com/questions/3216013/get-the-last-item-in-an-array . That question has 32 answers. Which suggests it is somewhat of a problem, doesn’t it? The consensus seems to be that modifying Array.prototype yourself is not a desirable solution.

The ideal solution would be for JavaScript next version to add the method ‘last’ to Array.prototype, as part of the standard. We can only hope that happens. I have tried to argue above why it would be useful,, and show how it would make code much simpler, easier to read, and easier to UNDERSTAND:

let fileNames = paths.map
( e => e.split ('/').last()
)

If the method last() existed, you wouldn’t need an explanation of HOW it produces its result. You only need to know that it does. You would only need to know WHAT, not HOW.

In the mean time there is a practical and not improper way to add method last() to Arrays. The solution is to not modify the built-in class Array, but to create your own. Instead of modifying the prototype, wrap arrays inside a wrapper-object, which provides the method last().

An easy way to do that is with the ‘w()” -API of the open-source library “cisf.js”. ‘w’ of course stands for “wrapper. Using w() makes it look almost as if the method Array.prototype.last() existed already:

let {w}   = require (“cisf”);  
let fileNames = paths.map
( e => w ( e.split (‘/’)
).last()
)

To learn more about cisf.js and its w() -API, follow these links:

2. Npm, the place it is installed from

3. NYC Node.js meetup July 2018 Presentation on Using Cisf to type-check JavaScript: