2

As I know spread operator return a new object after operation:

let a = {a:1};
let b = {a: 2}
let a = {...a,...b}

So, last a is not referenced to a, it is a new object in memory.

Is it possible to use spread operator without changing initial object, I mean a reference in memory?

POV
  • 11,293
  • 34
  • 107
  • 201
  • 2
    It's impossible - ES2018 guarantees it to always be a new object. PS: it's not operator, it's "spread syntax" – zerkms Oct 23 '18 at 23:24
  • Is there alternative? Like this: `var obj = Object.assign(updatedObject);` – POV Oct 23 '18 at 23:25
  • 1
    alternative to what? – zerkms Oct 23 '18 at 23:26
  • 1
    I don't think there's a better way than `Object.assign(a, b)` to add the properties of `b` to the existing object `a`. – Matt McCutchen Oct 23 '18 at 23:28
  • Alternative to modify current object and extend this by new properties – POV Oct 23 '18 at 23:29
  • 2
    If you want to keep the same object - just assign manually to its properties – zerkms Oct 23 '18 at 23:29
  • You mention wanting a reference, and mention adding properties, but your example is not at all clear, since both `a` and `b` have properties of the same name, and both properties are primitive values, which are not "passed by reference". – Heretic Monkey Oct 23 '18 at 23:34

2 Answers2

3

It's guaranteed by the 12.2.6.7 Runtime Semantics: Evaluation and 12.2.6.8 Runtime Semantics: PropertyDefinitionEvaluation that the object returned from an object literal with "spread syntax" is always a new object.

Relevant parts of the spec:

  1. Let obj be ObjectCreate(%ObjectPrototype%).

and

PropertyDefinition:...AssignmentExpression
1. Let exprValue be the result of evaluating AssignmentExpression.
2. Let fromValue be ? GetValue(exprValue).
3. Let excludedNames be a new empty List.
4. Return ? CopyDataProperties(object, fromValue, excludedNames).

There is currently no other way to mutate the object other than just assigning its properties directly.

As @Heretic Monkey noted in the comments: the question is tagged as typescript while I'm answering about javascript. The thing is that TS compiler must retain the native js runtime semantics, so in this very case it's okay to refer to the ES2018 standard.

zerkms
  • 249,484
  • 69
  • 436
  • 539
  • Note that the question is tagged `typescript`; the code given may or may not transpile into the spread syntax, depending on the transpilation target. – Heretic Monkey Oct 23 '18 at 23:37
  • @HereticMonkey it must retain the spread syntax semantics though – zerkms Oct 23 '18 at 23:37
  • 1
    @HereticMonkey it's interesting the TS spec does not mention object parameters spread syntax at all. – zerkms Oct 23 '18 at 23:40
  • If you look at the transpiled code [at this playground link](http://www.typescriptlang.org/play/index.html#src=let%20a%20%3D%20%7Ba%3A1%7D%3B%0D%0Alet%20b%20%3D%20%7Ba%3A%202%7D%0D%0Alet%20ab%20%3D%20%7B...a%2C...b%7D) (I changed the second `let a` to `let ab`) you can see they use `Object.assign` :(. – Heretic Monkey Oct 23 '18 at 23:44
  • 1
    Your answers is spot on, by the way, just wanted to warn you about TypeScript's... quirks. – Heretic Monkey Oct 23 '18 at 23:47
  • @HereticMonkey oh wow, even with `ES2018` as a target they still emit `Object.assign`, that's awkward. – zerkms Oct 23 '18 at 23:51
1

Spread operator only works on primitive types.

Here an example with primitive datatypes:

let x = {
  number: 1
}
let y = { ...x
}
x.number++
  console.log(x)
console.log(y)

Non Primitive:

let x1 = {
  numbers: {
    number: 1
  }
}
let y1 = { ...x1
}
x1.numbers.number++
  console.log(x1)
console.log(y1)

As you can see, the 2nd version still uses references, to bypass this use a deep clone function like this:

let x1 = {
  numbers: {
    number: 1
  }
}
let y1 = copy(x1)
x1.numbers.number++
  console.log(x1)
console.log(y1)

function copy(aObject) { // Deep Clone Object from https://stackoverflow.com/a/34624648/16642626
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

So if u truly want to keept the reference, you could nest the a and b in ur example so that the spread operator is not able to see the primitive types on the first layer

Branchverse
  • 1,203
  • 1
  • 7
  • 19