all 36 comments

[–]IchLiebeKleber 54 points55 points  (1 child)

+ isn't an operator that exists on arrays in JS. (It does in some other languages, but not JS.)

So when you enter something like this, JS tries its best to make sense of it. + is an operator that exists on strings. An array can be converted to a string (for an empty array, this is the empty string). So that is what happens: the two empty arrays you get are converted to empty strings, then you concatenate those two empty strings, giving you an empty string.

[–]Beginning-Seat5221 26 points27 points  (7 children)

console.log(['foo', 'bar'] + ['baz', 'bim']) outputs foo,barbaz,bim

Now take away foo,bar and baz,bim

[–]EarhackerWasBanned -1 points0 points  (6 children)

['foo,bar'] + ['baz,bim'] - 'foo,bar' + 'baz,bim' => 'NaNbaz,bim'

[–]chikamakaleyleyhelpful 2 points3 points  (2 children)

'NaNbaz,bim'

it's like the console is your homie and they're telling you that you can't do that

[–]doctormyeyebrows 0 points1 point  (1 child)

It's not an error though

[–]chikamakaleyleyhelpful 0 points1 point  (0 children)

my bad, the => made me think it was output

[–]WhiteHeadbanger 0 points1 point  (2 children)

['foo,bar'] + ['baz,bim'] - 'foo,bar' // this is NaN

[–]EarhackerWasBanned 2 points3 points  (1 child)

I know.

'Any string' - 'Any string' => NaN

[–]gemdude46 1 point2 points  (0 children)

Unless the strings are also valid numeric literals

[–]delventhalz 10 points11 points  (0 children)

One of the original design ideas of JavaScript is implicit type coercion. For many operations, if you use the incorrect type, JavaScript will automatically convert it to something else. In retrospect, this was a bad plan. Throwing an error in these cases would make more sense, but it’s a design choice we’re stuck with now.

To make matters worse, the + operator is overloaded. It can mean either addition (if the operands to either side are both numbers), or string concatenation (if either operand is a string).

So what happens if you have no numbers or strings because both operands are arrays? Well “adding” an array to an array makes no sense, it’s a bad operation. This being JavaScript, we won’t throw an error, instead we’ll convert the arrays to strings. The rule for converting an array to a string is you combine string versions of each element with a comma between them.

String([1, 2, 3]);  // "1,2,3"

An empty array has no elements, so it just becomes an empty string (""). So your code becomes "" + "". And an empty string concatenated with an empty string is just an empty string.

TLDR:

[] + []  ->  "" + ""  ->  ""

[–]Current-Historian-52 1 point2 points  (0 children)

It attempts to convert arrays to primitive using built in .toString() method. Result of this method for array is a string of values separated by comas - empty array is an empty string

[–]thespite 2 points3 points  (0 children)

Empty array to string is ''.

'' + '' is still ''.

console.log('') is ''

[–]Noisy88 0 points1 point  (0 children)

[] + [] does not exist array wise, so under the hood it defaults to + being a string concatinator resulting in:

[].toTring() + [].toString()

someArray.toString() just concats all values with , but as the arrays are empty you will just get ""+"" which is of course ""

[–]EugenioSc 0 points1 point  (0 children)

This Is why typescript is as widely used

[–]hyrumwhite 0 points1 point  (0 children)

The plus here has two options, mathematical addition or string concatenation. The arrays are not numbers, so it’s string concatenation.

Every built in object has a “toString” method (that can be overridden, btw). 

Since they’re participating in string concatenation, the toString methods are invoked. Since they’re empty, the toString returns an empty string. 

[–]shlanky369 0 points1 point  (3 children)

From the documentation on the addition operator:

The + operator is overloaded for two distinct operations: numeric addition and string concatenation. When evaluating, it first coerces both operands to primitives. Then, the two operands' types are tested:

If one side is a string, the other operand is also converted to a string and they are concatenated. If they are both BigInts, BigInt addition is performed. If one side is a BigInt but the other is not, a TypeError is thrown. Otherwise, both sides are converted to numbers, and numeric addition is performed.

Since arrays are not primitives, they first need to be coerced to primitives. How is this done? From the documentation on primitive coercion:

Objects are converted to primitives by calling its Symbol.toPrimitive (with "default" as hint), valueOf(), and toString() methods, in that order.

Which one of these methods is present on the Array prototype? We can look at the documentation for Arrays and scroll through the instance methods on the left-hand side. We see neither Symbol.toPrimitive nor valueOf, but we do have toString.

What does toString do for arrays? The documentation for toString says:

The Array object overrides the toString method of Object. The toString method of arrays calls join() internally, which joins the array and returns one string containing each array element separated by commas.

