This proposal has progressed to the Draft ECMAScript 6 Specification, which is available for review here: specification_drafts. Any new issues relating to them should be filed as bugs at http://bugs.ecmascript.org. The content on this page is for historic record only and may no longer reflect the current state of the feature described within.

Background

  • ES4’s destructuring assignment proposal
  • The ES4 destructuring assignment discussion page
  • Array destructuring is implemented in the Opera browser starting with Opera 8
  • ES4’s proposal is implemented in SpiderMonkey and Rhino

Goals

  • Allow for destructuring of arrays and objects using syntax that mirrors array and object initialisers
  • Destructuring patterns can appear in assignment expressions but also in the various binding forms
  • Create optimizable idioms such as “group assignment”, e.g. [a, b] = [b, a] to swap two variables

Syntax

Pattern ::= "{" (Field ("," Field)* ","?)? "}"  
          | "[" ArrayPatternList "]"
ArrayPatternList ::= "..." Element
                   | Element? ("," Element?)* ("," "..." Element)?
Element ::= Pattern | LValue
Field   ::= Identifier (":" Element)?
LValue  ::= <any lvalue expression allowed in a normal assignment expression> 

A Pattern may appear only on the left-hand-side of “=” in the following contexts:

  • in a plain assignment expression
  • in a variable initializer in a var, let or const definition
  • in the first expression in the head of a for-in loop

If preceded by a var, let, or const the Pattern must contain LValues that are all identifiers, else the compiler must throw a SyntaxError.

A Pattern may appear not on the left-hand side of “=” in the following contexts:

  • in a catch clause head: try {...} catch ({type, message, filename, lineNumber}) {...}
  • in a formal parameter list: function foo([first, second], third) {...}, callable via foo([1, 2], 3)
    • ES1-3 allow duplicate formal parameter names

In these cases all LValues must be identifiers. Thus only assignment expressions may bind other lvalues than fresh identifiers.

“Don’t Repeat Yourself” motivates a shorthand familiar from ML for object patterns: {x, y} in a pattern context is equivalent to {x: x, y: y}.

Note that an object initialiser cannot appear at the start of a statement, so an object destructuring assignment statement { x } = y must be parenthesized either as ({ x } = y) or ({ x }) = y.

Empty patterns are allowed. Rationale: same basis case as for initialisers helps code generators avoid extra checks.

(We don’t understand the statement about Empty patterns. Besides, they aren’t allowed by the above grammar. – MarkM & Tom)

The grammar does produce empty patterns such as in var {} = obj; and [] = returnsArray(). Note the parentheses and Kleene * usage. The rationale is that empty initialisers are allowed, and indeed top-down parsers must parse an empty pattern on the left of assignment as a primary initialiser expression first, only then discover a = token and revise the AST appropriately to make the initialiser be a destructuring pattern.

The further rationale is that code generators should not need to check and avoid emitting a no-op empty destructuring assignment or binding, if it happens that there are no properties or elements to destructure for the particular case the code generator is handling (say, based on database records or JSON). This is a bit thin by itself, but with the first point (symmetry with initialisers) it adds up to a better case for empty patterns.

Brendan Eich 2010/01/30 17:51

Semantics

Assignments

The meaning of the assignment expression P = E where P is a Pattern and E is an AssignmentExpression is:

  1. Evaluate E yielding a value V
  2. Assign V to a fresh temporary T
  3. If P is an array pattern then
    1. Taking each element L at the top level of P in order, with index I of L in P
      1. If L is an ... Element E then
        1. Let A be a new array created as if by the expression new Array() where Array is the standard built-in constructor with that name.
        2. If T is null or undefined
          1. Perform E = A
        3. Else if Type(T) is not Object, then throw a TypeError exception.
        4. Else
          1. Let len be the result of calling the [[Get]] internal method of T with argument “length”.
          2. Let n be ToUint32(len).
          3. Let index be I.
          4. Repeat while index < n
            1. Let indexName be ToString(index).
            2. Let value be the result of calling the [[Get]] internal method of T with argument indexName.
            3. Call the [[DefineOwnProperty]] internal method of A with arguments indexName, the Property Descriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
            4. Set index to index + 1.
          5. Perform E = A
      2. Else if L is an LValue N then
        1. Perform N = T[I]
      3. Else L is a Pattern P’
        1. Destructure P’ and T[I] according to this algorithm (from step 2)
  4. If P is an object pattern then
    1. Taking each field F with name Q at the top level of P in order
      1. If there is no “:” or right-hand side in F
        1. Perform Q = T.Q
      2. If the right-hand-side of F is an LValue N
        1. Perform N = T.Q
      3. Else the right-hand-side of F is a Pattern P’
        1. Destructure P’ and T.Q according to this algorithm (from step 2)
  5. Return T

