Is It Spread "Syntax" or the Spread "Operator"

Is it spread syntax or the spread operator ?

It's not an operator.

In all senses of the word, it's not one. It has been a huge misconception since it was introduced and despite popular opinion -- it's not one, and there are a few objective points to be made:

  • It doesn't fit the definition of an operator
  • It can't be used as an operator
  • The language specification implies that it's not an operator

It should be mentioned that spread syntax comes in different 'flavors', used in different contexts and are commonly referred to by different names while using the same punctuator. Spread syntax is basically an umbrella term for the application of the ... punctuator, and see Felix Kling's great answer detailing all the uses and names. More explanation about these individuals uses is given in the supplementary answer.

What is an operator?

Semantically, in the context of ECMAScript, operators are just builtin functions that take in arguments and evaluate to a single value -- written in prefix, infix, or postfix notation and usually with symbolic names such as + or /. From Wikipedia:

Simply, an expression involving an operator is evaluated in some way, and the resulting value may be just a value (an r-value), or may be an object allowing assignment (an l-value).

For example, the + operator results in a value such as 2, which is a right-hand-side expression, and the . operator results in an object allowing assignment such as foo.bar, a left-hand-side expression.

On the surface, the ... punctuator1 looks to be a prefix unary operator:

const baz = [foo, ...bar];

But the problem with that argument is that ...bar doesn't evaluate to a singular value; it spreads the iterable bar's elements, one by one. The same goes for spread arguments:

foo(...bar);

Here, foo receives separate arguments from the iterable bar. They're separate values being passed to foo, not just one value. It doesn't fit the definition of an operator, so it isn't one.

Why isn't it an operator?

Another point to be made is that operators are meant to be standalone and return a single value. For example:

const bar = [...foo];

As already mentioned, this works well. The problem arises when you try to do this:

const bar = ...foo;

If spread syntax were an operator, the latter would work fine because operators evaluate the expression to a single value but spread doesn't so it fails. Spread syntax and spread arguments only work in the context of arrays and function calls because those structures receive multiple values supplied by spreading array elements or arguments. Evaluating to multiple values is outside of the scope of what an operator is able to do.

What do the standards say?

The complete list of operators is listed in Clauses §12.5 through §12.15 in the ECMAScript 2015 Language Specification, the specification in which ... is introduced, which doesn't mention .... It can also be inferred that it's not an operator. The two main cases mentioned in this answer in which spread syntax is in a production, for function calls (spread arguments) or array literals (spread syntax) are described below:

ArrayLiteral :
[ Elisionopt ]
[ ElementList ]
[ ElementList , Elisionopt ]

ElementList :
Elisionopt AssignmentExpression
Elisionopt SpreadElement
ElementList , Elisionopt AssignmentExpression
ElementList , Elisionopt SpreadElement

Elision :
,
Elision ,

SpreadElement :
... AssignmentExpression

And for function calls:

CallExpression :
MemberExpression Arguments

Arguments :
( )
( ArgumentList )

ArgumentList :
AssignmentExpression
... AssignmentExpression
ArgumentList , AssignmentExpression
ArgumentList , ... AssignmentExpression

In these productions, there's a conclusion that can be made: that the spread 'operator' doesn't exist. As mentioned earlier, operators should be standalone, as in const bar = ...foo and evaluate to one single value. The syntax of the language prevents this, which means spread syntax was never meant to be standalone. It's an extension to array initializers and function calls, an extension to their grammar.

Why spread 'syntax'?

Syntax, as defined by Wikipedia:

In computer science, the syntax of a computer language is the set of rules that defines the combinations of symbols that are considered to be a correctly structured document or fragment in that language.

Syntax is basically the 'form' of the language, rules that govern what is legal or not regarding how the code should look, and how the code should be written. In this case, ECMAScript's grammar specifically defines the ... punctuator to only appear in function calls and array literals as an extension -- which is a rule that defines a combination of symbols (...foo) that are considered to be legal together, thus it is syntax similar to how an arrow function (=>) is not an operator, but syntax2.

Calling ... an operator is a misnomer. An operator is a builtin function that takes in arguments (operands) and is in the form of prefix, infix, or postfix notation and evaluates to exactly one value. ..., while satisfying the first two conditions, does not satisfy the last. ..., instead, is syntax because it is defined specifically and explicitly in the language's grammar. Thus, 'the spread operator' is objectively more correctly referred to as 'spread syntax'.


1 The term 'punctuator' refers to punctuators in ECMAScript 2015 and later specifications. These symbols include syntax components and operators, and are punctators of the language. ... is a punctuator itself, but the term 'spread syntax' refers to the whole application of the punctuator.

2 => itself is a punctuator, just as ... but what I'm referring to specifically is arrow function syntax, the application of the => punctuator ((…) => { … }), just as spread syntax refers to the application of the ... punctuator.

What is the role of the spread operator in this javascript code?

I know that the spread operator is used to receive array as a
parameter in a function

In that context, three dots ... are referred to as rest parameters syntax, not spread syntax. In the code included in your question, the context in which three dots ... are used, it is known as spread syntax.

In the code above, what does the spread operator do?

It iterates over the Set and spreads (adds) the entries in the Set in to a new array

Following code snippet shows a simple example:

const set = new Set([1,2,3]);
const newArr = [...set];

console.log(newArr);

Javascript Spread Operator used with return

Spread syntax can be used when all elements from an object or array need to be included in a list of some kind.

let arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
arr1 = [...arr1, ...arr2];

