I just played with Twitter API and found very weird construction. It's division operator between function name and arguments passing.
c/({"count":8098,"url":"http:\/\/example.com\/"});
I supposed that this should throw parser exception, but it works − just returning NaN instead of undefined. Also it works similar with * and - operators, while + returns c.toString() (or .valueOf, I dunno).
Even more, it's really syntax error thrown if i don't pass object to function. Here's examples:
function c() {}
>> undefined
c
>> function c() {}
c/()
>> SyntaxError: Unexpected token )
c/({});
>> NaN
c+({})
>> "function c() {}[object Object]"
c({})
>> undefined
I ran this in Chrome, but I belive it works everywhere if Twitter put it in response.
So. Why this even work, and how it works exactly? I believe this somehow releated to function returning value and its implicit type coercion, but I still don't get it.
Your URL is not escaped in that API call so twitter reads it incorrectly and creates meaningless syntax.
If you set the URL parameter to an escaped value (via encodeURIComponent) it will produce c({some:"Object"}) which is a JSONP response - JSONP is inherently a function call and works by injecting a script tag.
So, twitter does not produce this response deliberately, you're passing it problematic parameters and it's not failing gracefully.
As for why dividing a function in an object is NaN - in JS generally math operators don't throw very often. JavaScript is telling you - you tried to perform a math operation that doesn't make sense and the result is not a number - NaN.
For the funky results:
c/()
Is just invalid syntax, simply put you're diving something by nothing, 5/() would fail equally well.
c/({})
Diving a function by an object makes no sense, so it is giving you "Not a Number". I can add a spec quote but I don't think there is much more to add beyond "There is no way to interperet this differently".
c+({})
The + operator performs string concatenation in addition to numeric addition - so what's happening here is that toString is called for both the function and the object and you see the concatenated result.
c({})
This is just the return value of the function call
Related
During a debugging session, after changing the return of a method from 0 to null, I started seeing an exception that wasn't occurring before.
Digging deeper, I realized that if a variable is holding a Number, you can call a property on it like if it was any other object; the same thing, however, doesn't happen if you try to call a property on the number directly.
For instance:
const number = 0;
console.log(number.foo) // undefined
0.foo // throws SyntaxError: Invalid or unexpected token
What causes this distinction, and where can I read more about it?
UPDATE
I just realized that (0).foo returns undefined as well, so there's at least some consistency there, but I didn't know Numbers could have properties in javascript.
Actually you can only try to read, but never assign to these properties:
const number = 3;
number.someProperty = true;
number.someProperty // returns `undefined`
. is taken as the optional decimal point directly after a number. Adding a space, using brackets, or adding another . to be the decimal point (so that the second . will be interpreted as a property accessor) will resolve the error.
You can access properties on primitive numbers because attempting to invoke methods or lookup properties on primitives will cause them to be wrapped in the corresponding object to perform the operation.
console.log(0 .foo);
console.log(0..foo);
console.log((0).foo);
0.foo throws SyntaxError: Invalid or unexpected token and the code is not execute because it is not a valid program.
The lexical parser finds 0 and knows that a number starts there. After 0 there is a . that tells it that the number is a real number, not an integer number. If after . there are more digits then they are the decimal part of the number.
In this case there aren't more digits after . and the number is 2.. The next characters after . (foo) are not part of the number but they are an identifier.
All in all, the input "program" 0.foo is parsed into two tokens: 2., which is a number followed by foo, which is an identifier.
This combination (number followed immediately by identifier) is not a valid program fragment. In a correct program, a number can be followed by a space, an operator, a semicolon, a closed parenthesis and probably several other lexical tokens but not by an identifier.
The code fragment is not a valid JavaScript program; it cannot be compiled and therefore it is not executed.
There are several ways to make it work. For example (0).foo.
If you try to run 0.foo in the developers console in Firefox it throws an error that summarizes what I explained above:
Uncaught SyntaxError: identifier starts immediately after numeric literal
This is because of JS OOP nature and its prototyping model. Everything, even primitive values, has a prototype, which is usually an Object. So any primitive, String, Boolean, BigInt, etc. has properties, with exception of null and undefined.
(1).toString()
(1n).toString()
(true).toString()
You can access values' prototypes like this:
Number.prototype
Boolean.prototype
String.prototype
Thus you can add properties to primitives (but you should not do this due to compatibility issues and error-prone nature of such modifications). But here is an example of this:
Number.prototype.negative = function () {
return Number(this * -1)
};
(1).negative(); // -1
In the case of numbers you got a syntax error because of parsing rules of JS, not because of its OOP model. JS interprets 0. as a number with mantissa, thus it expects a number after the dot.
I am curious how the typescript compiler is interpreting the colon in the incorrect call to the function foo.
const foo = (bar: boolean) => {
return bar;
}
foo(bar: true) // ERROR "Expected 1 arguments, but go 2."
This implies that it is interpreting both "bar" and "true" as arguments. To be clear, I am just curious how the compiler is parsing this.
As a follow-up, are there any legitimate uses of a colon in a function call except for in an expression that would result in an argument as in:
const baz = (bin: {bar: boolean})=>{
return bin.bar
}
baz({bar: true})
There are two errors generated for that line. One is indeed that the function expects two arguments, but the other one is that , was expected (this is the error you will see if you run the compiler).
The language service tries to offer as much diagnostics as possible even on invalid code.
The parser will try to parse bar as an argument (ie an expression) when it reaches the : it will stop parsing the argument expression as : can't be part of such an expression (at this time anyway). Then the compiler will continue to parse what it expects will be an argument list, and expect the ,, but finding the : instead. Now this is not a tragedy, the compiler will continue to parse the argument list, and interpret true as the next argument expression.
The semantic checks, will then see this call (with two arguments) as an attempt to call a function with one parameter with two arguments and give an error on that.
The reason you are probably seeing in your IDE the semantic error over the syntactic one is because the semantic error is associated with the whole call(foo(bar: true)) while the syntactic error is associated with just : (and if you hover over : you will see the ',' expected error)
As to your follow-up : can't be used directly in the argument list (like it can for example in C# for named arguments). The only valid use is if you use an object literal.
I noticed today that Chrome 49 no longer outputs NaN when you type {}+{} into the console. Instead it outputs the string [object Object][object Object].
Why is this? Did the language change?
Chrome devtools now automatically wrap everything that begins with { and ends with } in an implicit pair of parentheses (see code), to force its evaluation as an expression. That way, {} creates an empty object now. You can see this if you go back through the history (↑), the previous line will be contained in (…).
Why? I don't know, but I could guess it reduces confusion for newbies that don't know of the block-vs-object-literal thing, and it's also more helpful if you just want to evaluate an expression.
And in fact that's the reasoning, as discussed in bug 499864. Pure convenience. And because node REPL had it as well (see code).
If you hit the up arrow after checking this, you'll notice that instead of {} + {} it displays ({} + {}), which results in "[object Object][object Object]".
In comparison, in Firefox, {} + {} still displays NaN, but if you do ({} + {}) it also displays "[object Object][object Object]".
So, it looks like Chrome is adding the surrounding parenthesis automatically when it sees this operation.
As of Chrome 54 with regards to the console:
Unfortunately, I added the Clippy quote myself. The console gives no information about what it has done for you.
The new rules are incredibly simple saving us the trouble of laboriously typing these 2 difficult charcters o= or 0, before pasting Object Literals into the console:
If you have code that starts with: optional whitespace,(no comments permitted) followed by a {;
and that code could be interpreted as an object;
and that object is followed by no other code, unless:
the code after the first object is a binary operator,
then there can be as many operations as you like including groupings
provided the final operator has an Object literal in the right hand position;
and that final Object has not been grouped in parens
and that code is not terminated with a semicolon
and there are no comments following the code (internal comments are permitted so long as they are not in the initial or final position)
then and only then will your JavaScript (which may or may not actually be valid code) will be re-intrepted as a valid Object. You will not be informed that your code has been reinterpreted.
{wat:1}),({wat:2} Is finally an error again.
{let i=0;var increment=_=>i++} is correctly allowed, finally, which is quite a nice way of doing closures.
However, the following is incorrectly an object, this is just as a convenience as mentioned by #Bergi, it interprets JS wrong to help you! The spec says it is a block with a labeled statement "foo" with a literal 1 that is not assigned to anything.
{foo:1}
The above should be the same as
if(1) {
foo: 1
}
The following is treated correctly as a block... because it has a comment in front of it!
//magic comment
{foo:1}
So is this:
{foo:1}
//also magic
This is an Object:
{foo:
//not so magic comment
1}
This is an error
//not so magic comment
{foo:1}.foo
So is this:
{foo:1}.foo
This is fine:
1..wat
undefined
so is this:
['foo'][0]
The next one is correctly interpreted as an object whacked into the expression position with a 0, which is generally how we unambiguously ensure we have an expression instead of a statement.
0,{foo:1}.foo
I don't get why they wrap the value in parens. JS has some ridiculous design decisions, but trying to make it behave nicer in this one situation isn't really an option, the console needs to run JS correctly, and we need to be confident that chrome isn't just guessing that it thinks we really meant it to do something else.
If you don't like comma operators you can use assignment
x = {foo:1}.foo
Because as it stands
{} + {} + {}
"[object Object][object Object][object Object]"
;{} + {} + {}
"NaN[object Object]"
Crazy and consistent I can deal with... crazy and inconsistent no thank you!
I've declared two methods.
String.prototype.hazaa = function (shazoo) {
return this + shazoo;
}
Number.prototype.hazaa = function (shazoo) {
return this + shazoo;
}
When I call the former, I get the expected behavior. However, invoking the second one, produces the error below.
Syntax error: Unexpected token ILLEGAL(...)
I have the feeling that it's my C#-ishness that is spooking (I'm thinking extension methods and object oriented calls). The invocation's performed as follows.
"abc".hazaa("shazoo");
12345.hazaa(00000000);
Is there another syntax to invoke the function I've added? Have I not declared the prototype addition the right way?
Yes, I have made the research but I might be missing a relevant point.
The issue is while it is parsing 12345.hazaa(00000000);, it sees hazaa as coming after the decimal point in the number, hence the unexpected token. If you wrap the number in parentheses it is parsed and executed correctly:
(12345).hazaa(00000000);
It will continue to work normally on variables, as the parsing has already happened:
var a = 123;
a.hazaa(0000);
As mentioned by Jaromanda X in the comments, another alternative to allow correct parsing is to use a double-dot syntax:
12345..hazaa(00000000);
var a = [1, 2, 3, 4];
var b = [10, 20, 30, 40];
console.log([a, b].length)
[a, b].some(function(x) {
x.push(x.shift())
});
I was extremely surprised today when this code caused
[a,b].some(function(x){ x.push(x.shift()) });
^
TypeError: Cannot call method 'some' of undefined
Obviously the JavaScript 'auto semicolon insertion' is not working as expected here. But why?
I know you might recommend to use ; everywhere to avoid something like that, but the question is not about whether it is better to use ; or not. I would love to know what exactly happens here?
When I'm worried about semicolon insertion, I think about what the lines in question would look like without any whitespace between them. In your case, that would be:
console.log([a,b].length)[a,b].some(function(x){ etc });
Here you're telling the Javascript engine to call console.log with the length of [a,b], then to look at index [a,b] of the result of that call.
console.log returns a string, so your code will attempt to find property b of that string, which is undefined, and the call to undefined.some() fails.
It's interesting to note that str[a,b] will resolve to str[b] assuming str is a string. As Kamil points out, a,b is a valid Javascript expression, and the result of that expression is simply b.
In general, one could say that implicit semi-colon's can easily fail when defining an array on a new line, because an array defined on a new line is interpreted as a property access of the value of the expression on the previous line.
Javascript does only consider new lines to mark the end of a statement if not ending the statement after this new line would cause a parse error. See What are the rules for JavaScript's automatic semicolon insertion (ASI)? and EcmaScript 5 spec for the exact rules. (Thanks to Rob W and limelights)
What happens is the following:
The code get interpreted as
console.log([a,b].length)[a,b].some(function(x){ x.push(x.shift()) });
i.e. all as one statement.
Now parse the statement:
some is called on the value of console.log([a,b].length)[a,b]
the value of console.log([a,b].length)[a,b] is computed by taking the returned value of console.log([a,b].length) (undefined) and then trying to access the property with the name of the value of a,b.
a,b evaluates to the value of b (try it in your console). There's no property with the value of b of undefined, so the resulting value will be undefined as well.
There's no method some on undefined, hence the error.
JavaScript doesn't treat every line break as a semicolon. It usually treats line
breaks as semicolons only if it can’t parse the code without the semicolons. Basically, JavaScript treats a line break as a semicolon if the next non-space character cannot be interpreted as a continuation of the current statement. JavaScript - The Definitive Guide: 6th Ed. section 2.4
So, in your case, it is interpreting the line as something like
console.log([a,b].length)[a,b].some(function(x){ x.push(x.shift()) });
And that is the reason for error. JavaScript is trying to perform array-access on the results of console.log([a,b].length). Depending on the JavaScript engine and the return value of console.log, you might get different errors.
If it is the last statement of the function or flow, you can avoid ';' but it is recommended to put ';' at the end of the each statement to avoid such error.