Search code examples
javascriptecmascript-6destructuringobject-destructuring

Why aren't these destructuring assignments equivalent?


I'm trying to nest these destructuring assignments such that context1 and context2 are initialized to market[pair.context] and market[pair.target] respectively:

// set market to this[pair.market] or empty object
const {
  [pair.market]: market = {},
} = this; 

// set context1 to market[pair.context] or empty object
// set context2 to market[pair.target] or empty object
const {
  [pair.context]: context1 = {},
  [pair.target]: context2 = {},
} = market;

I thought this correct way would be like this:

const {
  [pair.context]: context1 = {},
  [pair.target]: context2 = {},
} = {
  [pair.market]: market = {},
} = this;

However, when market[pair.context] or market[pair.target] already exist, it doesn't seem to behave as expected.

I'm fairly new to destructuring, but I'm determined to master it. Why is this the case, and how can I combine the first two destructures?


Relevant code for testing:

const pair1 = {
  context: 'A',
  target: 'B',
  market: 'MARKET1',
  price: '0.1',
};
const pair2 = {
  context: 'A',
  target: 'C',
  market: 'MARKET1',
  price: '1',
};
const pair3 = {
  context: 'C',
  target: 'B',
  market: 'MARKET2',
  price: '0.1',
};

// markets
function addPair (pair) {
  const {
    [pair.market]: market = {},
  } = this;

  const {
    [pair.context]: context1 = {},
    [pair.target]: context2 = {},
  } = market;

  this[pair.market] = {
    ...market,
    [pair.context]: {
      ...context1,
      [pair.target]: {
        price: +(pair.price),
      },
    },
    [pair.target]: {
      ...context2,
      [pair.context]: {
        price: +(1 / pair.price),
      },
    },
  };
}

const markets = {};

addPair.call(markets, pair1);
addPair.call(markets, pair2);
addPair.call(markets, pair3);

console.log(markets);

Solution

  • Destructuring assignment always evaluates to the right-hand-side of the assignment (which is true for all assignment). For example:

    const { foo, bar } = foobar;
    

    Will always evaluate to foobar and not foo nor bar because there is no way to evaluate to two values. That said, when you chain together things like this:

    const { foo } = { bar } = foobar;
    

    You are:

    1. Creating an implicit global bar (assuming you're not declaring it first) — not good
    2. Assigning foo as destructured from foobar

    First, the interpreter sees const { foo } = …. Then it evaluates the right-hand-side of that destructuring assignment to see what it will destructure, { bar } = foobar. Essentially { bar } = foobar will be performed first effectively (because it's evaluated in order to get the right-hand-side of the outer assignment), then will evaluate to foobar as mentioned above. Then const { foo } = foobar is performed. So:

    const { x } = { y } = {
      x: 1,
      y: {
        x: 6
      }
    }
    

    Will give you x as 1 and y as { x: 6 } — not x as 6 as assumed. It's the same as:

    const obj = {
      x: 1,
      y: {
        x: 6
      }
    }
    const { x } = obj;
    ({ y } = obj); //No declarator, parentheses for "expression coercion"
    

    But there's also ES2015 syntax that allows you to deeply nest destructuring assignment:

    const {
      [pair.market]: market = {},
      [pair.market]: {
        [pair.context]: context1 = {},
        [pair.target]: context2 = {}
      }
    } = this; 
    

    I would never choose brevity over readability so you're just fine with two separate deconstructions.