I'm looking at the MDN documentation of the plus (a + b) operator in JavaScript. It seems like the operator is defined on all combinations of number, string, and bool arguments. From experimentation, it is also defined on arrays, objects, null, closures ...
The question: is there any combination of values for which the plus operator is not defined, i.e. those arguments are not in the domain of the + function? (Where operationally, "not defined" basically means an exception will be thrown or the program will halt with an error if + is applied to those arguments.)
Bonus points for answering the equivalent question for the other "arithmetic operators" (which seem to do far more than arithmetic ...).
The + operator is only defined for numbers (addition) and strings (concatenation).
However, it coerces its arguments to number or string. Full details in the spec, §11.6.1. Basically: Processing + does a "to primitive" on the operands, which means it then ends up with numbers, strings, booleans, nulls, or undefineds. If either operand, after coercion to primitive, is a string, it then coerces the other to string and does concatenation; otherwise, it coerces both to number and does addition.
The result of "to primitive" on objects depends on the object, but most objects end up coercing to string or number.
The only objects that can't be coerced to primitives are objects lacking both a toString and a valueOf*; in that case, §8.12.8 tells us that a TypeError is thrown. In practice, the only objects that won't have toString and valueOf are ones that A) Don't have their own copy of them, and B) Have no prototype or a prototype chain that doesn't include Object.prototype. For example, an object created by Object.create(null) (which has no prototype).
So no, there's no combination of values for which + is undefined but there is an edge case where it will throw a TypeError:
(Normal case) Both operands can coerced to a primitive, and any primitive can be coerced to a string or a number (the resulting number may, of course, be NaN), and so we get a resulting value, or
(Edge case) One of the two operands can't be converted to primitive, in which case we get a TypeError
* Okay, they don't have to be missing: If the object has toString but toString returns a non-primitive, then we get the TypeError if the object doesn't have valueOf or the object has valueOf, but valueOf also returns a non-primitive. In practice, toString and valueOf are supposed to return primitives.
Related
Why this code returns 1, the method toString even not called anywhere, + operator should turn its operand to a number but i tried this and returned NaN
+{} // NaN
+{a: 2} //NaN
so why this code returns 1 ????
+({x:+null , y : +'001' , toString : function(){return this.x+this.y;}})
+({x:+null , y : +'001' , toString : function(){return this.x+this.y;}})
You are overloading toString which function returns (+null+'001')
+null coerces null to 0;
+'001' coerces the string to 1
you are returning 1 from toString.
In the previous cases, the result of non a overloaded toString method of an object literal is "[object Object]" which coerced to a number gives NaN.
The only thing that matters here is what toString, overloaded or not, returns.
When an expression uses a non-number value in a way that needs it to be a number, the value is first converted to its primitive value equivalent. Then, the primitive value is coerced to a number.
The steps taken by a JavaScript interpreter are as follows:
Call valueOf and convert it to a number, if possible.
Call toString, and if the return value is primitive, convert it to a number, if possible.
Throw TypeError.
In your case, since you use the unary + operator with a non-number object, and the unary + operator is only defined for numbers, these steps are taken. Your object does not have a valueOf, but it does have a toString method, so the return vaule of toString, coerced to a number, is used to evaluate the expression.
Kyle Simpson's excellent You Don't Know JS: Types & Grammar has more on type coercion, if you would like to explore further.
Symbol() + '' throws
TypeError: Cannot convert a Symbol value to a string
While a known workaround is to use String(Symbol()).
This looks inconsistent with other primitives, including the ones that should almost never be coerced (undefined and null).
How exactly does String differ from + '' (except it works)? Do specs explicitly specify that String should accept symbols? What were the motives to allow it in one way and disallow it in another?
How exactly does String differ from + '' (except it works)? Do specs explicitly specify that String should accept symbols?
They differ in the aspect that String() has a case for a Symbol(), whereas the + operator (when used for concatenation) directly calls the ToString() operation which throws a TypeError exception for a Symbol().
From String() spec:
If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value).
From + evaluation spec:
If Type(lprim) is String or Type(rprim) is String, then
Let lstr be ToString(lprim).
ReturnIfAbrupt(lstr).
Let rstr be ToString(rprim).
ReturnIfAbrupt(rstr).
Return the String that is the result of concatenating lstr and rstr.
Note: the definitions for lprim and rprim come from 10 previous steps in the evaluation process, which involve getting primitive types and values of the sides of the expression. I didn't include them to keep this post shorter. I have linked each specification I have referenced below.
From the ToString() output:
Symbol: Throw a TypeError exception.
As for your final question:
What were the motives to allow it in one way and disallow it in another?
That's something for the writers at ECMA International.
String() ES6 Spec
+ operator runtime evaluation ES6 Spec
ToString() Output behavior ES6 Spec
''.valueOf()// an empty string
false.valueOf()// false
but why
+'' // 0
+false // 0
I read the tutorial, the algorithm of numeric conversion is:
If valueOf method exists and returns a primitive, then return it.
Otherwise, if toString method exists and returns a primitive, then return it.
Otherwise, throw an exception.
This is conflict with the real case, if that is the rule, then I think both ''.valueOf() and false.valueOf() should return 0. Could anyone please let me know the possible reason?
From the Object.prototype.valueOf() MDN page:
The valueOf() method returns the primitive value of the specified object.
Since you're calling valueOf() on primitives, it simply returns those primitives.
(In JavaScript, there are 6 primitive data types: String, Number, Boolean, Null, undefined, Symbol (ES6))
Now, from the Unary Plus (+) MDN page:
The unary plus operator precedes its operand and evaluates to its operand but attempts to converts it into a number, if it isn't already.
Basically, +value is equivalent to calling Number(value).
Both Number('') and Number(false) return 0.
.valueOf is returning a primitive - the empty string. false is also a primitive.
The + operator is shorthand for converting a variable to a number. In this case, it is converting the false-y values to 0 - no conflict at all.
I was coding today and I made a mistake while declaring an Array of Arrays in javascript (using the literal notation), I had forgotten to place a comma between each element in the array. After some further tests I got:
[[0][0]] gave me [0]
[[1][2]] gave me [undefined]
[0][0] gave me 0
[3][3] gave me undefined
[3]3 gave me a SyntaxError: Unexpected number
[][] gave me SyntaxError: Unexpected token ]
[3]*3 gave me 9 (Number not inside an array)
[3,4]*3 gave me NaN
[3,3]*[3,3] gave me NaN
[3,3]*[[3,3]] gave me NaN
[3,3][3,3] gave me undefined
[3,3][[3,3]] gave me undefined
At first I thought that this behavior might be mathematical vector/matrix multiplication, but that does not seem to be the case.
So the no operator between each array is clearly different than adding a * operator, and the * operator itself does not seem to perform neither scalar multiplication nor matrix multiplication.
The minus and division signs seems to always yield NaN and the plus sign seems to call a toString on both arrays and concat the strings.
I found that to be very odd, what is the semantic meaning behind operations between two arrays? To me the thing that makes most sense is to either always give errors when declaring Array Array and to always give NaN when declaring Array _operator_ Array. But that is not the case at all. The + sign at least makes sense because Array inherits from Object and that also happens if you try new Date() + new Date() (and this automatic toString call might be useful sometimes, although I would not design the language this way).
the * operator itself does not seem to perform neither scalar multiplication nor matrix multiplication. The minus and division signs seems to always yield NaN.
Indeed. *, / and - only work on numbers, and they will cast their operands to numbers. The array [3,3] will in that process first be converted to the string "3,3, which is not a valid number, therefore NaN as the result. With [3]*3 it "works" because the array is casted to the number 3. Similarly, [3]-[1] would yield 2.
So the no operator between each array is clearly different than adding a * operator. I found that to be very odd, what is the semantic meaning behind operations between two arrays?
If you did place "no operator" between the arrays, the latter ones are no arrays any more. The first pair of […] does build an array literal, but all the following […] are property accessors in bracket notation. [0][0] just accesses the first item of the [0] array which happens to be 0.
That is why […][] is a syntax error - the bracket notation needs an expression for the property name.
What you did see with […][…,…] was the comma operator to delimit expressions, it is not an array literal but parsed as …[(…, …)]. Your [3,3][3,3] is equivalent to [3,3][3], and accessing the fourth item in the array [3,3] will yield undefined.
The + sign at least makes sense because Array inherits from Object and that also happens if you try new Date() + new Date() (and this automatic toString call might be useful sometimes, although I would not design the language this way).
Yes, the + operator is complicated in JS, as it deals with multiple different types and does either (numeric) addition or (string) concatenation.
This is even worse when you use it on objects. In that case, it is tried to be cast to a primitive value (string, number, boolean), and for that the [[DefaultValue]] algorithm is applied (with no hint). When both operands are identified to be numeric, the addition is performed - this can even happen to objects:
> 1 + {valueOf: function(){ return 2; }}
3
> 1 + {valueOf: function(){ return "2"; }}
"12"
I'm using moment.js in a current project and I see that the comparison operators <,>,>=,<= work correctly. However == does not.
It surprises me that these work and that you don't need to use the .isBefore() and .isAfter() functions.
Can anyone give me a brief description of why these work? Would it be possible to make == work as well?
Thanks.
This is due to how comparison and equality operators in JavaScript work (emphasis mine):
For relational abstract comparisons (e.g., <=), the operands are first
converted to primitives, then to the same type, before comparison.
[...]
The equality operator converts the operands if they are not of the
same type, then applies strict comparison. If either operand is a
number or a boolean, the operands are converted to numbers if
possible; else if either operand is a string, the string operand is
converted to a number if possible. If both operands are objects, then
JavaScript compares internal references which are equal when operands
refer to the same object in memory.
So when two moment objects are compared with inequalities, they are first converted to numbers. For Moment.js objects, this is milliseconds since the unix epoch of midnight UTC, Jan 1st 1970.
In your favorite browser's console window/node REPL:
> +moment()
<- 1412332710977
For the == equality check, the runtime does a reference comparison between the two objects, which returns false for two different moment instances, even if they both are for the same date/time.
A lack of default .equals() or operator==() overloading in JavaScript makes this behaviour rather counter-intuitive, especially if you're coming from other languages.
Also note that Moment's isBefore/isAfter functions are really slow, as they clone both moment objects internally before doing the comparison (this is because there is an optional argument to isBefore/isAfter to specify which time component to compare on (e.g. 'hour') and it clones regardless of whether this argument is present or not).
Moment suffers here from having mutable objects, which is why it is doing a defensive clone() in the first place, and then further from not having optimised the common path where this is actually unnecessary.
The magic is the ValueOf(). JavaScript calls the valueOf() method to convert an object to a primitive value when comparing. Moment object overrides valueOf() method to basically return the value valueOf() of underlying native Date object, which returns the epoch number.