Possibility to destructure last item from Array

My suggestion is a new possibility to destructure the last item from Arrays/Function parameters like so:

// ARRAYS:

const [first, ...rest] = [1,2,3,4] 
// first = 1, rest = [2,3,4], works as usual!

const [first, ...rest, last] = [1,2,3,4] 
// -> first = 1, rest = [2,3], last = 4

const [...rest, last] = [1,2,3,4] 
// rest = [1,2,3], last = 4

const [...rest, last] = [1,2] 
// rest = [1], last = 2

const [...rest, last] = [1] 
// rest = [1], last = 1

const [first, ...rest, last] = [1] 
// first = 1, rest = [1], last = 1

const [...rest, last] = [] 
// rest = [], last = undefined

// FUNCTION PARAMS:

const a = (first, ...rest, last) => last

a(1) // -> undefined
a(1,2) // -> undefined
a(1,2,3) // -> 3
a(1,2,3,4) // -> 4
// etc.

It would be rather helpful in many situations I come across frequently. At the time of writing, lodash's last() function tries to do that.

What do you think?

Kind regards,
Lorenz

EDIT:
I see this idea is already being worked on: GitHub - tc39/proposal-deiter: Double-Ended Iterator and Destructuring.

3 Likes

See also GitHub - tc39/proposal-array-last: A JavaScript TC39 Proposal for getting the last element from an array and GitHub - tc39/proposal-relative-indexing-method: A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray)

These 2 are the most surprising to me. Why should ... behave that way?

I would expect:

const [...rest, last] = [1] 
// rest = [], last = 1

const [first, ...rest, last] = [1] 
// first = 1, rest = [], last = undefined
7 Likes

I would expect the same as mhofman.

1 Like

The semantics you expect are what I'd prefer, too. But aside from that, I extremely want this.

const [first, ...rest, last] = [1] 
// first = 1, rest = [], last = undefined

I agree with this one.

const [...rest, last] = [1] 
// rest = [], last = 1

not with this one as by the logic you talked about everything is destructured FIFO/greedily. So rest = [1], last = undefined.

No, my intuition is that fixed identifiers consume elements from both the head and the tail (with precedence to the head), and the ... captures the rest, aka anything that wasn't consumed as fixed elements.

I just noticed this is also wrong, and I would expect a(1,2) // -> 2

1 Like

I think the destructuring should be done this way:

  • get each param before rest
  • get each param after rest in REVERSE order
  • get rest

Examples:

(1)

const [a, b, c, ...rest, x, y, z] = [1, 2, 3, 4, 5, 6, 7, 8, 9] 

// before rest
a = 1, b = 2, c = 3

// after rest
x = 7, y = 8, z = 9

// rest
rest = [4, 5, 6]

(2)

const [a, b, c, ...rest, x, y, z] = [1, 2, 3, 4, 5, 6] 

// before rest
a = 1, b = 2, c = 3

// after rest
x = 4, y = 5, z = 6

// rest
rest = []

(3)

const [a, b, c, ...rest, x, y, z] = [1, 2, 3, 4, 5] 

// before rest
a = 1, b = 2, c = 3

// after rest
x = undefined, y = 4, z = 5

// rest
rest = []

(4)

const [a, b, c, ...rest, x, y, z] = [1, 2] 

// before rest
a = 1, b = 2, c = undefined

// after rest
x = undefined, y = undefined, z = undefined

// rest
rest = []

In others words, this function:

function(a, b, c, ...rest, x, y, z) {
  console.log(a, b, c, rest, x, y, z)
}

is equivalent to this one:

function(...args) {
  var a = args.shift()
  var b = args.shift()
  var c = args.shift()

  var z = args.pop()
  var y = args.pop()
  var x = args.pop()

  const rest = args

  console.log(a, b, c, rest, x, y, z)
}
4 Likes

If anyone wants to mock up the logic I've already built most of it as part of my iterable slice implementation, which needs it to be able to understand negative end indicies.

Chat GPT: JavaScript doesn't support an inverse rest operator like (...a, b) to spread elements into arguments while also having a trailing argument outside of the spread.

However, you can achieve similar behavior using array destructuring and the spread operator in combination with regular function arguments.

Here's an example of how you might accomplish this:

function myFunction(a, b, ...c) {
    console.log(a, b, c);
}

const arr = [1, 2, 3, 4, 5];

myFunction(...arr.slice(0, -1), arr[arr.length - 1]);

Need this for reflections and meta prog