Why JavaScript needs the method ‘last()’ and the proper way to add it yourself
How do you extract file-names from file-paths?
Why would you need to? Well maybe you have an Array of URLs and you want to create hyperlinks, and want to show file-name as the link-name instead of the whole long URL.
This could be done easily if only JavaScript Arrays had the method ‘last()’. I’ll explain below why that is so.
Here’s an array of file-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 ‘/’.
Getting Closer
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]
);
So what’s the problem?
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. And no need to mess around with them 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 very dynamically. That array is not stored into any variable, it’s life-time is very short. 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 only we could ask the array itself for its last element.
The Improper Solution
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.
Biblical references aside it is really in your own best-interest to not modify base-classes. Forget about what you do to others. If you modify or add a base-class method like Array.prototype.last() your code becomes dependent on that method working in the specific way you programmed it. But if somebody else later adds that same method with a different implementation you’re in for trouble.
In the best case your program crashes and you are lucky because then you know there is a problem. It is worse if your program doesn’t crash but simply starts giving your users wrong answers with nobody realizing they are wrong. Until much later, when your space-probe giddily slides past Jupiter into the outer space rather than establishing the steady orbit around it like it was supposed to.
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 the right solution.
The Ideal 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.
Solution
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()
)
LINKS
- GitHub Cisf.js README.md
2. Npm, the place it is installed from
3. NYC Node.js meetup July 2018 Presentation on Using Cisf to type-check JavaScript:
4. Alternative syntactic way to add ‘last’ to JavaScript
Access to last element could also be provided by extending Array Destructuring, see my comment at :