In this chapter, you will learn about the data types that you can manipulate in a JavaScript program: numbers, strings, and other primitive types, as well as objects and arrays. You will see how to store these values in variables, how to convert values from one type to another, and how to combine values with operators.
As you read this chapter, you will encounter a number of scary warnings that seem to suggest that JavaScript suffers from some very poor design decisions. That perception is entirely accurate. However, in practice, problems are rare provided you follow some simple rules. Don't panic and pay close attention to the tips throughout this and the following chapters.
To run JavaScript programs as you follow along this book, you can follow a number of different approaches.
JavaScript was originally intended to execute in a browser. You can embed JavaScript in an HTML file and invoke the window.alert
method to display values. As an example, here is such a file:
<html> <head> <title>My First JavaScript Program</title> <script type="text/javascript"> let a = 6 let b = 7 window.alert(a * b) </script> </head> <body> </body> </html>
Simply open the file in your favorite web browser, and the result is displayed in a dialog box—see .
You can type short instruction sequences into the console that is a part of the development tools of your browser. Find out the menu or keyboard shortcut to display the development tools (which often is the F12 key or the Ctrl+Alt+I combination). Then pick the “Console” tab and type in your JavaScript code—see .
A third approach is to install node.js from http://nodejs.org. Then open a terminal and execute the node
program which launches a JavaScript “read-eval-print loop” or REPL. You type commands and see their result, as shown in Figure .
For longer code sequences, put the instructions in a file and use the console.log
method to produce output. For example, you can put these instructions into a file first.js
:
let a = 6 let b = 7 console.log(a * b)
Then run the command
node first.js
The output of the console.log
command will be displayed in the terminal.
You can also use a development environment such as Visual Studio Code, Eclipse, Komodo, or WebStorm. These environments let you edit and execute JavaScript code, as shown in .
typeof
OperatorEvery value in JavaScript is one of the following types:
false
and true
null
and undefined
The non-object types are collectively called primitive types.
You will find out more about these types in the sections that follow. However, symbols are discussed in Chapter XREF.
Given a value, you can find its type with the typeof
operator which returns a string 'number'
, 'boolean'
, 'undefined'
, 'object'
, 'string'
, 'symbol'
, or one of a small number of other strings. For example, typeof 42
is the string 'number'
.
Even though the null type is distinct from the object type, typeof null
is the string 'object'
. This is a historical accident.
In JavaScript, functions are special kinds of objects—see Chapter 4. For them, the typeof
operator returns a special string 'function'
.
Similar to Java, you can construct objects that wrap numbers, Boolean values, and strings. For example, typeof new Number(42)
and typeof new String('Hello')
are 'object'
. However, in JavaScript, there is no good reason to construct such wrapper instances. Since they can be a cause of confusion, coding standards often forbid their use.
JavaScript has two kinds of comments. Single-line comments start with //
and extend to the end of the line
// like this
Comments that are delimited by /*
and */
can span multiple lines
/* like this */
In this book, I use a Roman font to make the comments easier to read. Of course, your text editor will likely use some kind of color coding instead.
Unlike Java, JavaScript does not have a special form of documentation comments. However there are third party libraries such as JSDoc (http://usejsdoc.org) that provide the same functionality.
You can store a value in a variable with the let
statement:
let counter = 0
In JavaScript, variables do not have a type. You are free to store values of any type in any variable. For example, it is legal to replace the contents of counter
with a string:
counter = 'zero'
It is almost certainly not a good idea to do this. Nevertheless, there are situations where having untyped variables makes it easy to write generic code that works with different types.
You may have noticed that the statements above are not terminated by semicolons. In JavaScript, like in Python, semicolons are not required at the end of a line. In Python, it is considered “unpythonic” to add unnecessary semicolons. However, JavaScript programmers are split on that question. I will discuss the pros and cons in Chapter 3. Generally, I try not to take sides in unproductive discussions, but here I have no choice. I use the “no semicolon” style for one simple reason: It doesn't look like Java or C++. You can see right away when a code snippet is JavaScript.
If you never change the value of a variable, you can define it with a const
statement:
const PI = 3.141592653589793
It is an error to modify the value contained in a const
. Note, however, that an object or array that is declared as const
can nevertheless be mutated:
const numbers = [1, 2, 7, 9] numbers[2] = 4 // Ok
In other words, const
is like final
in Java and not at all like const
in C++.
The name of a variable must follow the general syntax for identifiers. An identifier consists of Unicode letters, digits, and the _
and $
characters. The first character cannot be a digit. Names with $
characters are sometimes used in tools and libraries. Some programmers use identifiers starting or ending with underscores to indicate “private” features. With your own names, it is best to avoid $
as well as _
at the start or the end. Internal _
are fine, but many JavaScript programmers prefer the camelCase
format in which uppercase letters are used for word boundaries.
You cannot use the following reserved words as identifiers:
await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield
In “strict mode”, a mode that forbids certain outdated constructs and enables new features, the following are also reserved:
implements interface let package protected private public static
You will see in Chapter 3 how to enable strict mode.
You can use any Unicode letters or digits in identifiers, such as:
const π = 3.141592653589793
However, this is not common, probably because many programmers lack input methods for typing such characters.
There are two obsolete forms of variable declarations, using the var
keyword or no keyword at all:
var counter = 0 // Obsolete PI = 3.141592653589793 // Obsolete
You will see in Chapter 3 why these forms should no longer be used.
Sometimes, you need to check whether a variable is defined. Referencing an undefined variable causes a ReferenceError
. But you can safely use the typeof
operator:
typeof var returns 'undefined'
if var has not been defined.
In JavaScript, all numbers are double-precision floating-point numbers unless you specifically use “big integers” described in the following section.
Of course, you can use integer values. You simply don't worry about the difference between, say, 1
and 1.0
. What about roundoff? Any integers numbers between Number.MIN_SAFE_INTEGER
(-253+1 or -9,007,199,254,740,991) and Number.MAX_SAFE_INTEGER
(253-1 or 9,007,199,254,740,991) are represented accurately. That's a larger range than integers in Java. As long as results stay within this range, arithmetic operations on integers are also accurate. Outside the range, you will encounter roundoff errors. For example, Number.MAX_SAFE_INTEGER * 10
evaluates to 90071992547409900
.
As with floating-point numbers in any programming language, you cannot avoid roundoff errors with fractional values. For example, 0.1 + 0.2
evaluates to 0.30000000000000004
, as it would in Java, C++, or Python. Such errors are inevitable since decimal numbers such as 0.1, 0.2, and 0.3 do not have exact binary representations. If you need to compute with dollars and cents, you should represent all quantities as integer multiples of a penny,
To convert a string to a number, use the parseFloat
or parseInt
functions. For example, parseFloat('3.14')
is the number 3.14
, and parseInt('3')
is 3.
JavaScript, like C++ but unlike Java, has both functions and methods. The parseFloat
and parseInt
functions are not methods, so you don't invoke them with the dot notation.
What happens when you use a fractional number when an integer is expected? It depends on the situation. Suppose you exctract a substring of a string. Then fractional index values are truncated to the next smaller integer:
'Hello'.substring(0, 2.5) // The string 'He'
But when you provide a fractional array index, the result is undefined:
'Hello'[2.5] // undefined
If you divide by zero, the result is Infinity
or -Infinity
. However, 0 / 0
is NaN
, the “Not a Number” constant.
Some number-producing functions return NaN
to indicate a faulty input. For example, parseInt('Hello')
is NaN
.
JavaScript has the usual operators + - * /
for addition, subtraction, multiplication, and division. Note that the /
operator always yields a floating-point result, even if both operands are integers. For example, 1 / 2
is 0.5
, not 0 as it would be in Java or C++.
The %
operator yields the remainder of the integer division for nonnegative integer operands, just like it does in Java, C++, and Python. For example, if k
is a nonnegative integer, then k % 2
is 0 if k
is even, 1 if k
is odd.
If k
and n
are positive values, possibly fractional, then k % n
is the value that is obtained by subtracting n
from k
until the result is less than n
. For example, 3.5 % 1.2
is 1.1
, the result of subtracting 1.2 twice. See for negative operands.
The **
operator denotes “raising to a power”, as it does in Python (and all the way back to Fortran). The value of 2 ** 10
is 1024
, 2 ** -1
is 0.5, and 2 ** 0.5
is the square root of 2.
If an operand of any arithmetic operator is the “not a number” value NaN
, the result is again NaN
.
As in Java, C++, and Python, you can combine assignment and arithmetic operations:
counter += 10 // The same as counter = counter + 10
The ++
and --
operators increment and decrement a variable:
counter++ // The same as counter = counter + 1
Just like Java and C++, JavaScript copies the C language where ++
can be applied either after or before a variable, yielding the pre-increment or post-increment value.
let counter = 0 let riddle = counter++ let enigma = ++counter
What are the values of riddle
and enigma
? If you don't happen to know, of course, you can find out by carefully parsing the preceding description, by trying it out, or by tapping the fount of wisdom that is the Internet. However, I urge you never to write code that depends on this knowledge.
Some programmers find the ++
and --
operators so reprehensible that they recommend never to use them. And there is no real need—after all, counter += 1
is not much longer than counter++
. In this book, I will use the more concise operators, but never in a situation where their value is captured.
As in Java, the +
operator is also used for string concatenatiton. If s
is a string and x
a value of any type, then s + x
and x + s
are strings, obtained by turning x
into a string and concatenating both strings—that is, combining their characters into one string.
For example,
let counter = 7
let agent = '00' + counter // The string '007'
If an operand in the expression x + y
is not a string or number, then both operands are turned into strings and concatenated, or into numbers and added. When both conversions are possible, the rules get very complex—see Chapter 4 for the details. However, the results are rarely useful. For example, null
+ undefined
is 0 + NaN
or NaN
(see ).
With the other arithmetic operators, only conversion to numbers is attempted. For example, the value of 6 * '7'
is 42—the string '7'
is converted to the number 7
.
Value | To Number | To String |
---|---|---|
A number | Itself | A string containing the digits of the number |
A string containing the digits of a number | The number value | Itself |
The empty string '' |
0 |
'' |
Any other string | NaN |
Itself |
false |
0 |
'false' |
true |
1 |
'true' |
null |
0 |
'null' |
undefined |
NaN |
'undefined' |
The empty array [] |
0 | '' |
An array containing a single number | The number | A string containing the digits of the number |
Other arrays | NaN |
The elements as strings, joined by commas |
Objects | Generally, NaN , but see Chapter 4 |
Generally, '[object Object]' , but see Chapter 4 |
Don't rely on automatic type conversions. They are confusing and can lead to unintended results. Always explictly convert arithmetic operands to numbers. Prefer template strings () over string concatenation.
The Boolean type has two values false
and true
. In a condition, values of any type will be converted to a Boolean value. The values 0, NaN
, null
, undefined
, and the empty string are converted to false
, all others to true
. This sounds simple enough, but as you will see in the following chapter, it can lead to very confusing results.
To minimize such confusion, it is a good idea to use actual Boolean values for all conditions.
null
and undefined
JavaScript has two ways to indicate the absence of a value. When a variable is declared but not initialized, its value is undefined
. This commonly happens with functions. When you call a function and fail to provide a parameter, the parameter variable has the value undefined
.
The null
value is intended to denote the intentional absence of a value.
Is this a useful distinction? There are two schools of thought. Some programmers think that having two “bottom” values is error-prone and suggest that you only use one. In that case, you should use undefined
. You can't avoid undefined
in the Java language, but you can (mostly) avoid null
.
The opposing point of view is that you should always initialize variables and properties, and always use null
for missing values. Then you can (mostly) avoid undefined
.
In any project, explicitly settle on one or the other approach: Use either undefined
or null
for indicating the absence of a value. Otherwise, you end up with pointless philosophical discussions and unnecessary checks for both undefined
and null
.
Unlike null
, undefined
is not a reserved word. It is a variable in the global scope. In ancient times, you were able to assign a new value to the undefined
variable! This was clearly a terrible idea, and now undefined
is a constant. However, you can still a define local variables called undefined
. Of course that's also a bad idea. Don't define local variables NaN
and Infinity
either.
String literals are enclosed in single or double quotes: 'Hello'
or "Hello"
. In this book, I always use single quotes as delimiters.
If you use a quote inside a string that is delimited by the same quote type, you escape it with a backslash. You also escape backslashes and the control characters in Table XREF.
For example, '\'\\\\\'\n'
is a string containing '\\'
followed by a newline.
Escape sequence | Name | Unicode Value |
|
Backspace |
|
|
Tab |
|
|
Linefeed |
|
|
Carriage return |
|
|
Form feed |
|
|
Vertical tab |
|
|
Single quote |
|
|
Double quote |
|
|
Backslash |
|
To understand the next act of the string drama, you have to know about Unicode encoding scheme. Before Unicode, there was a mix of incompatible character encodings where one sequence of bytes could mean very different things to readers in the USA, Russia, or China.
Unicode was designed to solve these problems. When the unification effort started in the 1980s, a fixed 16-bit code was deemed more than sufficient to encode all characters used in all languages in the world, with room to spare for future expansion. In 1991, Unicode 1.0 was released, using slightly less than half of the available 65,536 code values. When JavaScript was created in 1995, it embraced Unicode. JavaScript strings are sequences of 16-bit values.
Unfortunately, over time, the inevitable happened. Unicode grew beyond 65,536 characters, primarily due to the addition of a very large set of ideographs used for Chinese, Japanese, and Korean. Now, Unicode uses 21 bits, and everyone believes that is truly sufficient.
We need a bit of terminology to explain how this problem is resolved with systems that are stuck with 16-bit quantities such as JavaScript and Java. A Unicode code point is a 21-bit code value that is associated with a character. In the Unicode standard, code points are written with four to six hexadecimal digits and prefixed with U+
, such as U+0041
for the code point of the Latin letter A. The “classic” Unicode characters have code points U+0000
to U+FFFF
. The supplementary characters have code points U+10000
to U+10FFFF
. An example is the Unicode character U+1F310
“Globe with meridians” 🌐.
The UTF-16 encoding represents all Unicode code points in a variable-length code. The “classic” Unicode characters are represented as 16-bit values, called code units. The supplementary characters are encoded as consecutive pairs of code units. Each of the values in such an encoding pair falls into a range of 2048 unused values of the basic multilingual plane, called the surrogates area (U+D800
to U+DBFF
for the first code unit, U+DC00
to U+DFFF
for the second code unit). This is rather clever, because you can immediately tell whether a code unit encodes a single character or it is the first or second part of a supplementary character. For example, U+1F310
is encoded by the two surrogate code units U+D83C
and U+DF10
. (See http://en.wikipedia.org/wiki/UTF-16 for a description of the encoding algorithm.)
If you deal with strings that contain Chinese characters or emoji such as the “Globe with meridians” character or many others, you need to know that some characters require a single 16-bit code unit, and others require two.
To include arbitrary Unicode characters in a JavaScript string, you can of course just type or paste them in, provided your source file uses an appropriate encoding (such as UTF-8):
let greeting = 'Hello 🌐'
If it is important to keep your files in ASCII, you can use the \u{code point}
notation:
let greeting = 'Hello \u{1F310}'
Note that this string has “length” 8, even though it contains seven Unicode characters. (Note the space between Hello
and 🌐
.) You can use the bracket operator to access the code units of a string. The expression greeting[0]
is a string consisting of a single letter 'H'
. But the bracket notation doesn't work well for strings with code points above U+FFFF. The code units for the 🌐 character are at positions 6 and 7. The expressions greeting[6]
and greeting[7]
are strings of length 1, each containing a single code unit in the surrogates area. In other words, they are not proper Unicode strings.
In Chapter 2, you will see how you can visit the individual code points of a string with the for of
loop.
You can also provide 16 bit code units in string literals. Then you omit the braces: \uD83C\uDF10
. For code units up to U+00FF, you can use “hex escapes”, for example \xA0
instead of \u{00A0}
. I can think of no good reason to do either.
Chapter XREF lists the various methods that you can use to work with strings.
JavaScript objects are very different from those in class-based languages such as Java and C++. A JavaScript object is simply a set of name/value pairs or “properties”, like this:
{ name: 'Harry Smith', age: 42 }
Such an object has only public data and neither encapsulation nor behavior. The object is not an instance of any particular class. In other words, it is nothing like an object in traditional object-oriented programming. As you will see in Chapter 4, it is possible to define classes and methods, but the mechanisms are very different from most other languages.
An object literal can have a trailing comma. This makes it easy to add other properties as the code evolves:
let harry = { name: 'Harry Smith', age: 42, // Add more properties below }
Once you have an object, you can access its properties with the usual dot notation:
harry.age = 39
You can add new properties:
harry.salary = 150000
Use the delete
operator to remove a property:
delete harry.salary
Accessing a nonexistent property yields undefined
:
let boss = harry.supervisor // undefined
Quite often, when defining an object literal, property values are stored in variables whose names are equal to the property names. For example,
let name = 'Harry Smith' let harry = {name: name
, age: 42 } // The'name'
property is set to the value of thename
variable
There is a shortcut for this situation:
let harry = { name
, age: 42 } // The property name equals the variable name
A property name can be computed. Then you use array brackets to access the property value:
let propertyName = 'age' let harrysAge = harry[propertyName]
You can use a similar bracket syntax in object literals:
let harry = { name: 'Harry Smith', [propertyName] : 42 }
A property name is always a string. If the name doesn't follow the rules of an identifier, quote it in an object literal:
let harry = { name: 'Harry Smith', 'favorite beer': 'IPA' }
To access such a property, you cannot use the dot notation. Use brackets instead:
harry['favorite beer'] = 'Lager'
Such property names are not common, but they can sometimes be convenient. For example, you can have an object whose property names are file names and whose property values are the contents of those files.
Sometimes, an object needs to be converted to a string, for example, when it is concatenated with another string. By default, the result is the string '[object Object]'
. If the object has a toString
method, as in the example above, that method is invoked.
Similarly, when an object occurs in an arithmetic expression, it is converted to a number. If the object has a valueOf
method, it is invoked. Otherwise, the conversion yields NaN
. Chapter 4 has all the details.
There are parsing situations where an opening brace can indicate an object literal or a block statement. In those cases, the block statement takes precedence. For example, if you type
{} - 1
into the browser console or node.js REPL, the empty block is executed. Then, the expression - 1
is evaluated and displayed.
In contrast, in the expression
1 - {}
{}
is an empty object that is converted to NaN
. Then the result ( also NaN
) is displayed.
This ambiguity doesn't normally occur in practical programs. When you form an object literal, you usually store it in a variable, pass it as an argument, or return it as a result. In all those sitations, the parser would not expect a block statement would not be expected.
You will see an exception in Chapter 3—returning an object literal from an arrow expression. The remedy is simple: enclose the object literal in parentheses.
In JavaScript, an array is simply an object whose property names are '0'
, '1'
, '2'
, and so on. You can define array literals by enclosing their elements in square brackets:
let numbers = [1, 7, 2, 9]
This is an object with five properties: '0'
, '1'
, '2'
, '3'
, and 'length'
.
You need to use the bracket notation to access the first four properties: numbers['1']
or numbers[1]
is 7. In general, the argument inside the brackets is converted to a string.
You can leave some elements undefined, like this:
let someNumbers = [, 7, , 9]
Then someNumbers[0]
and someNumbers[2]
yield the value undefined
.
The length
property is one more than the highest index. Both numbers
and someNumbers
have length 4.
A trailing comma does not indicate an undefined element. For example, [1, 2, 7, 9,]
has length 4. As with object literals, trailing commas are intended for literals that may be expanded over time, such as:
const developers = [ 'Harry Smith', 'Sally Lee', // Add more elements below ]
Since arrays are objects, you can add arbitrary properties:
numbers.lucky = true
This is not common, but it is perfectly valid JavaScript.
The typeof
operator returns 'object'
for an array. To test whether an object is an array, call Array.isArray(obj)
.
When an array needs to be converted to a string, all elements are turned into strings and joined with commas. For example,
'' + [1, 2, 3]
is the string '1,2,3'
.
This is generally harmless, but there are two potentially troublesome cases. An empty array is converted to an empty string. And an array of length 1 is converted to a string holding the single element. If that element is a number, the string can be further converted if a number is expected. Consider the expression
[6] * [7]
It evaluates to the number 42.
JavaScript, like Java, has no notion of multi-dimensional arrays, but you can simulate them with arrays of arrays. For example,
const melancholyMagicSquare = [ [16, 3, 2, 13], [5, 10, 11, 8], [9, 6, 7, 12], [4, 15, 14, 1] ]
In Chapter 2, you will see how to visit all elements of an array. Turn to Chapter XREF for a complete discussion of all array methods.
The JavaScript Object Notation or JSON is a lightweight text format for exchanging object data between applications (which may or may not be implemented in JavaScript).
In a nutshell, JSON is very similar to the JavaScript syntax for object and array literals, with a few restrictions:
true
, false
, and null
.See https://www.json.org/ for a formal description of the notation.
An example of a JSON string is:
{ "name": "Harry Smith", "age": 42, "lucky numbers": [17, 29], "lucky": false }
The JSON.stringify
method turns a JavaScript object into a JSON string, and JSON.parse
parses a JSON string, yielding a JavaScript object. These methods are commonly used when communicating with a server via HTTP.
Some programmers use the JSON.stringify
method for logging. A logging command
console.log('harry=' + harry)
gives you a useless message
harry=[object Object]
A remedy is to call JSON.stringify
:
console.log('harry=' + JSON.stringify(harry))
Note that this problem only occurs with strings that contain objects. If you log an object by itself, the console displays it nicely. An easy alternative is to log the names and values separately:
console.log('harry=', harry, 'sally=', sally)
Or even easier, put them into an object:
console.log({harry, sally})
JSON.stringify
drops object properties whose value is undefined
, and it turns array elements with undefined
values to null
. For example, JSON.stringify({ name: ['Harry', undefined, 'Smith'], age: undefined })
is the string '{"name":["Harry",null,"Smith"]}'
.