As can be seen, destructuring assignment is simple syntactic sugar.

Note: In contrast with normal assignment expressions, the locations updated by destructuring assignment are not computed before the value that is to be stored. Destructuring assignment is simple syntactic sugar for a common compute-and-destructure pattern, and true to this pattern it computes the value prior to computing the locations. Apart from with and embedding-specific deep scope chains, there is no difference between prebinding lvalues vs. post-binding them.

Variable Definitions

The meaning of DefiningKeyword P = E where the DefiningKeyword can be var, let, or const and all LValues in the Pattern P are simple identifiers i1, i2, ..., is DefiningKeyword i1, i2, ... ; P = E.

For Loops

Catch Heads

Function Parameters

Setter Parameter

Examples

Swap:

[a, b] = [b, a]

Multiple-value returns:

function f() { return [1, 2] }
var a, b;
[a, b] = f();

Multiple-value returns, some values are not interesting:

function f() { return [1, 2, 3] }
var [a, , b] = f();

Going deeper into the array:

[a,,[b,,[c]]] = f();

Object destructuring:

var { op: a, lhs: b, rhs: c } = getASTNode()

Digging deeper into an object:

var { op: a, lhs: { op: b }, rhs: c } = getASTNode()

Looping across an object:

for ( let [name, value] in obj )
    print("Name: " + name + ", Value: " + value);

Looping across values in an object:

for each ( let { name: n, family: { father: f } } in obj )
    print("Name: " + n + ", Father: " + f);

Summing the salary fields of all records whose record key begins with N:

for ( let [[k], { "salary": s }] in database )
    if (k == "N")
        sum += s;

Function that destructures its first argument and accepts some optional object arguments:

function f( { "name": n }, ...[ a, b, c ] )
{
}

Brendan Eich 2009/05/06 01:41Lars T Hansen 2007/01/16 07:47

Issues

It would be nice if we could get destructuring to work with rest arguments/spread operator.

[x, ...xs] = someList

Erik Arvidsson 2009/07/30 18:48

Erik: good idea, I will write it up in the rest parameters proposal.

The idea of {x: a, y: b, ...: c}record extension and row capture – for structuring and destructuring comes to mind, but ES objects are a different animal compared to ML records. For example, which properties of c are copied into the object being initialized by {x: a, y: b, ...: c}? enumerable? own? etc.

Brendan Eich 2009/07/30 22:25

The shorthand {x, y} for {x: x, y: y} allowed on the left-hand side of assignment should be allowed on the right too, markm suggests.

Brendan Eich 2009/07/30 22:10

Allowing {x, y} in an object literal expression seems like a great idea. It seems useful since it makes patterns reversible and it also makes common programming patterns shorter:

var obj = (function() {
  function foo() {...}
  ...
  return {foo};
})();

Erik Arvidsson 2010/04/08 03:08

Agreed – see object initialiser shorthand.

Brendan Eich 2010/05/26 21:57

A non matching rest pattern is bound to an empty Array. This follows from symmetry with rest params.

[x, ...xs] = [0, 1]; // x = 0, xs = [1]
[y, ...ys = [0]; // y = 0, ys = []
[z, ...zs] = []; // z = undefined, zs = []

And the rest param symmetry just for reference:

function f(x, ...xs) {
  assertTrue(Array.isArray(xs));
}
f();

Erik Arvidsson 2010/11/05 22:26

 
harmony/destructuring.txt · Last modified: 2013/07/12 00:00 by rwaldron
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki