.item()
for Arrays, Typed Arrays, and stringsThe ECMAScript proposal “.item()
” (by Shu-yu Guo and Tab Atkins) introduces the mentioned method for indexable values (Arrays, Typed Arrays, strings). Given an index, the method returns the corresponding element. Its key benefit is that indices can be negative (-1
gets the last element, -2
the second last, etc.). This blog post examines .item()
in detail.
Update November 2020: The method name .item()
ended up not being web-compatible. The tentative new name is .at()
.
item()
for indexable classes Method .item()
works as follows for Arrays:
const arr = ['a', 'b', 'c', 'd'];
assert.equal(arr.item( 1), 'b');
assert.equal(arr.item( 0), 'a');
assert.equal(arr.item(-1), 'd');
That is, the following two expressions are roughly equivalent:
arr.item(-1)
arr[arr.length - 1]
The previous two lines demonstrates the key benefit of .item()
: We can use negative indices that access the elements at the end of an Array. Other Array methods such as .slice()
already support negative indices (while the square bracket operator []
doesn’t):
> ['a', 'b', 'c', 'd'].slice(1, -1)
[ 'b', 'c' ]
> ['a', 'b', 'c', 'd'].slice(-1)
[ 'd' ]
If an index is out of bounds, the square bracket operator accesses the value of a non-index property:
// Set up an Array
const arr = [];
arr['4294967296'] = 'abc';
// `arr` has no indexed properties
assert.equal(arr.length, 0);
// Index 4294967296 is out of bounds
assert.equal(arr[4294967296], 'abc');
The range of Array indices is [0, 2^32^−1) (the maximum Array length 2^32^−1 is excluded).
In contrast, .item()
returns undefined
in such a case:
assert.equal(arr.item(4294967296), undefined);
That makes working with indices a little bit safer.
.item()
The proposal is to add method .item()
to all indexable classes and primitive types of ECMAScript:
Array
Uint8Array
etc.string
Additionally, several DOM classes already have that method:
HTMLCollection
(dynamic, returned by .getElementsByClassName()
, .getElementsByTagName()
, etc.)NodeList
(static, returned by .querySelectorAll()
)DOMTokenList
(static, value of .classList
).item()
When it comes to negative indices, there are alternatives to .item()
, but they are clumsy and/or inefficient:
const arr = ['a', 'b', 'c', 'd'];
const N = -2;
const element1 = arr[arr.length + N];
assert.equal(element1, 'c');
const element2 = arr.slice(N)[0];
assert.equal(element2, 'c');
const {length, [length + N]: element3} = arr;
assert.equal(element3, 'c');
If we need to get the last element of an Array and don’t mind that element being removed in the process, then we can also use .pop()
:
const lastElement = arr.pop();
assert.equal(lastElement, 'd');
.item()
This is how we could polyfill .item()
(a slightly edited version of code shown in the proposal):
function item(n) {
// ToInteger() abstract operation
n = Math.trunc(n) || 0;
// Allow negative indexing from the end
if (n < 0) n += this.length;
// Out-of-bounds access is guaranteed to return undefined
if (n < 0 || n >= this.length) return undefined;
// Otherwise, this is just normal property access
return this[n];
}
// Other TypedArray constructors omitted for brevity.
for (const C of [Array, String, Uint8Array]) {
Object.defineProperty(
C.prototype, 'item',
{
value: item,
writable: true,
enumerable: false,
configurable: true,
});
}
The following two npm packages provide polyfills:
.item()
and upgrading indexable DOM collections A current plan for the DOM is to base existing and upcoming indexable DOM data structures such as HTMLCollection
and NodeList
on ObservableArray
. Instances of that class are proxies for Arrays and therefore have methods such as .map()
that are currently not available in indexable DOM data structures. Then it will no longer be necessary to convert these data structures to Arrays before using these methods:
// Old:
[...document.querySelectorAll('img')].map(img => img.src)
// New:
document.querySelectorAll('img').map(img => img.src)
All indexable DOM data structures have the method .item()
. For various reasons, the easiest way to make ObservableArray
compatible with them, was to add this method to Array
.
.replace()
with callback The following code shows where .item()
is useful:
const result = 'first=jane, last=doe'.replace(
/(?<key>[a-z]+)=(?<value>[a-z]+)/g,
(...args) => {
const groups = args.item(-1); // (A)
const {key, value} = groups;
return key.toUpperCase() + '=' + value.toUpperCase();
});
assert.equal(result, 'FIRST=JANE, LAST=DOE');
groups
is always the last parameter of the .replace()
callback. This is a workaround if .item()
isn’t available in line A:
const groups = args.pop();
Unfortunately, JavaScript can’t be changed to allow negative indices in square brackets. The problem is that such a change would break existing code.
Let’s look at a few examples. I don’t recommend the techniques they are using, but similar code does exist on the web. Each one would break if negative indices were allowed in brackets.
First example:
const english = ['hello', 'world'];
const german = ['hallo', 'Welt'];
function translate(word) {
return german[english.indexOf(word)];
}
assert.equal(translate('world'), 'Welt');
assert.equal(translate('universe'), undefined);
Second example:
const arr = ['fee', 'fi', 'fo', 'fum'];
arr['-1'] = 'Englishman';
// Current behavior:
assert.equal(arr[-1], 'Englishman');
Third example:
const numbers = [1, 2, 3];
const reversed = [];
let i = numbers.length - 1;
while (numbers[i]) {
reversed.push(numbers[i--]);
}
assert.deepEqual(reversed, [3, 2, 1]);
.getItem()
and .setItem()
? As explained in Sect. “.item()
and upgrading indexable DOM collections”, the method name .item()
helps with upgrading the DOM. That’s why this name was preferred over alternatives.
.last
? There is also a stage 1 proposal for a getter/setter .last
. However, quoting that proposal:
Other proposals (Array.prototype.item and Array Slice Notation) also sufficiently solve this problem, and are advancing quicker through the standards track. Should one of these proposals advance to Stage 3, this proposal will be dropped.
Sources of this blog post:
The following people contributed to this blog post via discussions on Twitter: