Any value that you use is of a certain type. In JavaScript, the following are just a few primitive data types:
1
, 100
, 3
.14
.a
, one
, and one 2 three
.true
or false
.undefined
. The undefined data type can only have one value-the special value undefined
.null
value. It means no value, an empty value, or nothing. The difference with undefined is that if a variable has a null value, it's still defined; it just so happens that its value is nothing. You'll see some examples shortly.Any value that doesn't belong to one of the five primitive types listed here is an object. Even null is considered an object, which is a little awkward having an object (something) that is actually nothing. We'll learn more about objects in Chapter 4, Objects, but for the time being, just remember that in JavaScript, the data types are as follows:
If you want to know the type of a variable or a value, you can use the special typeof
operator. This operator returns a string that represents the data type. The return values of using typeof
are one of the following:
In the next few sections, you'll see typeof
in action using examples of each of the five primitive data types.
The simplest number is an integer. If you assign 1
to a variable, and then use the typeof
operator, it returns the string number
, as follows:
> var n = 1; > typeof n; "number" > n = 1234; > typeof n; "number"
In the preceding example, you can see that the second time you set a variable's value, you don't need the var
statement.
Numbers can also be floating point (decimals), for example:
> var n2 = 1.23; > typeof n; "number"
You can call typeof
directly on the value without assigning it to a variable first, for example:
> typeof 123; "number"
When a number starts with a 0
, it's considered an octal number. For example, the octal 0377
is the decimal 255
:
> var n3 = 0377; > typeof n3; "number" > n3; 255
The last line in the preceding example prints the decimal representation of the octal value.
ES6 provides a prefix 0o
(or 0O
, but this looks very confusing in most monospace fonts) to represent octals. Consider the following line of code for example:
console.log(0o776); //510
While you may not be intimately familiar with octal numbers, you've probably used hexadecimal values to define colors in CSS stylesheets.
In CSS, you have several options to define a color, two of them are as follows:
0
to 255
. For example, rgb(0, 0, 0) is black and rgb(255, 0, 0) is red (maximum amount of red and no green or blue).255
.In JavaScript, you can put 0x
before a hexadecimal value, also called hex for short, for example:
> var n4 = 0x00; > typeof n4; "number" > n4; 0 > var n5 = 0xff; > typeof n5; "number" > n5; 255
Untill ES6, if you needed binary representation of an integer, you had to pass them to the parseInt()
function as a string with a radix of 2
, as follows:
console.log(parseInt('111',2)); //7
In ES6 you can use 0b
(or 0B
) prefix to represent binary integers. For example:
console.log(0b111); //7
1e1
(also written as 1e+1
or 1E1
or 1E+1
) represents the number 1 with a 0 after it, or in other words, 10
. Similarly, 2e+3
represents the number 2 with three 0s after it, or 2000
, for example:
> 1e1; 10 > 1e+1; 10 > 2e+3; 2000 > typeof 2e+3; "number"
2e+3
means moving the decimal point three digits to the right of the number 2. There's also 2e-3
, meaning you move the decimal point three digits to the left of the number 2. Look at the following figure:
The following is the code:
> 2e-3; 0.002 > 123.456E-3; 0.123456 > typeof 2e-3; "number"
There is a special value in JavaScript called Infinity. It represents a number too big for JavaScript to handle. Infinity is indeed a number, as typing typeof Infinity
in the console will confirm. You can also quickly check that a number with 308
zeros is ok, but 309
zeros is too much. To be precise, the biggest number JavaScript can handle is 1.7976931348623157e+308
, while the smallest is 5e-324
, Look at the following example:
> Infinity; Infinity > typeof Infinity; "number" > 1e309; Infinity > 1e308; 1e+308
Dividing by zero gives you infinity, for example:
> var a = 6 / 0; > a; Infinity
Infinity
is the biggest number (or rather a little bigger than the biggest), but how about the smallest? It's infinity with a minus sign in front of it; -Infinity
, for example:
> var i = -Infinity; > i; -Infinity > typeof i; "number"
Does this mean you can have something that's exactly twice as big as Infinity, from 0 up to infinity and then from 0 down to minus infinity? Well, not really. When you sum Infinity
and -Infinity
, you don't get 0
, but something that is called Not a Number (NaN), For example:
> Infinity - Infinity; NaN > -Infinity + Infinity; NaN
Any other arithmetic operation with Infinity
as one of the operands gives you Infinity
, for example:
> Infinity - 20; Infinity > -Infinity * 3; -Infinity > Infinity / 2; Infinity > Infinity - 99999999999999999; Infinity
There is a lesser known global method, isFinite()
, that tells you if the value is infinity or not. ES6 adds a Number.isFinite()
method to do just that. Why another method, you may ask. The global variant of isFinite()
tries to cast the value through Number(value), while Number.isFinite()
doesn't, hence it's more accurate.
What was this NaN
in the previous example? It turns out that despite its name, Not a Number, NaN
is a special value that is also a number:
> typeof NaN; "number" > var a = NaN; > a; NaN
You get NaN
when you try to perform an operation that assumes numbers, but the operation fails. For example, if you try to multiply 10
by the character "f"
, the result is NaN
, because "f"
is obviously not a valid operand for a multiplication:
> var a = 10 * "f"; > a; NaN
NaN
is contagious, so if you have even one NaN
in your arithmetic operation, the whole result goes down the drain, for example:
> 1 + 2 + NaN; NaN
ES5 has a global method-isNaN()
. It determines if a value is NaN
or not. ES6 provides a very similar method-Number.isNaN()
(Notice that this method is not global).
The difference between the global isNaN()
and Number.isNaN()
is that global isNaN()
casts non-numeric values before evaluating them to be NaN
. Let's look at the following example. We are using the ES6 Number.isNaN()
method to test if something is a NaN
or not:
console.log(Number.isNaN('test')); //false : Strings are not NaN console.log(Number.isNaN(123)); //false : integers are not NaN console.log(Number.isNaN(NaN)); //true : NaNs are NaNs console.log(Number.isNaN(123/'abc')); //true : 123/'abc' results in an NaN
We saw that ES5's global isNaN()
method first casts non-numeric values and then does the comparison; the following result will be different from its ES6 counterpart:
console.log(isNaN('test')); //true
In general, compared to its global variant, Number.isNaN()
is more correct. However, neither of them can be used to figure out if something is not a number-they just answer if the value is a NaN
or not. Practically, you are interested in knowing if a value identifies as a number or not. Mozilla suggests the following polyfill method to do just that:
function isNumber(value) { return typeof value==='number' && !Number.isNaN(value); }
This is a new method in ES6. It returns true
if the number is finite and does not contain any decimal points (is an integer):
console.log(Number.isInteger('test')); //false console.log(Number.isInteger(Infinity)); //false console.log(Number.isInteger(NaN)); //false console.log(Number.isInteger(123)); //true console.log(Number.isInteger(1.23)); //false
A string is a sequence of characters used to represent text. In JavaScript, any value placed between single or double quotes is considered a string. This means that 1
is a number, but "1"
is a string. When used with strings, typeof
returns the string "string"
, for example:
> var s = "some characters"; > typeof s; "string" > var s = 'some characters and numbers 123 5.87'; > typeof s; "string"
Here's an example of a number used in the string context:
> var s = '1'; > typeof s; "string"
If you put nothing in quotes, it's still a string (an empty string), for example:
> var s = ""; typeof s; "string"
As you already know, when you use the plus sign with two numbers, this is the arithmetic addition operation. However, if you use the plus sign with strings, this is a string concatenation operation, and it returns the two strings glued together:
> var s1 = "web"; > var s2 = "site"; > var s = s1 + s2; > s; "website" > typeof s; "string"
The dual purpose of the +
operator is a source of errors. Therefore, if you intend to concatenate strings, it's always best to make sure that all of the operands are strings. The same applies for addition; if you intend to add numbers then make sure the operands are numbers. You'll learn various ways to do so further in the chapter and the book.
When you use a number-like string, for example, "1"
, as an operand in an arithmetic operation, the string is converted to a number behind the scenes. This works for all arithmetic operations except addition, because of its ambiguity. Consider the following example:
> var s = '1'; > s = 3 * s; > typeof s; "number" > s; 3 > var s = '1'; > s++; > typeof s; "number" > s; 2
A lazy way to convert any number-like string to a number is to multiply it by 1
(another way is to use a function called parseInt()
, as you'll see in the next chapter):
> var s = "100"; typeof s; "string" > s = s * 1; 100 > typeof s; "number"
If the conversion fails, you'll get NaN
:
> var movie = '101 dalmatians'; > movie * 1; NaN
You can convert a string to a number by multiplying it by 1
. The opposite-converting anything to a string-can be done by concatenating it with an empty string, as follows:
> var n = 1; > typeof n; "number" > n = "" + n; "1" > typeof n; "string"
There are also strings with special meanings, as listed in the following table:
String |
Meaning |
Example |
|
The
If you want to have an actual backslash in the string, escape it with another backslash.
|
> var s = 'I don't know'; > var s = "I don't know"; > var s = "I don't know"; > var s = '"Hello", he said.'; > var s = ""Hello", he said."; Escaping the escape: > var s = "1\\2"; s; "1\2"
|
|
End of line. |
> var s = '\n1\n2\n3\n'; > s; " 1 2 3 "
|
|
Carriage return. |
Consider the following statements: > var s = '1\r2'; > var s = '1\n\r2'; > var s = '1\r\n2';
The result of all of these is as follows:
> s; "1 2"
|
|
Tab. |
> var s = "1\t2"; > s; "1 2"
|
|
The |
Here's my name in Bulgarian written with Cyrillic characters: > "\u0421\u0442\u043E\u044F\u043D"; "Стoян"
|
There are also additional characters that are rarely used: \b
(backspace), \v
(vertical tab), and \f
(form feed).
ES6 introduced template literals. If you are familiar with other programming languages, Perl and Python have supported template literals for a while now. Template literals allow expressions to be embedded within regular strings. ES6 has two kinds of literals: template literals and tagged literals.
Template literals are single or multiple line strings with embedded expressions. For example, you must have done something similar to this:
var log_level="debug"; var log_message="meltdown"; console.log("Log level: "+ log_level + " - message : " + log_message); //Log level: debug - message : meltdown
You can accomplish the same using template literals, as follows:
console.log(`Log level: ${log_level} - message: ${log_message}`)
Template literals are enclosed by the back-tick (``
) (grave accent) character instead of the usual double or single quotes. Template literal place holders are indicated by the dollar sign and curly braces (${expression}
). By default, they are concatenated to form a single string. The following example shows a template literal with a slightly complex expression:
var a = 10; var b = 10; console.log(`Sum is ${a + b} and Multiplication would be ${a * b}.`); //Sum is 20 and Multiplication would be 100.
How about embedding a function call?
var a = 10; var b = 10; function sum(x,y){ return x+y } function multi(x,y){ return x*y } console.log(`Sum is ${sum(a,b)} and Multiplication would be ${multi(a,b)}.`);
Template literals also simplify multiline string syntax. Instead of writing the following line of code:
console.log("This is line one \n" + "and this is line two");
You can have a much cleaner syntax using template literals, which is as follows:
console.log(`This is line one and this is line two`);
ES6 has another interesting literal type called Tagged Template Literals. Tagged templates allow you to modify the output of template literals using a function. If you prefix an expression to a template literal, that prefix is considered to be a function to be invoked. The function needs to be defined before we can use the tagged template literal. For example, the following expression:
transform`Name is ${lastname}, ${firstname} ${lastname}`
The preceding expression is converted into a function call:
transform([["Name is ", ", ", " "],firstname, lastname)
The tag function, 'transform', gets two parameters-template strings like Name is
and substitutions defined by ${}
. The substitutions are only known at runtime. Let's expand the transform
function:
function transform(strings, ...substitutes){ console.log(strings[0]); //"Name is" console.log(substitutes[0]); //Bond } var firstname = "James"; var lastname = "Bond" transform`Name is ${lastname}, ${firstname} ${lastname}`
When template strings (Name is
) are passed to the tag function, there are two forms of each template string, as follows:
You can access the raw string form using raw property, as the following example shows:
function rawTag(strings,...substitutes){ console.log(strings.raw[0]) } rawTag`This is a raw text and \n are not treated differently` //This is a raw text and \n are not treated differently
There are only two values that belong to the Boolean data type-the true
and false
values used without quotes:
> var b = true; > typeof b; "boolean" > var b = false; > typeof b; "boolean"
If you quote true
or false
, they become strings, as shown in the following example:
> var b = "true"; > typeof b; "string"
There are three operators, called logical operators, that work with Boolean values. These are as follows:
! - logical NOT (negation) && - logical AND || - logical OR
You know that when something is not true, it must be false. Here's how this is expressed using JavaScript and the logical !
operator:
> var b = !true; > b; false
If you use the logical NOT
twice, you will get the original value, which is as follows:
> var b = !!true; > b; true
If you use a logical operator on a non-Boolean value, the value is converted to Boolean behind the scenes, as follows:
> var b = "one"; > !b; false
In the preceding case, the string value "one"
is converted to a Boolean, true
, and then negated. The result of negating true
is false
. In the following example, there's a double negation, so the result is true
:
> var b = "one"; > !!b; true
You can convert any value to its Boolean equivalent using a double negation. Understanding how any value converts to a Boolean is important. Most values convert to true
with the exception of the following, which convert to false
:
""
0
NaN
false
These six values are referred to as falsy, while all others are truthy, (including, for example, the strings "0"
, " "
, and "false"
).
Let's see some examples of the other two operators-the logical AND
(&&
) and the logical OR
(||
). When you use &&
, the result is true
only if all of the operands are true
. When you use ||
, the result is true
if at least one of the operands is true
:
> var b1 = true, b2 = false; > b1 || b2; true > b1 && b2; false
Here's a list of the possible operations and their results:
Operation |
Result |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You can use several logical operations one after the other, as follows:
> true && true && false && true; false > false || true || false; true
You can also mix &&
and ||
in the same expression. In such cases, you should use parentheses to clarify how you intend the operation to work. Consider the following example:
> false && false || true && true; true > false && (false || true) && true; false
You might wonder why the previous expression (false && false || true && true
) returned true
. The answer lies in the operator precedence, as you know from mathematics:
> 1 + 2 * 3; 7
This is because multiplication has a higher precedence over addition, so 2 * 3
is evaluated first, as if you typed:
> 1 + (2 * 3); 7
Similarly for logical operations, !
has the highest precedence and is executed first, assuming there are no parentheses that demand otherwise. Then, in the order of precedence, comes &&
and finally, ||
. In other words, the following two code snippets are the same. The first one is as follows:
> false && false || true && true; true
And the second one is as follows:
> (false && false) || (true && true); true
The ECMAScript standard defines the precedence of operators. While it may be a good memorization exercise, this book doesn't offer it. First of all, you'll forget it, and second, even if you manage to remember it, you shouldn't rely on it. The person reading and maintaining your code will likely be confused.
If you have several logical operations one after the other, but the result becomes clear at some point before the end, the final operations will not be performed because they don't affect the end result. Consider the following line of code as an example:
> true || false || true || false || true; true
As these are all OR
operations and have the same precedence, the result will be true
if at least, one of the operands is true
. After the first operand is evaluated, it becomes clear that the result will be true
, no matter what values follow. So, the JavaScript engine decides to be lazy (OK, efficient) and avoids unnecessary work by evaluating code that doesn't affect the end result. You can verify this short-circuiting behavior by experimenting in the console, as shown in the following code block:
> var b = 5; > true || (b = 6); true > b; 5 > true && (b = 6); 6 > b; 6
This example also shows another interesting behavior-if JavaScript encounters a non-Boolean expression as an operand in a logical operation, the non-Boolean is returned as a result:
> true || "something"; true > true && "something"; "something" > true && "something" && true; true
This behavior is not something you should rely on because it makes the code harder to understand. It's common to use this behavior to define variables when you're not sure whether they were previously defined. In the next example, if the mynumber
variable is defined, its value is kept; otherwise, it's initialized with the value 10
:
> var mynumber = mynumber || 10; > mynumber; 10
This is simple and looks elegant, but be aware that it's not completely foolproof. If mynumber
is defined and initialized to 0
, or to any of the six falsy values, this code might not behave as you expect, as shown in the following piece of code:
> var mynumber = 0; > var mynumber = mynumber || 10; > mynumber; 10
There's another set of operators that all return a Boolean value as a result of the operation. These are the comparison operators. The following table lists them together with example uses:
Operator symbol |
Description |
Example |
|
Equality comparison: This returns |
> 1 == 1; true > 1 == 2; false > 1 =='1'; true
|
|
Equality and type comparison: This returns |
> 1 === '1'; false > 1 === 1; true
|
|
Non-equality comparison: This returns |
> 1 != 1; false > 1 != '1'; false > 1 != '2'; true
|
|
Non-equality comparison without type conversion: Returns |
> 1 !== 1; false > 1 !== '1'; true
|
|
This returns |
> 1 > 1; false > 33 > 22; true
|
|
This returns |
> 1 >= 1; true
|
|
This returns |
> 1 < 1; false > 1 < 2; true
|
|
This returns |
> 1 <= 1; true > 1 <= 2; true
|
Note that NaN
is not equal to anything, not even itself. Take a look at the following line of code:
> NaN == NaN; false
If you try to use a non-existing variable, you'll get the following error:
> foo; ReferenceError: foo is not defined
Using the typeof
operator on a non-existing variable is not an error. You will get the "undefined"
string back, as follows:
> typeof foo; "undefined"
If you declare a variable without giving it a value, this is, of course, not an error. But, the typeof
still returns "undefined"
:
> var somevar; > somevar; > typeof somevar; "undefined"
This is because, when you declare a variable without initializing it, JavaScript automatically initializes it with the undefined
value, as shown in the following lines of code:
> var somevar; > somevar === undefined; true
The null
value, on the other hand, is not assigned by JavaScript behind the scenes; it's assigned by your code, which is as follows:
> var somevar = null; null > somevar; null > typeof somevar; "object"
Although the difference between null
and undefined
is small, it can be critical at times. For example, if you attempt an arithmetic operation, you will get different results:
> var i = 1 + undefined; > i; NaN > var i = 1 + null; > i; 1
This is because of the different ways null
and undefined
are converted to the other primitive types. The following examples show the possible conversions:
> 1 * undefined;
> 1 * null; 0
> !!undefined; false > !!null; false
> "value: " + null; "value: null" > "value: " + undefined; "value: undefined"
ES6 introduced a new primitive type-symbols. Several languages have a similar notion. Symbols look very similar to regular strings, but they are very different. Let's see how these symbols are created:
var atom = Symbol()
Notice that we don't use new
operator while creating symbols. You will get an error when you do use it:
var atom = new Symbol() //Symbol is not a constructor
You can describe Symbol
as well:
var atom = Symbol('atomic symbol')
Describing symbols comes in very handy while debugging large programs where there are lots of symbols scattered across.
The most important property of Symbol
(and hence the reason of their existence) is that they are unique and immutable:
console.log(Symbol() === Symbol()) //false console.log(Symbol('atom') === Symbol('atom')) // false
For now, we will have to pause this discussion on symbols. Symbols are used as property keys and places where you need unique identifiers. We will discuss symbols in a later part of this book.