I do not think this is really inconsistent. Yes, they can be a bit confusing, because JavaScript arrays do everything for which other languages ββhave separate structures (list, queue, stack, ...), but their definition is quite consistent between the languages. You can easily group them in the categories that you have already described:
- list of methods:
push / unshift returns the length after adding elementspop / shift returns the requested item- you could define additional methods to get the first and last element, but they are rarely needed
splice is a universal tool for removing / replacing / inserting elements in the middle of the list - it returns an array of deleted elements.sort and reverse are two standard in-place reordering methods.
All other methods do not modify the original array:
slice to get subarrays by position, filter to get them by condition and concat to merge with other creatures and return new arraysforEach just forEach through the array and returns nothingevery / some check the elements for the condition, indexOf and lastIndexOf look for the elements (according to the principle of equality) - both return their resultsreduce / reduceRight reduces the elements of the array to a single value and returns this. Special cases:map reduces to a new array - it is similar to forEach , but returns the resultsjoin and toString reduce to string
These methods are sufficient for most of our needs. We can do everything with them, and I donβt know any libraries that would add similar, but internal or resultant methods to them. Most data processing libraries (e.g. Underscore ) only make them cross-browser ( es5-shim ) and provide additional utilities.
I want to always mutate the array and always return the same array, so I can have some consistency and also be able to chain.
I would say that JavaScript consistency is to always return a new array when elements or lengths change. I suppose this is because objects are reference values, and changing them too often causes side effects in other areas that reference the same array.
Chaining is still possible with this, you can use slice , concat , sort , reverse , filter and map together to create a new array in just one step. If you want to "modify" an array only, you can simply reassign it to an array variable:
A = A.slice(0,1).reverse().concat(['a','b']);
Mutation methods have only one advantage for me: they are faster because they can be more memory efficient (of course, it depends on the implementation and garbage collection). Therefore, it allows you to implement some methods for them. Since the Array Subclass is neither possible nor useful, I will define them on a native prototype :
var ap = Array.prototype; // the simple ones: ap.each = function(){ ap.forEach.apply(this, arguments); return this; }; ap.prepend = function() { ap.unshift.apply(this, arguments); return this; }; ap.append = function() { ap.push.apply(this, arguments; return this; }; ap.reversed = function() { return ap.reverse.call(ap.slice.call(this)); }; ap.sorted = function() { return ap.sort.apply(ap.slice.call(this), arguments); }; // more complex: ap.shorten = function(start, end) { // in-place slice if (Object(this) !== this) throw new TypeError(); var len = this.length >>> 0; start = start >>> 0; // actually should do isFinite, then floor towards 0 end = typeof end === 'undefined' ? len : end >>> 0; // again start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len); end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len); ap.splice.call(this, end, len); ap.splice.call(this, 0, start); return this; }; ap.restrict = function(fun) { // in-place filter // while applying fun the array stays unmodified var res = ap.filter.apply(this, arguments); res.unshift(0, this.length >>> 0); ap.splice.apply(this, res); return this; }; ap.transform = function(fun) { // in-place map if (Object(this) !== this || typeof fun !== 'function') throw new TypeError(); var len = this.length >>> 0, thisArg = arguments[1]; for (var i=0; i<len; i++) if (i in this) this[i] = fun.call(thisArg, this[i], i, this) return this; }; // possibly more
Now you can do
A.shorten(0, 1).reverse().append('a', 'b');