2. Values and types
Sections
Reading settings
No voice for this language in this browser.
More voices
Code and English terms
Off: the page’s voice reads everything, with no pause between two voices.
No voice for this language in this browser.
More voices
More voices: Microsoft Edge has natural online voices, and Windows (Settings › Time & language › Speech), macOS and Android install others.
Code: the files examples/l02_*.js, and the C# and Java sides in compare/l02_numbers.cs and compare/L02Numbers.java.
The examples of this lesson and the next two print their results with two small helpers from examples/show.js. show prints a label, then the value the way Node.js’s interactive shell would, with strings in quotes so that '12' and 12 look different. attempt does the same for a function, and prints the error instead when the function throws.
import { inspect } from 'node:util';
export function show(label, value) { console.log(`${label.padEnd(34)} ${inspect(value)}`);}
export function attempt(label, fn) { try { show(label, fn()); } catch (err) { console.log(`${label.padEnd(34)} ${err.name}: ${err.message}`); }}let, const and var
Section titled “let, const and var”| C# | Java | JavaScript | |
|---|---|---|---|
| A variable you can reassign | var count = 1; |
var count = 1; |
let count = 1; |
| A variable you can’t reassign | a readonly field; no local equivalent |
final var count = 1; |
const count = 1; |
| A constant known at compile time | const int Count = 1; |
static final int COUNT = 1; |
— |
| The old way | — | — | var count = 1;, scoped to the function (lesson 3) |
import { attempt, show } from './show.js';
let count = 1;count = 2; // let: the binding can changeshow('count', count);
const settings = { theme: 'dark', tabs: ['lessons'] };settings.theme = 'light'; // const: the binding can't change, the object cansettings.tabs.push('journal');show('settings', settings);attempt('settings = {}', () => { settings = {};});
const frozen = Object.freeze({ theme: 'dark', tabs: ['lessons'] });attempt("frozen.theme = 'light'", () => { frozen.theme = 'light'; // a module is strict code: the assignment throws});frozen.tabs.push('journal'); // freeze is shallowshow('frozen', frozen);count 2settings { theme: 'light', tabs: [ 'lessons', 'journal' ] }settings = {} TypeError: Assignment to constant variable.frozen.theme = 'light' TypeError: Cannot assign to read only property 'theme' of object '#<Object>'frozen { theme: 'dark', tabs: [ 'lessons', 'journal' ] }const is final in Java, not const in C#: the variable always refers to the same object, and the object stays mutable. Object.freeze makes the properties of one object read-only, and not the objects they point to. The error comes at run time, when the line executes: nothing checks it before. Use const by default and let when you reassign; var is only found in old code.
Values have types, variables don’t
Section titled “Values have types, variables don’t”import { show } from './show.js';
let value = 42;show('typeof value', typeof value);value = 'forty-two'; // no error: the variable has no typeshow('typeof value', typeof value);
show('typeof undefined', typeof undefined);show('typeof true', typeof true);show('typeof 3.14', typeof 3.14);show('typeof 10n', typeof 10n);show("typeof 'text'", typeof 'text');show('typeof Symbol()', typeof Symbol());show('typeof {}', typeof {});show('typeof []', typeof []);show('typeof null', typeof null);show('typeof (() => 1)', typeof (() => 1));show('Array.isArray([])', Array.isArray([]));typeof value 'number'typeof value 'string'typeof undefined 'undefined'typeof true 'boolean'typeof 3.14 'number'typeof 10n 'bigint'typeof 'text' 'string'typeof Symbol() 'symbol'typeof {} 'object'typeof [] 'object'typeof null 'object'typeof (() => 1) 'function'Array.isArray([]) trueThe specification defines eight types: seven primitive types, which are Undefined, Null, Boolean, Number, BigInt, String and Symbol, and Object. Arrays, functions, dates and maps are all objects. The typeof operator almost follows those types, with two exceptions written into the specification: it returns 'object' for null, a mistake of the first implementation kept for compatibility, and 'function' for objects you can call. To recognize an array, use Array.isArray.
In C#, the closest model is a program where every variable is declared dynamic. The difference comes when types don’t match: C# throws a RuntimeBinderException, and JavaScript converts one of the values and carries on. Most of the surprises of this lesson come from those conversions. Static types that catch mistakes before the program runs are what TypeScript adds, in the next course.
number: always a double
Section titled “number: always a double”JavaScript has a single number type for integers and fractions: a 64-bit IEEE 754 floating-point number, the double of C# and Java.
import { show } from './show.js';
show('0.1 + 0.2', 0.1 + 0.2);show('7 / 2', 7 / 2);show('Math.trunc(-7 / 2)', Math.trunc(-7 / 2));show('Math.floor(-7 / 2)', Math.floor(-7 / 2));show('-7 % 2', -7 % 2);show('1 / 0', 1 / 0);show('0 / 0', 0 / 0);show('NaN === NaN', NaN === NaN);show('Number.isNaN(NaN)', Number.isNaN(NaN));show("isNaN('abc')", isNaN('abc'));show("Number.isNaN('abc')", Number.isNaN('abc'));
// Integers are exact up to 2^53 - 1show('Number.MAX_SAFE_INTEGER', Number.MAX_SAFE_INTEGER);show('2 ** 53 + 1', 2 ** 53 + 1);show('2 ** 53 + 1 === 2 ** 53', 2 ** 53 + 1 === 2 ** 53);show('9007199254740993', 9007199254740993);show('Number.isSafeInteger(2 ** 53)', Number.isSafeInteger(2 ** 53));
// Bitwise operators work on 32-bit integersshow('2 ** 31 | 0', 2 ** 31 | 0);show('(2 ** 32 + 5) | 0', (2 ** 32 + 5) | 0);
// Zero has a signshow('-0', -0);show('-0 === 0', -0 === 0);show('Object.is(-0, 0)', Object.is(-0, 0));show('(0.1 + 0.2).toFixed(2)', (0.1 + 0.2).toFixed(2));0.1 + 0.2 0.300000000000000047 / 2 3.5Math.trunc(-7 / 2) -3Math.floor(-7 / 2) -4-7 % 2 -11 / 0 Infinity0 / 0 NaNNaN === NaN falseNumber.isNaN(NaN) trueisNaN('abc') trueNumber.isNaN('abc') falseNumber.MAX_SAFE_INTEGER 90071992547409912 ** 53 + 1 90071992547409922 ** 53 + 1 === 2 ** 53 true9007199254740993 9007199254740992Number.isSafeInteger(2 ** 53) false2 ** 31 | 0 -2147483648(2 ** 32 + 5) | 0 5-0 -0-0 === 0 trueObject.is(-0, 0) false(0.1 + 0.2).toFixed(2) '0.30'The same operations in C# and in Java:
> dotnet run l02_numbers.cs0.1 + 0.2 0.300000000000000047 / 2 37.0 / 2 3.51.0 / 0 InfinityNaN == NaN False1 / zero DivideByZeroException: Attempted to divide by zero.int.MaxValue + 1 -2147483648long.MaxValue 9223372036854775807BigInteger.Pow(2, 64) 18446744073709551616"1" + 2 12guitar emoji .Length 2-0.0 == 0.0 True> java L02Numbers.java0.1 + 0.2 0.300000000000000047 / 2 37.0 / 2 3.51.0 / 0 InfinityNaN == NaN false1 / zero java.lang.ArithmeticException: / by zeroInteger.MAX_VALUE + 1 -2147483648Long.MAX_VALUE 9223372036854775807BigInteger 2^64 18446744073709551616"1" + 2 12guitar emoji .length() 2-0.0 == 0.0 trueWhat JavaScript shares with C# and Java:
0.1 + 0.2is0.30000000000000004in all three: it is a property of binary floating point, not of JavaScript. For money, count integer cents, or use a decimal library.- Dividing a double by zero gives
Infinity, andNaNis different from everything, itself included. %keeps the sign of the dividend:-7 % 2is-1in all three.
What differs:
- There is no integer division.
7 / 2is3.5.Math.truncrounds toward zero like the integer division of C# and Java;Math.floorrounds down, which differs for negative numbers. - There is no integer overflow, and no exception for dividing by zero. Integers are exact up to 2 to the power of 53, minus 1, which is
Number.MAX_SAFE_INTEGER; above that, the double rounds silently. The literal9007199254740993is already9007199254740992when the program starts. A C#longidentifier sent in JSON can lose its last digits when a JavaScript client parses it, which is why some APIs send large identifiers as strings. - Bitwise operators convert to 32-bit integers (ToInt32):
2 ** 31 | 0wraps to-2147483648, andx | 0, an old idiom to truncate a number, breaks above two billion. - The global
isNaNconverts its argument first, soisNaN('abc')istrue.Number.isNaNdoesn’t convert, and is the one to use. - Zero has a sign that
===ignores.Object.issees the difference between-0and0, and says thatNaNisNaN.
bigint: integers of any size
Section titled “bigint: integers of any size”import { attempt, show } from './show.js';
show('2n ** 64n', 2n ** 64n);show('7n / 2n', 7n / 2n);show('typeof 7n', typeof 7n);show('BigInt(2 ** 53) + 1n', BigInt(2 ** 53) + 1n);attempt('1n + 1', () => 1n + 1);show('1n + BigInt(1)', 1n + BigInt(1));show('1n == 1', 1n == 1);show('1n === 1', 1n === 1);show('Number(2n ** 64n)', Number(2n ** 64n));attempt('BigInt(1.5)', () => BigInt(1.5));attempt('JSON.stringify({ id: 1n })', () => JSON.stringify({ id: 1n }));attempt('Math.max(1n, 2n)', () => Math.max(1n, 2n));2n ** 64n 18446744073709551616n7n / 2n 3ntypeof 7n 'bigint'BigInt(2 ** 53) + 1n 9007199254740993n1n + 1 TypeError: Cannot mix BigInt and other types, use explicit conversions1n + BigInt(1) 2n1n == 1 true1n === 1 falseNumber(2n ** 64n) 18446744073709552000BigInt(1.5) RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integerJSON.stringify({ id: 1n }) TypeError: Do not know how to serialize a BigIntMath.max(1n, 2n) TypeError: Cannot convert a BigInt value to a numberA BigInt is an integer of any size, written with an n suffix, like System.Numerics.BigInteger and java.math.BigInteger, with operators instead of methods. Its division truncates, like integer division in C#. Here JavaScript is stricter than C#: C# converts an int to a BigInteger implicitly, while JavaScript refuses to mix the two types in arithmetic and asks for an explicit BigInt(…) or Number(…). JSON.stringify and Math don’t accept BigInts either.
string: UTF-16, immutable
Section titled “string: UTF-16, immutable”import { attempt, show } from './show.js';
const course = 'JavaScript';show('course.length', course.length);show('course[4]', course[4]);show('course.at(-1)', course.at(-1));show('`${course} for C#`', `${course} for C#`);attempt("course[0] = 'X'", () => { course[0] = 'X';});show("'é'.length", 'é'.length);show("'e\\u0301'.length", 'é'.length);show("'é' === 'e\\u0301'", 'é' === 'é');show("'e\\u0301'.normalize() === 'é'", 'é'.normalize() === 'é');show("'🎸'.length", '🎸'.length);show("[...'🎸'].length", [...'🎸'].length);show("'🎸'.codePointAt(0)", '🎸'.codePointAt(0));show("'b' > 'a'", 'b' > 'a');show("'B' > 'a'", 'B' > 'a');show("'10' < '9'", '10' < '9');course.length 10course[4] 'S'course.at(-1) 't'`${course} for C#` 'JavaScript for C#'course[0] = 'X' TypeError: Cannot assign to read only property '0' of string 'JavaScript''é'.length 1'é'.length 2'é' === 'é' false'é'.normalize() === 'é' true'🎸'.length 2[...'🎸'].length 1'🎸'.codePointAt(0) 127928'b' > 'a' true'B' > 'a' false'10' < '9' trueStrings work as in C# and Java: immutable sequences of UTF-16 code units, where length counts code units and an emoji outside the Basic Multilingual Plane takes two. The C# and Java programs above print 2 for the same guitar. There is no separate char type: course[4] is a string of length one. Spreading a string with ... iterates over code points, which is how to count the guitar as one. === compares code units, so the two spellings of é differ until normalize makes them equal, and < compares code units too, so uppercase letters sort before lowercase ones and '10' sorts before '9'. Lesson 11 compares strings the way people read them, with Intl.Collator.
undefined and null
Section titled “undefined and null”C# and Java have one absent value, null. JavaScript has two.
import { attempt, show } from './show.js';
let notAssigned;const page = { title: 'Mission', order: 0, draft: null };
function noReturn() {}function greet(name) { return name;}
show('notAssigned', notAssigned);show('page.author', page.author);show('noReturn()', noReturn());show('greet()', greet());show('page.draft', page.draft);show('typeof page.draft', typeof page.draft);
attempt('page.author.name', () => page.author.name);show('page.author?.name', page.author?.name);show("page.author ?? 'anonymous'", page.author ?? 'anonymous');
// || replaces every falsy value, ?? only null and undefinedshow('page.order || 99', page.order || 99);show('page.order ?? 99', page.order ?? 99);
show('JSON.stringify(page)', JSON.stringify({ ...page, author: undefined }));show('null == undefined', null == undefined);show('null === undefined', null === undefined);notAssigned undefinedpage.author undefinednoReturn() undefinedgreet() undefinedpage.draft nulltypeof page.draft 'object'page.author.name TypeError: Cannot read properties of undefined (reading 'name')page.author?.name undefinedpage.author ?? 'anonymous' 'anonymous'page.order || 99 99page.order ?? 99 0JSON.stringify(page) '{"title":"Mission","order":0,"draft":null}'null == undefined truenull === undefined falseThe language produces undefined by itself: for a variable without a value, a property that doesn’t exist, a missing argument, and a function that returns nothing. null appears only when code writes it. Reading a missing property is not an error, it gives undefined; the error comes one step later, when you read a property of undefined, and that TypeError is the NullReferenceException of JavaScript. JSON.stringify keeps null and drops the properties that are undefined.
The operators ?. and ?? work as in C#. The trap is the older logical or, ||, which you will find everywhere: it returns its right side for every falsy value, 0 and the empty string included, while ?? does so only for null and undefined. page.order || 99 turns the order 0 into 99.
In GuitarAlchemist/ga
Section titled “In GuitarAlchemist/ga”BSPDoomExplorer.tsx, line 4895 reads the rotation speed of a 3D sample with obj.userData.rotationSpeed || 0.5, and line 5386 computes the time between two frames. Reduced to plain JavaScript:
// Line 4895: the rotation speed of a sample, 0.5 when missingfunction speedWithOr(userData) { return userData.rotationSpeed || 0.5;}function speedWithNullish(userData) { return userData.rotationSpeed ?? 0.5;}for (const userData of [{}, { rotationSpeed: 0.3 }, { rotationSpeed: 0 }]) { console.log(JSON.stringify(userData).padEnd(22), '||', speedWithOr(userData), ' ??', speedWithNullish(userData));}
// Line 5386: the time since the previous frame, stored as a property of the function itselffunction updateFPS(now) { const delta = now - updateFPS.lastTime || 0; updateFPS.lastTime = now; return delta;}console.log('first frame ', updateFPS(1000));console.log('second frame', updateFPS(1016));console.log('undefined - 1000 =', undefined - 1000, '; NaN || 0 =', NaN || 0);{} || 0.5 ?? 0.5{"rotationSpeed":0.3} || 0.3 ?? 0.3{"rotationSpeed":0} || 0.5 ?? 0first frame 0second frame 16undefined - 1000 = NaN ; NaN || 0 = 0Today, every sample gets a random speed between 0.3 and 0.7 (line 3039), so the || never meets a zero. The same file stops other objects by setting rotationSpeed = 0 (line 5556): the day a sample is stopped that way, || 0.5 will make it turn again. ?? says what the line means.
The second line works, but not for the reason it seems. Subtraction binds tighter than ||, so it reads (now - updateFPS.lastTime) || 0, not now - (updateFPS.lastTime || 0). On the first frame, lastTime is undefined, the subtraction gives NaN, NaN is falsy, and the || 0 turns it into 0, which the code then skips with if (delta > 0).
== and ===
Section titled “== and ===”import { show } from './show.js';
show("'0' == 0", '0' == 0);show("'' == 0", '' == 0);show("'' == '0'", '' == '0');show("'1' === 1", '1' === 1);show('true == 1', true == 1);show("true == 'true'", true == 'true');show('[] == false', [] == false);show('[0] == false', [0] == false);show("[1, 2] == '1,2'", [1, 2] == '1,2');show('null == 0', null == 0);show('null >= 0', null >= 0);show('undefined == 0', undefined == 0);show('NaN == NaN', NaN == NaN);
// The one common use of ==: null or undefined in a single testfor (const value of [null, undefined, 0, '', false]) { show(`${String(value) || "''"} == null`, value == null);}'0' == 0 true'' == 0 true'' == '0' false'1' === 1 falsetrue == 1 truetrue == 'true' false[] == false true[0] == false true[1, 2] == '1,2' truenull == 0 falsenull >= 0 trueundefined == 0 falseNaN == NaN falsenull == null trueundefined == null true0 == null false'' == null falsefalse == null falseIn C# and Java, comparing a string with a number doesn’t compile. In JavaScript, === (IsStrictlyEqual) answers false when the types differ, and == (IsLooselyEqual) converts first, following a few rules:
- Two values of the same type are compared as
===would. nullandundefinedare equal to each other, and to nothing else.- A string compared with a number becomes a number.
- A boolean becomes a number first:
trueis1,falseis0. - An object compared with a primitive is converted to a primitive, usually through its
toStringmethod: an empty array becomes the empty string,[0]becomes the string'0', and[1, 2]becomes'1,2'.
[] == false applies rules 4, 5 and 3 in turn: false becomes the number zero, the empty array becomes the empty string, and the empty string becomes zero. The empty string and the string '0' are both loosely equal to the number zero, yet not to each other: loose equality isn’t even transitive. And null >= 0 is true while null == 0 is false, because the relational operators convert null to 0 and == has its own rule for null.
Use the strict operators, === and !==, everywhere. The one idiom worth keeping is value == null, true for null and undefined only, which GA uses in DynamicPanel.tsx, line 36. ESLint’s eqeqeq rule enforces the rule and has an option to allow that idiom; it is not part of js.configs.recommended, the base of GA’s ESLint configuration.
Conversions: +, -, parsing and truthiness
Section titled “Conversions: +, -, parsing and truthiness”import { show } from './show.js';
show("'1' + 2", '1' + 2);show("1 + 2 + '3'", 1 + 2 + '3');show("'3' - 1", '3' - 1);show("'5' * '2'", '5' * '2');show("'3' + -'1'", '3' + -'1');show('[] + []', [] + []);show('[] + {}', [] + {});show("+'42'", +'42');
// Parsing: Number reads the whole string, parseInt stops at the first invalid charactershow("Number('')", Number(''));show("Number(' 12 ')", Number(' 12 '));show("Number('12px')", Number('12px'));show("parseInt('12px', 10)", parseInt('12px', 10));show("parseInt('', 10)", parseInt('', 10));show("parseInt('0x1F')", parseInt('0x1F'));show("parseInt('1e3', 10)", parseInt('1e3', 10));show("Number('1e3')", Number('1e3'));show('parseInt(0.0000005)', parseInt(0.0000005));
// Truthy and falsy: if converts any value to a booleanconst values = [false, 0, -0, 0n, '', null, undefined, NaN, '0', 'false', [], {}, -1];const falsy = values.filter((v) => !v);const truthy = values.filter((v) => v);show('falsy', falsy);show('truthy', truthy);'1' + 2 '12'1 + 2 + '3' '33''3' - 1 2'5' * '2' 10'3' + -'1' '3-1'[] + [] ''[] + {} '[object Object]'+'42' 42Number('') 0Number(' 12 ') 12Number('12px') NaNparseInt('12px', 10) 12parseInt('', 10) NaNparseInt('0x1F') 31parseInt('1e3', 10) 1Number('1e3') 1000parseInt(0.0000005) 5falsy [ false, 0, -0, 0n, '', null, undefined, NaN ]truthy [ '0', 'false', [], {}, -1 ]+ is the operator that surprises, because it has two meanings: if either operand is a string after conversion to a primitive, it concatenates; otherwise it adds. C# and Java concatenate "1" + 2 into "12" too, as their outputs above show; the difference is that JavaScript also converts objects, an empty array to the empty string and an empty object to the text [object Object], and evaluates from left to right, so 1 + 2 + '3' is '33'. The other arithmetic operators, -, * and /, have only one meaning and convert both sides to numbers, and so does the unary +.
To turn text into a number on purpose:
Number(text)reads the whole string, ignores surrounding spaces, accepts1e3and0x1F, and returnsNaNif anything else is left. Its one trap: the empty string gives0.parseInt(text, 10)reads digits until the first character it can’t use, so'12px'gives12. Always pass the radix: without it, a0xprefix switches to hexadecimal. Its argument is a string:parseInt(0.0000005)converts the number to'5e-7'first, and reads5.- Neither throws. C#’s
int.Parseand Java’sInteger.parseIntreject'12px'with an exception; in JavaScript, check the result withNumber.isNaNorNumber.isInteger.
An if, !, && and || accept any value and convert it with ToBoolean. Eight values are falsy: false, 0, -0, 0n, the empty string, null, undefined and NaN. Everything else is truthy, including the string '0', the string 'false' and an empty array. C# and Java require a boolean in a condition, so if (items.length) has no equivalent there, and if (count) is false when the count is zero.
Key takeaways
Section titled “Key takeaways”constfixes the variable, not the object;Object.freezefixes one level of an object.- Values have types, variables don’t:
typeoftells you the type, with'object'fornulland arrays. numberis adouble: no integer division, no overflow, exact integers only up to 2 to the power of 53, minus 1.bigintcovers larger integers and refuses to mix withnumber.- Strings are UTF-16 and immutable, as in C# and Java.
undefinedmeans “never set”,nullmeans “set to nothing”;??replaces both,||replaces every falsy value,0and''included.- Use
===.== nullis the only loose comparison worth writing. +concatenates as soon as a string is involved;NumberandparseInt(text, 10)parse, and neither throws.
Exercises
Section titled “Exercises”- Write
isBlank(value), true fornull,undefinedand strings made only of whitespace, and false for everything else, including0,falseandNaN.
Solution
solutions/l02_ex1_is_blank.js:
function isBlank(value) { return value == null || (typeof value === 'string' && value.trim() === '');}isBlank(null) trueisBlank(undefined) trueisBlank('') trueisBlank(' ') trueisBlank('\t\n') trueisBlank('text') falseisBlank(0) falseisBlank(false) falseisBlank(NaN) falseisBlank([]) false!value would be shorter and wrong: it is true for 0, false and NaN. value == null covers null and undefined in one test, and the typeof check keeps trim away from values that aren’t strings.
- Write
parsePort(text), which returns a port number between 1 and 65535 from a string of digits, and throws aTypeErroror aRangeErrorotherwise, asint.ParseorInteger.parseIntwould. Try it with'8080','',' 80','8080abc','0x50','1e3','70000'and the number8080.
Solution
solutions/l02_ex2_parse_port.js:
function parsePort(text) { if (typeof text !== 'string' || !/^\d+$/.test(text)) { throw new TypeError(`not a port: ${JSON.stringify(text)}`); } const port = Number(text); if (port < 1 || port > 65535) { throw new RangeError(`port out of range: ${port}`); } return port;}"8080" 8080"443" 443"" TypeError: not a port: """ 80" TypeError: not a port: " 80""8080abc" TypeError: not a port: "8080abc""0x50" TypeError: not a port: "0x50""1e3" TypeError: not a port: "1e3""70000" RangeError: port out of range: 70000"0" RangeError: port out of range: 08080 TypeError: not a port: 8080The regular expression does the work neither built-in function does: Number alone would accept '' as 0, ' 80', '0x50' and '1e3', and parseInt would accept '8080abc'. Once the string is only digits, Number is safe. Lesson 11 covers regular expressions.
- Predict each result, then run
solutions/l02_ex3_predict.js:'2' + 2 * '2',null + 1,undefined + 1,[] == ![],'b' + 'a' + +'a' + 'a',0.1 * 3 === 0.3, and10n ** 400n > Number.MAX_VALUE.
Solution
'2' + 2 * '2' '24'null + 1 1undefined + 1 NaN[] == ![] true'b' + 'a' + +'a' + 'a' 'baNaNa'0.1 * 3 === 0.3 false10n ** 400n > Number.MAX_VALUE true*comes first and converts both strings:2 * '2'is4, then'2' + 4concatenates.- In arithmetic,
nullbecomes0andundefinedbecomesNaN. ![]isfalse, since an array is truthy; then[] == falseistrue, as above.+'a'isNaN, and'ba' + NaNconcatenates the textNaN.0.1 * 3is0.30000000000000004, like0.1 + 0.2.- Comparisons, unlike arithmetic, may mix a
bigintand anumber: 10400 is larger than the largest double, about 1.8 × 10308.
Sources
Section titled “Sources”- ECMAScript — ECMAScript language types, the
typeofoperator, IsLooselyEqual, IsStrictlyEqual, ToBoolean, ToNumber, ApplyStringOrNumericBinaryOperator - MDN — JavaScript data types and data structures, Equality comparisons and sameness, Type coercion
- Microsoft — Floating-point numeric types, Java Language Specification — 4.2.3 Floating-point types