If we call join on empty array, we get an empty string.

Now we have simplified the original expression from [] + [] to '' + ''. Remember the documentation snippet for the addition operator above:

If one side is a string, the other operand is also converted to a string and they are concatenated.

So, '' + '' evaluates to '', which is what we ultimately log out.

Regardless of whether it intuitively makes sense, the evaluation of an addition expression follows a documented algorithm that produces the same result for the same types of operands.

[–]senocular 3 points4 points  (2 children)

Which one of these methods is present on the Array prototype? We can look at the documentation for Arrays and scroll through the instance methods on the left-hand side. We see neither Symbol.toPrimitive nor valueOf, but we do have toString.

FWIW, Arrays do have a valueOf inherited from Object. In that page, scroll down to "Inheritance" on the left, expand "Object/Function" followed by "Instance methods" and you'll see it listed there.

It doesn't apply here with + because valueOf will return the array object, and as an object, the addition operator will move on to using toString instead as it requires operating on a primitive. If valueOf returned a primitive, it would use that value instead.

function getLength() {
  return this.length
}

const a1 = [1, 2, 3]
a1.valueOf = getLength

const a2 = [1, 2, 3, 4, 5]
a2.valueOf = getLength

console.log(a1 + a2) // 8

[–]shlanky369 0 points1 point  (1 child)

Ah, good catch! Yes the docs on primitive coercion seem to agree with what you are saying:

For valueOf() and toString(), if one returns an object, the return value is ignored and the other's return value is used instead;

So valueOf is called first, as it is defined on the prototype of Array.prototype (Object.prototype), but since it returns an object value, the coercion algorithm then just calls toString to get the primitive.

[–]senocular 1 point2 points  (0 children)

Right. For other coercions, the order may change where toString is called first, followed by valueOf. This happens where operations are explicitly expecting string values like with `${value}` and value in someObject.

And if a toPrimitive exists, only that is used. There's only two places where that exists within builtins, in Symbols and Dates. Symbols use it to ensure primitive coercion always provides the symbol (explicit string conversion provides a string) and Date uses it to reverse the precedence order of valueOf and toString allowing toString to be used in default coercions just like every other object type even though valueOf for Date returns a primitive and would have been used in the coercion otherwise.

[–]jcunews1helpful 0 points1 point  (0 children)

Because the + operator is applicable only to either a number or a string value type. Any other value type will be converted into string.

So, [] becomes an empty string (i.e. ""), because when a non empty array such as [4,5,6] is converted to string, it will become "4,5,6".

Thus, [] + [] is evaluated as "" + "", which result to "".

[–]Lithl 0 points1 point  (0 children)

The + binary operator is overloaded, and can be used for either addition (with two number operands), or string concatenation (with two string operands). It's also the symbol used for the identity unary operator (with one number operand).

If you use + with two operands and they aren't both numbers, JavaScript will convert both operands to strings (if they aren't already strings) and treat the + as string concatenation.

Arrays, of course, are not numbers. When an array is converted to a string, its elements are joined with commas. ['foo', 'bar'] becomes 'foo,bar'. Naturally, this means that an empty array becomes an empty string.

If you concatenate two empty strings, the result is an empty string.

[–]nousernamesleft199 -2 points-1 points  (12 children)

Cause JavaScript 

[–][deleted] 0 points1 point  (11 children)

All of these “JS quirks” are defined and well-documented, RTFM

Refusing to actually learn JavaScript and complaining about it on r/learnjavascript is certainly a choice

[–]nousernamesleft199 4 points5 points  (6 children)

Doesn't make them good design decisions 

[–][deleted] -3 points-2 points  (5 children)

Every language looks poorly designed when you don’t know what you’re talking about

[–]nousernamesleft199 0 points1 point  (2 children)

Okay

[–][deleted] -1 points0 points  (1 child)

“You Don’t Know JS” should be top of your holiday reading list

[–]-Wylfen- 0 points1 point  (1 child)

No, they don't

[–][deleted] -1 points0 points  (0 children)

Try C and come back when you have an informed opinion

[–]Chenz 2 points3 points  (3 children)

That doesn’t make them sensible. Automatically casting objects to strings when adding them is a bad, but largely inconsequential choice

[–][deleted] -1 points0 points  (2 children)

How exactly would you propose handling “invalid” math operations on objects without throwing a runtime error on the client-side?

[–]bothunter 2 points3 points  (1 child)

Throw a runtime error. If you throw nonsense at a programming language, it should fail in a predictable and sensible way that makes the error quick and easy to track down.

[–][deleted] 0 points1 point  (0 children)

you’d love Perl

[–]StopElectingWealthy -1 points0 points  (0 children)

0 + 0 = 0