In your question, what's happening is that, userCanDeclined is checked if its value is true, if so, return an array in which the array below is spread(its values copied into the new returned array)

 [
{
label: 'Decline Draw',
icon: 'ban',
action: () => (el, ev) => {
Service.decline(this.onCloseView);
},
}
]

Else, spread or copy an empty array if false

  []

I don't understand about spread syntax inside objects

The object spread is quite different. It maps to Object.assign() internally.

So const a = {...1} is same as const a = Object.assign({}, 1)
Here Object.assign({},1) has treated 1 as object not as number. Therefore, you did not get any exception thrown.

Additionally if you have tried same thing for arrays [...1] it should have thrown error, since it does not treats 1 as object and you get the same behavior as ..1.

To summarize:

console.log({...false}) => console.log(Object.assign({}, false))
console.log({...1}) => console.log(Object.assign({}, 1))
console.log({...null}) => console.log(Object.assign({}, null))
console.log({...undefined}) => console.log(Object.assign({}, undefined))

PS: Object.assign() spec

Spread Syntax ES6

  1. In your example given, there is essentially no difference between the two
  2. .concat is significantly more efficient: http://jsperf.com/spread-into-array-vs-concat because ... (spread) is merely syntax sugar on top of more fundamental underlying syntax that explicitly iterates over indexes to expand the array.
  3. Spread allows sugared syntax on top of more clunky direct array manipulation

To expand on #3 above, your use of spread is a somewhat contrived example (albeit one that will likely appear in the wild frequently). Spread is useful when - for example - the entirety of an arguments list should be passed to .call in the function body.

function myFunc(){
otherFunc.call( myObj, ...args );
}

versus

function myFunc(){
otherFunc.call( myObj, args[0], args[1], args[2], args[3], args[4] );
}

This is another arbitrary example, but it's a little clearer why the spread operator will be nice to use in some otherwise verbose and clunky situations.

As @loganfsmyth points out:

Spread also works on arbitrary iterable objects which means it not only works on Arrays but also Map and Set among others.

This is a great point, and adds to the idea that - while not impossible to achieve in ES5 - the functionality introduced in the spread operator is one of the more useful items in the new syntax.


For the actual underlying syntax for the spread operator in this particular context (since ... can also be a "rest" parameter), see the specification. "more fundamental underlying syntax that explicitly iterates over indexes to expand the array" as I wrote above is enough to get the point across, but the actual definition uses GetValue and GetIterator for the variable that follows.

Does Spread Syntax create a shallow copy or a deep copy?

A variable can contain either a value (in case of primitive values, like 1), or a reference (in case of objects, like { food: "pasta" }. Primitive types can only be copied, and since they contain no properties, the shallow/deep distinction does not exist.

If you considered references themselves as primitive values, b = a is a copy of a reference. But since JavaScript does not give you direct access to references (like C does, where the equivalent concept is a pointer), refering to copying a reference as "copy" is misleading and confusing. In context of JavaScript, "copy" is a copy of a value, and a reference is not considered a value.

For non-primitive values, there are three different scenarios:

  • Coreferences, as created by b = a, merely point to the same object; if you modify a in any way, b is affected the same way. Intuitively you'd say that whichever object you modify it is reflected in the copy, but it would be wrong: there is no copy, there is just one object. Regardless of which reference you access the object from, it is the same entity. It is like slapping Mr. Pitt, and wondering why Brad is mad at you — Brad and Mr. Pitt are the same person, not clones.

let a = {
food: "pasta",
contents: {
flour: 1,
water: 1
}
}
let b = a;
a.taste = "good";
a.contents.flour = 2;
console.log(b);

JavaScript spread operator and conditional, why it doesn't work for arrays?

Ok so according to the docs the spread operator initially worked only with iterables(such as arrays, objects are not iterable), the thing that you can use it with objects it's because they added this feature later in time but in fact the spread operator used with objects is a "wrapper" of Object.assign.

Said that, in your case:

Case with objects:

console.log({
...(true && { foo: 'bar' }),
...(false && { bar: 'baz' }),
});

Works because basically, on the 'falsy' condition it's doing:

console.log(Object.assign({}, false)) // Output: { }

Case with arrays:

console.log([
'foo',
...(true && ['bar']),
...(false && ['baz']),
]);

In this case doesn't work because on the 'falsy' condition js will search for a iterable method on false which of course doesn't exists so it raises the error

Can the 'spread operator' replace an object with same uuid? Solution: Object.assing()

I can suggest these ways, through a filter, a map and an object.

But filter way changes the order of elements in array

const origArray = [
{"uuid":"c752cf08","name":"Team 1",},
{"uuid":"d46829db","name":"Team 2",},
{"uuid":"d46829d0","name":"Team 3",}];

const match = 1;
const name = 'Team 100';

//------------------------
const workWithFilter = (prev) =>
[...prev.filter((_, i) => i !== match), { ...prev[match], name }];

const result1 = workWithFilter(origArray);
console.log('workWithFilter:', result1);

//------------------------
const workWithMap = (prev) =>
prev.map((v, i) => (i === match) ? { ...v, name } : v);

const result3 = workWithMap(origArray);
console.log('workWithMap:', result3);

//------------------------
const workWithObject = (prev) =>
Object.assign([...prev], { [match]: { ...prev[match], name } });

const result4 = workWithObject(origArray);
console.log('workWithObject:', result4);

//------------------------
const doesWork = (prev) => {
let old = [...prev]
old.splice(match, 1, { ...prev[match], name });
return old;
}

const result2 = doesWork(origArray);
console.log('doesWork:', result2);
.as-console-wrapper {max-height: 100% !important; top: 0}


Related Topics



Leave a reply



Submit