sandre89
sandre89

Reputation: 5918

Javascript: why does `const number = 0; number.foo` returns `undefined`, but `0.foo` throws `Invalid or unexpected token`

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`

Upvotes: 1

Views: 346

Answers (3)

axiac
axiac

Reputation: 72386

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 0.. 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: 0., 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.

Upvotes: 1

Paul Rumkin
Paul Rumkin

Reputation: 6883

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.

Upvotes: 0

Unmitigated
Unmitigated

Reputation: 89497

. 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);

Upvotes: 3

Related Questions