is it possible somehow to add autoincrement .length property
Not really; at least, you can't do all the things Array
does around length
and index properties.
In an ES5-enabled environment, you can make length
a property with getter and setter functions. But it's awkward to find out what length
should be, because there's (so far) no "catch all" way of setting it when code like:
arr[1] = "foo";
...runs. (ES6 may well make it possible to have catch-all property setters, via proxies.)
So your length
would have to figure it out from the object contents, which is not going to be very efficient, e.g.:
var explicitLength;
Object.defineProperty(this, "length", {
get: function() {
var length;
// Find our length from our max index property
length = Object.keys(this).reduce(function(maxIndex, key) {
var index = key === "" ? NaN : +key;
if (!isNaN(index) && index > maxIndex) {
return index;
}
return maxIndex;
}, -1) + 1;
// If we have an explicitly-set length, and it's greater,
// use that instead. Note that if explicitLength is
// `undefined`, the condition will always be false.
if (explicitLength > length) {
length = explicitLength;
}
return length;
},
set: function(value) {
explicitLength = value;
// You might choose to have code here removing properties
// defining indexes >= value, likes Array does
}
});
Note that even when we have an explicitly-set length, we still have to check to see if the natural length is greater, to deal with:
arr.length = 2;
arr[5] = "foo";
console.log(arr.length); // Should be 6, not 2
Doing that much work on a property retrieval is obviously not a good thing, so I would steer clear of this sort of thing.
Above I said "...so I would steer clear of this sort of thing."
Well that's all very well and good, but what should you do instead?
Suppose you have the functions nifty
and spiffy
that you want to have available. You have two realistic options:
Use an actual array, and mix in your custom methods to each array instance
Enhance Array.prototype
#1 Use an actual array and mix in your methods
You'd define your methods on a convenient object, say ArrayMixin
:
var ArrayMixin = {
nifty: function() {
// ...do something nifty with `this`
},
spiffy: function() {
// ...do something spiffy with `this`
}
};
Then have a builder function for creating arrays:
function createArray(arr) {
arr = arr || []; // `arr` is optional
extend(arr, ArrayMixin);
return arr;
}
What's this extend
function, you ask? Something that copies properties from one object to another. If you use any libraries or frameworks (jQuery, Underscore, Angular, PrototypeJS, ...), they're likely to have an extend
function of some kind that does this. The usually look something like this (this is very off-the-cuff).
function extend(target) {
var arg, obj, key;
for (arg = 1; arg < arguments.length; ++arg) {
obj = arguments[arg];
for (key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
}
return target;
}
Note that that's a shallow copy, and it doesn't bother to try to make the properties it adds to target
non-enumerable. Tweak as desired.
So then when you want an array:
var a = createArray(['one', 'two', 'three']);
...and a
has nifty
and spiffy
on it.
Example:
var ArrayMixin = {
nifty: function() {
this.forEach(function(entry, index) {
this[index] = entry.toUpperCase();
}, this);
return this;
},
spiffy: function() {
this.forEach(function(entry, index) {
this[index] = entry.toLowerCase();
}, this);
return this;
}
};
function createArray(arr) {
arr = arr || []; // `arr` is optional
extend(arr, ArrayMixin);
return arr;
}
function extend(target) {
var arg, obj, key;
for (arg = 1; arg < arguments.length; ++arg) {
obj = arguments[arg];
for (key in obj) {
if (obj.hasOwnProperty(key)) {
target[key] = obj[key];
}
}
}
return target;
}
var a = createArray(['one', 'two', 'three']);
a.nifty();
snippet.log(a.join(", "));
a.spiffy();
snippet.log(a.join(", "));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Enhance Array.prototype
People freak out about enhancing Array.prototype
, but there's really nothing wrong with it. The only reason it causes trouble is if people use broken code that expects for-in
to loop through array indexes and you've had to enhance the prototype without making the new methods non-enumerable. But again, that code is broken; for-in
isn't for looping through array indexes, it's for looping through object properties.
So here's enhancing Array.prototype
:
(function(aproto) {
var add;
if (Object.defineProperty) {
// Function to add a non-enumerable property
add = function(obj, prop, value) {
Object.definePrperty(obj, prop, {
value: value
});
};
} else {
// Old JavaScript engine, oh well
add = function(obj, prop, value) {
obj[prop] = value;
};
}
add(aproto, "nifty", function() {
// ...do something nifty with `this`
});
add(aproto, "spiffy", function() {
// ...do something spiffy with `this`
});
})(Array.prototype);
Now, using it is really clean:
var a = ['one', 'two', 'three'];
...and a
has nifty
and spiffy
on it.
Example:
(function(aproto) {
var add;
if (Object.defineProperty) {
// Function to add a non-enumerable property
add = function(obj, prop, value) {
Object.defineProperty(obj, prop, {
value: value
});
};
} else {
// Old JavaScript engine, oh well
add = function(obj, prop, value) {
obj[prop] = value;
};
}
add(aproto, "nifty", function() {
this.forEach(function(entry, index) {
this[index] = entry.toUpperCase();
}, this);
});
add(aproto, "spiffy", function() {
this.forEach(function(entry, index) {
this[index] = entry.toLowerCase();
}, this);
});
})(Array.prototype);
var a = ['one', 'two', 'three'];
a.nifty();
snippet.log(a.join(", "));
a.spiffy();
snippet.log(a.join(", "));
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>