Module 2 : Variables, Data Types, Type Casting, and Comments
JS Institute and Edube.org deserve all recognition. Just jotted the ideas I didn't know.
After completing Module 2, the student will:
have the knowledge and skills to work with variables (i.e. naming, declaring, initializing and modifying their values)
understand concepts such as scope, code blocks, shadowing, and hoisting;
know the basic properties of primitive data types such as boolean, number, bigint, undefined, null, and be able to use them;
be familiar with the basic properties of the primitive data type string, including string literals – single or double quotes, the escape character, string interpolation, basic properties and methods;
know the basic properties of complex data types such as Array and Object (treated as a record) and be able to use them in practice.
Among JavaScript programmers, the most popular are the following:
Other tools than minimal trio: a code editor, interpreter, and debugger may also be needed :
package managers – enabling the management of libraries (containing ready-made solutions that we can use in our programs) or components of the development environment (e.g. npm or yarn)
task runners and module bundlers – used, in simple terms, to automate the process of software development and merge the resulting code from many files and libraries (e.g. Grunt or Webpack)
testing framework – allows for automatic testing of the correctness of our program in search of potential errors (e.g. Mocha, Jasmine, or Jest)
security analyzers – as you can guess, used to control the security of our solution (e.g. Snyk, RetireJS, or OWASP Dependency Check)
For the declarations, we use the keywords var
or let
for variables and const
for constants.
var height; // This is declaration statement.
console.log(height); // -> outputs "undefined"
console.log(weight); // -> Uncaught ReferenceError: weight is not defined
var vs let
Both are meant for declaring variables. Currently, it is highly recommended to use the word let.
The keyword
var
comes from the original JavaScript syntax, and the keywordlet
was introduced much later. Therefore, you will find var more in older programs.One of the basic differences in the use of var and let is that let prevents us from declaring another variable with the same name (an error is generated). Using var allows you to re-declare a variable, which can potentially lead to errors in the program execution.
strict mode
"use strict";
height = 180; // -> Uncaught ReferenceError: height is not defined
console.log(height);
At the beginning of our code, we’ve added "use strict";
to force the interpreter to behave according to modern JavaScript standards.
The sentence "use strict";
must be placed at the very beginning of the code. It will cause the interpreter to deal with the rest of the code using the modern JavaScript standard.
Notes
Variables in the JavaScript language are weakly and dynamically typed i.e variable can store different types of values (int, string, float) at any point in time.
implicit conversion is possible.
greeting = "Hello!"; greeting = greeting + 100; // number 100 converted to string console.log(greeting); // -> Hello!100
constants
need to be simultaneously declared and initialized.
a change in the constant is impossible.
const greeting; // -> Uncaught SyntaxError: Missing initializer in const declaration greeting = "Hello!"; const greeting = "Hello!"; greeting = "Hi!"; // -> Uncaught TypeError: Assignment to constant variable.
Literals
In this example, we declare the variable year and immediately initiate it with the value 1990. The digits 1990, written in the code at the place of variable initialization, are a literal that represents a number.
Then we display on the console the value 1991 and "Alice", in both cases using literals (representing a number and a string respectively). In JavaScript, almost each data type has its own literal.
let year = 1990; console.log(year); // -> 1990 console.log(1991); // -> 1991 console.log("Alice"); // -> Alice
Scope
Seems LEGB.
Unfortunately, the scopes of variables (and constants) declared with let
and const
look slightly different than those declared with var.
Any variable or constant declared using
let
orconst
, can percolate inside nested code blocks. They can't expand their scope outside the block in which they were defined.// What happens if we declare something using let or const // inside a block? let height = 180; { let weight = 70; console.log(height); // -> 180 console.log(weight); // -> 70 } console.log(height); // -> 180 console.log(weight); // -> Uncaught ReferenceError: weight is not defined
If you declare with
var
inside a block, then... well, it will usually turn out to be global again. Both variables,height
andweight
, turn out to be global. Will the variables declared usingvar
always, regardless of the place of declaration, be global? Definitely not. The problem is that var ignores ordinary program blocks, treating them as if they do not exist.var height = 180; { var weight = 70; console.log(height); // -> 180 console.log(weight); // -> 70 } console.log(height); // -> 180 console.log(weight); // -> 70
So if variable is declared using
var
, mostly it will be global & available throughout the program. Variable declarations using the wordvar
can only be local when they are defined inside the function.var globalGreeting = "Good "; function testFunction() { var localGreeting = "Morning "; console.log("function:"); console.log(globalGreeting); console.log(localGreeting); } testFunction(); console.log("main program:"); console.log(globalGreeting); console.log(localGreeting); // -> Uncaught ReferenceError: localGreeting is not defined
Variables declared with the let
keyword are local inside the code block (i.e. inside the range limited by curly brackets), while variables declared with the var
keyword are local inside the function block.
So if you declare a variable inside a function block, whether using let
or var
, it will only be visible (and usable) inside the function block. This is very useful, because usually the variables you use inside a function are not needed outside of it.
Variable shadowing
It means that we can declare a global variable and a local variable of the same name.
In the local scope, in which we declare a local variable using its name, we will have access to the local value (the global variable is hidden behind the local one, so we do not have access to it in this local scope). Using this name outside the local scope means that we will be referring to the global variable. This is not best programming practice, however, and try to avoid giving the same variable names to multiple variables, regardless of where you declare them.
let counter = 100; // same with var
console.log(counter); // -> 100
{
let counter = 200;
console.log(counter); // -> 200
}
console.log(counter); // -> 100
Variable Hoisting
A good practice is always to declare variables before they are used but the original JavaScript syntax allows for some deviations from this rule. The JavaScript interpreter scans the program before running it.
It searches for all variable declarations and moves them to the beginning of the range in which they were declared (to the beginning of the program if they are global, to the beginning of the block if it is a local let
declaration, or to the beginning of the function if it is a local var
declaration).
var height = 180;
console.log(height); // -> 180
console.log(weight); // -> Uncaught ReferenceError: weight is not defined
In the above example, we forgot to declare the variable weight. The result is obvious: we’re referring to a variable (that is, we’re trying to read its contents) which does not exist.
var height = 180;
console.log(height); // -> 180
console.log(weight); // -> undefined
var weight = 70;
console.log(weight); // -> 70
Here, Hoisting has worked, and the declaration has been moved by the interpreter to the beginning of the range (in this case the program).
However, the attempt to display the contents of the weight variable give two different results. Why? Hoisting only concerns the declaration, not initialization. So the value 70, which we assign to the weight variable, remains on the line where the original declaration is. The above example is interpreted by the JavaScript engine more or less in the following way:
var weight;
var height = 180;
console.log(height); // -> 180
console.log(weight); // -> undefined
weight = 70;
console.log(weight); // -> 70
Hoisting unfortunately works a little differently with the let
and const
declarations.
Most of all, you will remember always TO DECLARE VARIABLES BEFORE USING THEM.
Summary 2.1
Remember to declare variables before using them.
Pay attention to where you declare them – whether they are local or global.
Try to use the keywords let and const, not the word var. Knowing the latter will be useful not for understanding the examples found in various sources, but avoid using it yourself.
Remember not to use the same names for different variables, even if you declare them in different ranges.
Primitive data types
There are six primitive (or simple) data types: Boolean, Number, BigInt, String, Symbol, and undefined. Additionally, the primitive null value is also treated as a separate type. The primitive is a type of data whose values are atomic, in other words, it will not be possible to extract components from it.
All possible return values of the typeof
operator are:
"undefined" "object" "boolean" "number" "bigint" "string" "symbol" "function"
Boolean
The value false
is always returned for:
0
,NaN
,empty string,
undefined
,null
Any other value will result in true
being returned.
console.log(Boolean(true)); // -> true
console.log(Boolean(42)); // -> true
console.log(Boolean(0)); // -> false
console.log(Boolean(NaN)); // -> false
console.log(Boolean("text")); // -> true
console.log(Boolean("")); // -> false
console.log(Boolean(undefined)); // -> false
console.log(Boolean(null)); // -> false
Number
Represents both real numbers (e.g. fractions) and integers.
let a = 10; // decimal - default
let delayInSeconds = 0.00016; // fraction
let b = 0x10; // hexadecimal
let c = 0o10; // octal
let d = 0b10; // binary
console.log(a); // -> 10
console.log(b); // -> 16
console.log(c); // -> 8
console.log(d); // -> 2
let x = 9e3; // exponential form
let y = 123e-5; // exponential form
console.log(x); // -> 9000
console.log(y); // -> 0.00123
We use three additional special values, which are: Infinity
, -Infinity
and NaN
(not a number).
NaN
, is not so much a numerical value but a notification that some arithmetic action (or mathematical function) could not be performed because the argument is either not a number, or cannot be converted to a number.
let a = 1 / 0;
let b = -Infinity;
console.log(a); // -> Infinity
console.log(b); // -> -Infinity
console.log(typeof a); // -> number
console.log(typeof b); // -> number
let s = "it's definitely not a number";
let n = s * 10;
console.log(n); // -> NaN
console.log(typeof n); // -> number
BigInt
It allows us to write integers of virtually any length.
As the BigInt is an integer type, the division result will always be rounded down to the nearest whole number.
BigInt literals are numbers with the …n suffix.
You cannot use other types in arithmetic operations on BigInts.
The BigInt does not have its own equivalent of Infinity
or NaN
values. In the case of the BigInt type, such actions will generate an error.
let big = 1234567890000000000000n;
let big2 = 1n;
console.log(typeof big); // -> bigint
console.log(big2); // -> 1n
console.log(7n / 4n); // -> 1n
let big3 = 1000n + 20; // -> Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
let big4 = 1000n / 0n; // -> Uncaught RangeError: Division by zero
Strings
Strings, like other primitives, are immutable, so when we want to change even one letter in a string, in reality, we create a new string.
Seeing the arithmetic operators -
, *
, or \
, the JavaScript interpreter tries to interpret the given values as numbers, or convert them into numbers. So if the character strings consists of digits, the automatic conversion will be successful and we will get the result of the arithmetic action as a Number type value. If the character string cannot be interpreted as a number (and converted) we will get the NaN result.
let path = "C:\\Windows" - "Windows";
console.log(path); // -> NaN
let test = "100" - "10";
console.log(test); // -> 90
console.log(typeof test); // -> number
The exception is the addition operation, which will not be treated as an arithmetic one, but as an attempt to create a new string by combining two input strings.
let path = "C:\\" + "Windows";
console.log(path); // -> C:\Windows
let test = "100" + "10";
console.log(test); // -> 10010
console.log(typeof test); // -> string
String interpolation
Such a literal is created using backticks `
instead of quotation marks.
The places where values are inserted are marked with curly brackets preceded by a $
sign.
let country = "Malawi";
let continent = "Africa";
let sentence = `${country} is located in ${continent}.`;
console.log(sentence); // -> Malawi is located in Africa.
Undefined
The undefined type has only one value: undefined
. It’s the default value that all variables have after a declaration if no value is assigned to them. You can also assign the value undefined
to any variable, but in general, this should be avoided, because if we need to mark a variable as not holding any meaningful value, we should use null
.
Let declaredVar;
console.log(typeof declaredVar); // -> undefined
declaredVar = 5;
console.log(typeof declaredVar); // -> number
declaredVar = undefined;
console.log(typeof declaredVar); // -> undefined
The undefined value can also be returned by the typeof operator when a non-existent variable is used as an argument.
Console.log(typeof notDeclaredVar); // -> undefined
console.log(notDeclaredVar); // -> Uncaught ReferenceError: notDeclared is not defined
Symbol
It’s a new primitive type that was added to JavaScript in 2015. It doesn't have any literal value, and can only be created using a special constructor function. Symbols are a form of identifier that are guaranteed to be unique.
null
The value itself is a primitive, while the type to which it belongs is not a primitive type, such as Number or undefined. When checked with the typeof
operator for null
, it will return "object"
.
The null
value is used to indicate that the variable right now does not contain anything, and most often it is a variable that is intended to contain values of complex types.
we can assume that the undefined
value is assigned to uninitialized variables automatically, but if we want to explicitly indicate that the variable does not contain anything, we assign it a null
value.
let someResource;
console.log(someResource); // -> undefined
console.log(typeof someResource); // -> undefined
someResource = null;
console.log(someResource); // -> null
console.log(typeof someResource); // -> object
Type conversions
Primitive construction functions
The following functions will return primitives of a given type: Boolean
, Number
, BigInt
, and String
.
Most of these functions can be called without any arguments. In such a situation:
the function
String
will by default create and return an empty string – primitive "";the function
Number
will by default create and return the value 0;the function
Boolean
will by default create and return the value of false.
The function BigInt
, unlike other constructor functions, requires you to pass some initial value to it. This can be an integer number that will be converted to a BigInt (see examples).
const str = String();
const num = Number();
const bool = Boolean();
console.log(str); // ->
console.log(num); // -> 0
console.log(bool); // -> false
const big1 = BigInt(42);
console.log(big1); // -> 42n
const big2 = BigInt(); // -> Uncaught TypeError: Cannot convert undefined to a BigInt
But creating default values is not impressive at all. We can accomplish these using literals. So what do we use these functions for? Well, we use them in type conversions.
Explicit conversions
A BigInt
can also be converted to a Number
, but we need to remember that a BigInt can store much bigger values than a Number, so for large values, part of them can be truncated or end up being imprecise.
console.log(Number(42)); // -> 42
console.log(Number("11")); // -> 11
console.log(Number("0x11")); // -> 17
console.log(Number("0o11")); // -> 9
console.log(Number("0b11")); // -> 3
console.log(Number("12e3")); // -> 12000
console.log(Number("Infinity"));// -> Infinity
console.log(Number("text")); // -> NaN : any string that cannot be converted
console.log(Number(14n)); // -> 14
console.log(Number(123456789123456789123n)); // - > 123456789123
456800000
console.log(Number(true)); // -> 1
console.log(Number(false)); // -> 0
console.log(Number(undefined)); // -> NaN
console.log(Number(null));// -> 0
Unlike other conversions, conversion to a BigInt will throw an error, and will stop the program when unable to convert a given value. Please pay attention to the fact that the first error prevents further code execution.
console.log(BigInt(11)); // -> 11n
console.log(BigInt(0x11)); // -> 17n
console.log(BigInt(11e2)); // -> 1100n
console.log(BigInt(true)); // -> 1n
console.log(BigInt("11")); // -> 11n
console.log(BigInt("0x11")); // -> 17n
console.log(BigInt(null)); // -> Uncaught TypeError: Cannot convert null to a BigInt
console.log(BigInt(undefined)); // -> Uncaught TypeError: Cannot convert undefined to a BigInt
console.log(BigInt(NaN)); // -> Uncaught RangeError: The number NaN cannot be converted to a BigInt because it is not an integer
Implicit conversions
Conversions can also happen automatically, and they happen all the time.
const str1 = 42 + "1"; // when one of the arguments is a string, JavaScript will convert the rest of the arguments to a string
console.log(str1); // -> 421
console.log(typeof str1); // -> string
const str2 = 42 - "1"; // Subtraction with a string, doesn't make much sense. So JavaScript converts everything to Numbers.
console.log(str2); // -> 41
console.log(typeof str2); // -> number
Autoboxing
All data of primitive types such as Number, BigInt, Boolean, or String have corresponding objects to which they can be converted. Each of these objects will have methods designed for a specific data type.
If a dot appears after a literal representing a primitive type, or after a variable containing this type of data, the JavaScript interpreter tries to treat this value as an object and not a primitive. For this purpose, it converts the primitive to the corresponding object on the fly, which has the appropriate methods (i.e. it performs autoboxing).
let river = "Mekong";
let character = river.charAt(2);
console.log(character); // -> k
In the variable river
, we store the primitive of a String type. In the next line, we refer to this variable, writing a dot after its name and the name of one of the methods – charAt
(a method of the String class object). Although the primitive has no methods that can be called, the interpreter temporarily converts this value to a suitable object that already has such methods. One of these methods is charAt
.
After the operation is completed, the interpreter removes the temporary object. So from our point of view, it looks like we just called a method on a given primitive type.
Complex data types
May consist of multiple elements, each of which may be of a primitive or composite type.
Two of the complex types : objects and arrays.
Object
A record is a collection of named fields. Each field has its own name (or key) and value assigned to it. In the case of JavaScript objects, these fields are usually called properties.
It allow you to store multiple values of different types in one place.
What do we need objects for? The simplest reason for using them may be the desire to store several values in one place, which are linked to each other for some reason.
// empty object
let testObj = {};
console.log(typeof testObj); // -> object
// defining an object containing two fields with keys nr and str
let testObj = {
nr: 600,
str: "text"
};
We can also modify the whole object by adding a new, previously non-existent property. We also do this using dot notation – if during an attempt to modify the property the interpreter does not find the key we specify, it will create it.
If you can add new fields to an existing object, can you also delete them? Of course you can: the delete operator is used for this.
let user1 = {
name: "Calvin",
surname: "Hart",
age: 66,
email: "CalvinMHart@teleworm.us"
};
console.log(user1.age); // -> 66
user1.age = 67;
console.log(user1.age); // -> 67
console.log(user2.phone); // -> undefined
user2.phone = "904-399-7557";
console.log(user2.phone); // -> 904-399-7557
console.log(user2.phone); // -> 904-399-7557
delete user2.phone;
console.log(user2.phone); // -> undefined
Array
The stored data (the values) can be of any type. The difference between these structures is that in an array we only store values, without the associated names (i.e. the keys).
let emptyArray = [];
console.log(emptyArray[0]); // -> undefined
let values = ["Test", 7, 12.3, false];
What's interesting is that we don't have to fill the array with elements one by one – you can leave empty spaces in it.
let animals = [];
console.log(animals[0]); // -> undefined
animals[0] = "dog";
animals[2] = "cat";
console.log(animals[0]); // -> dog
console.log(animals[1]); // -> undefined
console.log(animals[2]); // -> cat
Array elements can be any data, including another array, objects.
let names = [["Olivia", "Emma", "Mia", "Sofia"], ["William", "James", "Daniel"]];
console.log(names[0][1]); // -> Emma
console.log(names[1][1]); // -> James
let femaleNames = names[0];
let malesNames = names[1];
let users =[
{
name: "Calvin",
surname: "Hart",
age: 66,
email: "CalvinMHart@teleworm.us"
},
{
name: "Mateus",
surname: "Pinto",
age: 21,
email: "MateusPinto@dayrep.com"
}
];
console.log(users[0].name); // -> Calvin
console.log(users[1].age); // -> 21
Apply the typeof
operator to the variable containing the array. In JavaScript, everything except primitives is an object.
Arrays are also treated as a special kind of object. The typeof
operator does not distinguish between object types (or more precisely, classes), so it informs us that the days variable contains an object. If we would like to make sure that the variable contains an array, we can do it using the instanceof
operator.
let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let day = "Sunday";
console.log(typeof days); // -> object
console.log(typeof day); // -> string
console.log(days instanceof Array); // -> true
console.log(day instanceof Array); // -> false
method and property
Now it turns out that an array is implemented as an object in JavaScript, so it probably also has its methods and properties.
The length is a property and not a method.
The slice, concat
method allows you to create a new array from selected elements of the original array. Calling the method does not affect the original array.
let names = ["Olivia", "Emma", "Mateo", "Samuel"];
console.log(names.length); // -> 4
console.log(names.indexOf("Mateo"));
names.push("Amelia"); // append it to the right end of the array.
names.unshift("Javed"); // new element is added to the beginning of the array.
let name = names.pop(); // remove the last element from the array.
name = names.shift(); // remove the element from the beginning of the array.
names.reverse();
let n1 = names.slice(2);
console.log(n1); // -> ["Mateo", "Samuel"]
let n2 = names.slice(1,3);
console.log(n2); // -> ["Emma", "Mateo"]
let n3 = names.slice(0, -1);
console.log(n3); // -> ["Olivia", "Emma", "Mateo"]
let n4 = names.slice(-2);
console.log(n4); // -> ["Samuel", "Mateo"]
console.log(names); // -> ["Olivia", "Emma", "Mateo","Samuel"]
let otherNames = ["William", "Jane"];
let allNames = names.concat(otherNames);
console.log(allNames); // -> ["Olivia", "Emma", "Mateo", "Samuel", "William", "Jane"]