Why can't Javascript spread operator be used after object key? - javascript

I have the following code:
const array = [{
a: 'a',
b: 'b'
}];
console.log(...array);
const store = {
obj: ...array
}
console.log will print the results just fine. However, when trying to set the key of store I get a Parsing error: Unexpected token.
Isn't ...array a valid object to assign to the obj key of store?

... spreads out array into individual items. Array can have more than 1 element and hence there will be more than 1 RHS and that will be invalid. Hence, you can use obj : {...array} or obj : [...array]
const array = [{a: 'a',b: 'b'},{c: 'c', d: 'd'}];
console.log(...array);
const store = {
obj: {...array},
obj1: [...array]
};
console.log(store);

The spread syntax works inside of objects or iterable. In your case, you need to spread the elements within an array.
Spread Syntax
Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
const array = [0, 1, 2]
const store = {
obj: [...array] // <-- the array is being spreded into an array.
}
console.log(store)

Related

Why object merges properties but array does not merges values

Can someone tell why object merges values but array does not
See the code block below:
const a = {'a': 1, 'b': 2}
const b = {'b': 4, 'c': 3}
console.log({...a, ...b})
This Outputs
{ a: 1, b: 4, c: 3 }
But when I use the code below:
const c = [1,2]
const d = [2,3]
console.log([...c, ...d])
This outputs
[ 1, 2, 2, 3 ]
Why object merges properties...
It doesn't merge properties, it merges objects. Notice the value of b in your result: It's 4 (the value from the b object), not some merged value of 2 (from the a object) and 4 (from the b object). Each property from each source object is just copied into the target object (with later objects overwriting properties from earlier objects), the properties themselves are not merged together.
But fundamentally, object property spread and iterable spread are just completely different things with different purposes and different semantics, because objects and arrays are different beasts (at least conceptually; arrays actually are objects in JavaScript). Properties have names which are an intrinsic part of the property. Array elements just have indexes, and it's normal for values to be moved around an array (moved to different indexes). The two different definitions of spread are each useful for the data type they're defined for.
If you want to treat an array like an object, though, you can since arrays are objects in JavaScript. (Although in this case it isn't useful.) Here's an example (I've changed c's element values so it's clear what's coming from where):
const c = ["a", "b"];
const d = [2, 3];
console.log(Object.assign([], c, d));
In that case, since d has values for both indexes 0 and 1, none of c's elements are in the result. But:
const c = ["a", "b", "c", "d", "e"];
const d = [2, 3];
console.log(Object.assign([], c, d));
Short answer
When using the spread operator, Regular Objects are ASSIGNED.
When using the spread operator, Arrays are CONCATENATED.
Long Answer
I believe the source of your confusion is that every array in JavaScript is just an object belonging to the Array constructor. So why doesn't joining two or more arrays with the spread operator work the same way as objects do?
Let's analyze what is happening in case of the Object
const a = {'a': 1, 'b': 2};
const b = {'b': 4, 'c': 3};
console.log({...a, ...b}); // Output: { a: 1, b: 4, c: 3 }
console.log(Object.assign({}, a, b)); // Output: { a: 1, b: 4, c: 3 }
console.log({...b, ...a}); // Output: { a: 1, b: 2, c: 3 }
console.log(Object.assign({}, b, a)); // Output: { a: 1, b: 2, c: 3 }
An object is a data structure holding key:value pairs.
Object assignment overwrites the keys with the latest values.
The key b occurs in more than one object and is overwritten with it's latest value. As you can see, if you change the order of the objects spread/assigned, the resulting value of the value of b changes based on the latest object having b.
Now let's come to the Array.
const c = [1,2];
const d = [2,3];
console.log([...c, ...d]); // Output: [ 1, 2, 2, 3 ]
console.log(c.concat(d)); // Output: [ 1, 2, 2, 3 ]
console.log(Object.assign({}, c, d)); // Output: { '0': 2, '1': 3 }
console.log(Object.values(Object.assign({}, c, d))); // Output: [ 2, 3 ]
An array is an object created with the Array constructor which outputs the array as a collection of the values assigned to its keys.
Array concatenation simply joins the arrays.
As you can see above, Object.assign still works on an array because the array is technically an object and it behaves exactly how Object.assign is supposed to work. The keys in this case are simply what we call "index" in an array. This is why when you do array[index] it returns the value, it's the same as object[key] that returns a value. If keys exist, the Object.assign replaces the keys/index with the latest values, else it adds the key-value pair to the object.
Conclusion:
Thus, the difference is how the spread operator works for objects and arrays.
In Objects, spread does Object.assign.
In Arrays, spread does Array concatenation => arrayA.concat(arrayB, arrayC, ...)
Bonus: Set
However, if you want the array to return only unique values, you have to use the Set data structure.
const c = [1,2];
const d = [2,3];
console.log([...new Set([...c, ...d])]); // Output: [1, 2, 3]
console.log(Array.from(new Set(a.concat(b)))); // Output: [1, 2, 3]

set new value for array's value using destructure

i got two noob questions about destructure an array:
1st question: when destructuring an object, I can define a new value or a new key or both. On array, can I add a new value without add a new key?
const obj = {a: undefined, b:2};
const {a = 3, b} = obj;
console.log(a); // 3
I want to know if there is a version of this but with array instead.
2nd question: is it possible to do not provide a default value for objects? Considering that I think that it is not possible to change default values using destructure.
const obj = [1, {a: 1, b:2}, 3, 4];
const [, object, three, four] = obj;
console.log(object); //{a: 1, b:2}
In this example, object returns {a: 1, b:2} but I wanted it change the value instead. Is that possible?
thanks, regards.
You are confusing default values with mutation of values, and assignment of values to variables with mutation of objects. Below is a demo of the default value feature of destructuring, with comments to explain the behavior.
You will see here that in general, destructuring is not designed for mutation of objects, but for extraction of variables and values. And hopefully also get a feel for why it would be undesirable for mutation to be mixed in to it, even if it were possible.
const obj = [1, {a: 1, b:2, 99:'z'}, ,3, 4, {mutateme: 1}];
const [, {a=3,b=4,c=5}, object={a:7,b:7},three, four, object2] = obj;
// a prop has value=1, b has value=2, c is not defined use default value 5
console.log(a,b,c,object);
//object is empty use default value={a:7,b:7}
// obj is unchanged
console.log(obj)
// mutate object2={mutateme:1} by reference (like a pointer)
object2.mutateme=7
// {mutateme: 1=>7}
console.log(obj)
// example of how you could (sort of) mutate inside a destructuring statement
// computed property, obj[1]=obj[3]=99 returns 99,
// so extract property 99 to variable z and mutate object obj at index [1] and [3] to =99
// y will 99 now.
const [y1, {[obj[1]=obj[3]=99]:z},, y2 ] = obj
console.log(y1, z, y2)
// if something similar were built into destructuring syntax,
// can you imagine how confusing it could get, and cause of all kinds of unexpected behavior?

Why does {. . . .0} evaluate to {}?

I just found {....0} in friend's code. Evaluating it in console returns {} (empty object).
Why is that? What is the meaning of 4 dots in JavaScript?
Four dots actually have no meaning. ... is the spread operator, and .0 is short for 0.0.
Spreading 0 (or any number) into an object yields an empty object, therefore {}.
Three dots in an object literal are a spread property, e.g.:
const a = { b: 1, c: 1 };
const d = { ...a, e: 1 }; // { b: 1, c: 1, e: 1 }
The last dot with a 0 is a number literal .0 is the same as 0.0. Therefore this:
{ ...(0.0) }
spreads all properties of the number object into the object, however as numbers don't have any (own) properties you get back an empty object.
In a simple terms {...} spread operator in javascript extends one object/array with another.
So, when babelifier tries extending one with another, it has to identify whether it is trying to extend an array or an object.
In the case of array, it iterates over elements.
In the case of object, it iterates over keys.
In this scenario, the babelyfier is trying to extract keys for number by checking the Object's own property call which is missing for number so it returns empty Object.
Spread operator {...} allows iterables to expand. It means that those data types that can be defined in form of key-value pairs can be expanded. In terms of Object we call key-value pair as Object property and it's value whereas in terms of arrays we can think index as key and element in array as it's value.
let obj = { a: 4, b: 1};
let obj2 = { ...obj, c: 2, d: 4}; // {a: 4, b: 1, c: 2, d: 4}
let arr1 = ['1', '2'];
let obj3 = { ...arr1, ...['3']}; // {0: "3", 1: "2"}
In terms of array, as it takes index as key so here it replaces element '1' of arr1 with '3' because both of them have same index in different array.
With strings too spread operator returns non-empty object. As string is an array of character so it treats string as an array.
let obj4 = {...'hi',...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
let obj5 = {...'y',...'x'} // {0: "x" }
But with other primitive data types it return empty object
with Numbers
let obj6 = { ...0.0, ...55} // {}
with Boolean
let obj7 = { ...true, ...false} // {}
In conclusion those data types that can be treated in form of key-value pairs when used with spread operator {...} returns non-empty object otherwise it returns empty object {}

Array destructuring in JavaScript

I have this code in my vue-js app:
methods: {
onSubmit() {
ApiService.post('auth/sign_in', {
email: this.email,
password: this.password,
})
.then((res) => {
saveHeaderToCookie(res.headers);
this.$router.push({ name: 'about' });
})
.catch((res) => {
this.message = res.response.data.errors[0];
this.msgStatus = true;
this.msgType = 'error';
});
},
}
While running Eslint I got an error saying "Use array destructuring" (prefer-destructuring) at this line:
this.message = res.response.data.errors[0];
What is array destructuring and how to do this? Please provide me a concept on this. I've researched it but could not figure it out.
Destucturing is using structure-like syntax on the left-hand-side of an assignment to assign elements of a structure on the right-hand-side to individual variables. For exampple,
let array = [1, 2, 3, 4];
let [first, _, third] = array;
destructures the array [1, 2, 3] and assigns individual elements to first and third (_ being a placeholder, making it skip the second element). Because LHS is shorter than RHS, 4 is also being ignored. It is equivalent to:
let first = array[0];
let third = array[2];
There is also an object destructuring assignment:
let object = {first: 1, second: 2, third: 3, some: 4};
let {first, third, fourth: some} = object;
which is equivalent to
let first = object.first;
let third = object.third;
let fourth = object.some;
Spread operator is also permitted:
let [first, ...rest] = [1, 2, 3];
would assign 1 to first, and [2, 3] to rest.
In your code, it says you could do this instead:
[this.message] = res.response.data.errors;
The documentation on prefer-destructuring lays out what it considers to be "correct".
U can rewrite that line as [this.message] = res.response.data.errors; and that es-lint error will go off. See this example for better understanding
var x = {
y: {
z: {
w: [3, 4]
}
}
};
function foo() {
[this.a] = x.y.z.w
console.log(this.a);
}
foo() // prints 3
For more information about array destructuring please see here
Always look things up on MDN if you want to find out about javascript things. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring
Here's a simple example of destructuring:
const [a, b] = ['a', 'b'];
Its a shorthand available since es6 that allows doing variable assignment in a more shorthand way.
The original way would be like:
const arr = ['a', 'b'];
const a = arr[0];
const b = arr[1];
And the es6 way would be like:
const arr = ['a', 'b'];
const [a, b] = arr;
Now in regards to the eslint error, I actually disagree with that one. Your code by itself should be fine. So you should file an issue on the Eslint github repo to ask about why that line is triggering the "prefer-destructuring" warning.
Beside of the given destructuring assignments, you could take an object destructuring for an array if you like to take certain elements, like the 11th and 15th element of an array.
In this case, you need to use the object property assignment pattern [YDKJS: ES6 & Beyond] with a new variable name, because you can not have variables as numbers.
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
{ 11: a, 15: b } = array;
console.log(a, b);
Destructuring is a method of extracting multiple values from data stored in (possibly nested) objects and Arrays. It can be used in locations that receive data or as the value of objects. We will go through some examples of how to use destructuring:
Array Destructuring
Array destructuring works for all iterable values
const iterable = ['a', 'b'];
const [x, y] = iterable;
// x = 'a'; y = 'b'
Destructuring helps with processing return values
const [all, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
Object Destructuring
const obj = { first: 'Jane', last: 'Doe' };
const {first: f, last: l} = obj;
// f = 'Jane'; l = 'Doe'
// {prop} is short for {prop: prop}
const {first, last} = obj;
// first = 'Jane'; last = 'Doe'
Examples of where to use Destructuring
// Variable declarations:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];
// Assignments:
[x] = ['a'];
// Parameter definitions:
function f([x]) { ··· }
f(['a']);
// OR USE IT IN A FOR-OF loop
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(index, element);
}
// Output:
// 0 a
// 1 b
Patterns for Destructuring
There are two parties involved in any destructuring
Destructuring Source: The data to be destructured for example the right side of a destructuring assignment.
Destructuring Target: The pattern used for destructuring. For example the left side of a destructuring assignment.
The destructuring target is either one of three patterns:
Assignment target: Usually an assignment target is a variable. But in destructuring assignment you have more options. (e.g. x)
Object pattern: The parts of an object pattern are properties, the property values are again patterns (recursively) (e.g. { first: «pattern», last: «pattern» } )
Array pattern: The parts of an Array pattern are elements, the elements are again patterns (e.g. [ «pattern», «pattern» ])
This means you can nest patterns, arbitrarily deeply:
const obj = { a: [{ foo: 123, bar: 'abc' }, {}], b: true };
const { a: [{foo: f}] } = obj; // f = 123
**How do patterns access the innards of values? **
Object patterns coerce destructuring sources to objects before accessing properties. That means that it works with primitive values. The coercion to object is performed using ToObject() which converts primitive values to wrapper objects and leaves objects untouched. Undefined or Null will throw a type error when encountered. Can use empty object pattern to check whether a value is coercible to an object as seen here:
({} = [true, false]); // OK, Arrays are coercible to objects
({} = 'abc'); // OK, strings are coercible to objects
({} = undefined); // TypeError
({} = null); // TypeError
Array destructuring uses an iterator to get to the elements of a source. Therefore, you can Array-destructure any value that is iterable.
Examples:
// Strings are iterable:
const [x,...y] = 'abc'; // x='a'; y=['b', 'c']
// set value indices
const [x,y] = new Set(['a', 'b']); // x='a'; y='b’;
A value is iterable if it has a method whose key is symbol.iterator that returns an object. Array-destructuring throws a TypeError if the value to be destructured isn't iterable
Example:
let x;
[x] = [true, false]; // OK, Arrays are iterable
[x] = 'abc'; // OK, strings are iterable
[x] = { * [Symbol.iterator]() { yield 1 } }; // OK, iterable
[x] = {}; // TypeError, empty objects are not iterable
[x] = undefined; // TypeError, not iterable
[x] = null; // TypeError, not iterable
// TypeError is thrown even before accessing elements of the iterable which means you can use empty Array pattern [] to check if value is iterable
[] = {}; // TypeError, empty objects are not iterable
[] = undefined; // TypeError, not iterable
[] = null; // TypeError, not iterable
Default values can be set
Default values can be set as a fallback
Example:
const [x=3, y] = []; // x = 3; y = undefined
Undefined triggers default values

Difference between Object.assign and object spread (using [...] syntax)?

I have some code here and I was wondering if it is the same thing or different. I am pretty sure these are both suppose to be the same but I wasnt sure if I was doing it right.
let zoneComment = updatedMap[action.comment.zone]
? [...updatedMap[action.comment.zone]] : [];
let zoneComment = updatedMap[action.comment.zone]
? Object.assign([], updatedMap[action.comment.zone]) : [];
If these are the same then which should I use or does it matter? I want to use best practice so if it is your OPINION of which is better then please state so.
In your particular case they are not the same.
The reason is that you have an array, not an object.
Doing ... on an array will spread out all the elements in the array (but not the properties)
Doing Object.assign expects an object so it will treat an array as an object and copy all enumerable own properties into it, not just the elements:
const a = [1, 2, 3];
a.test = 'example';
const one = [...a] // [1, 2, 3];
const two = Object.assign([], a); // { '0': 1, '1': 2, '2': 3, 'test': 'example' }
console.log('\none');
for (let prop in one) {
console.log(prop);
}
console.log('\ntwo');
for (let prop in two) {
console.log(prop);
}
However, if you compare the ... operator applied on an object with Object.assign, they are essentially the same:
// same result
const a = { name: 'test' }
console.log({ ...a })
console.log(Object.assign({}, a))
except ... always creates a new object but Object.assign also allows you to mutate an existing object.
// same result
const a = { name: 'test' }
const b = { ...a, name: 'change' };
console.log(a.name); // test
Object.assign(a, { name: 'change'})
console.log(a.name); // change
Keep in mind that Object.assign is already a part of the language whereas object spread is still only a proposal and would require a preprocessing step (transpilation) with a tool like babel.
To make it short, always use ... spread construction and never Object.assign on arrays.
Object.assign is intended for objects. Although arrays are objects, too, it will cause a certain effect on them which is useful virtually never.
Object.assign(obj1, obj2) gets values from all enumerable keys from obj2 and assigns them to obj1. Arrays are objects, and array indexes are object keys, in fact.
[...[1, 2, 3], ...[4, 5]] results in [1, 2, 3, 4, 5] array.
Object.assign([1, 2, 3], [4, 5]) results in [4, 5, 3] array, because values on 0 and 1 indexes in first array are overwritten with values from second array.
In the case when first array is empty, Object.assign([], arr) and [...arr] results are similar. However, the proper ES5 alternative to [...arr] is [].concat(arr) and not Object.assign([], arr).
Your question really bubbles down to:
Are [...arr] and Object.assign([], arr) providing the same result when arr is an array?
The answer is: usually, yes, but:
if arr is a sparse array that has no value for its last slot, then the length property of the result will not be the same in both cases: the spread syntax will maintain the same value for the length property, but Object.assign will produce an array with a length that corresponds to the index of the last used slot, plus one.
if arr is a sparse array (like what you get with Array(10)) then the spread syntax will create an array with undefined values at those indexes, so it will not be a sparse array. Object.assign on the other hand, will really keep those slots empty (non-existing).
if arr has custom enumerable properties, they will be copied by Object.assign, but not by the spread syntax.
Here is a demo of the first two of those differences:
var arr = ["abc"]
arr[2] = "def"; // leave slot 1 empty
arr.length = 4; // empty slot at index 3
var a = [...arr];
var b = Object.assign([], arr);
console.log(a.length, a instanceof Array); // 4, true
console.log(b.length, b instanceof Array); // 3, true
console.log('1' in arr); // false
console.log('1' in a); // true (undefined)
console.log('1' in b); // false
If however arr is a standard array (with no extra properties) and has all its slots filled, then both ways produce the same result:
Both return an array. [...arr] does this by definition, and Object.assign does this because its first argument is an array, and it is that object that it will return: mutated, but it's proto will not change. Although length is not an enumerable property, and Object.assign will not copy it, the behaviour of the first-argument array is that it will adapt its length attribute as the other properties are assigned to it.
Both take shallow copies.
Conclusion
If your array has custom properties you want to have copied, and it has no empty slots at the end: use Object.assign.
If your array has no custom properties (or you don't care about them) and does not have empty slots: use the spread syntax.
If your array has custom properties you want to have copied, and empty slots you want to maintain: neither method will do both of this. But with Object.assign it is easier to accomplish:
a = Object.assign([], arr, { length: arr.length });

Categories