install IJavaScript kernel from here: https://github.com/n-riesco/ijavascript#windows-official-python-distribution
JavaScript is a scripting and programming language that runs in a web browser. Its primary purpose is to make web pages more interactive. A web page, as you currently know it, consists of only two things: HTML, which provides the structure of the page as well as its content, and CSS which provides its visual styling. JavaScript, then, exists to give the web page interactivity. It can do a number of key things to make your web pages more interactive, such as:
JavaScript was originally intended to run only in the browser, but modern JavaScript doesn't even require a browser to run. Today it can be used as a front end or a backend language interchangeably, or both at the same time. In the past, to handle things like connecting to databases and back end processes like aggregating and manipulating data, you needed to rely on server-side languages like Python or C++, but with the advent of server-side technologies like NodeJS, everything can be done with JavaScript if you so desire. By implementing and using modern mobile application development frameworks, JavaScript can even be used to create mobile apps and browser-based games.
JavaScript permeates every aspect of the web. It's sometimes said that "Software ate the world, the web ate software, and JavaScript ate the web" and this really is true in many ways. According to Google, there are nearly 2 billion web pages in existence, and a 2020 survey by W3Techs found that 96.5% of them use JavaScript.
Because of this deep integration with everything web-based, JavaScript is sometimes called the "standard programming language of the web". It's not just restricted to the web these days either. JavaScript and its related technologies are used in tablets, smartphones, and even smart TVs. Some of the biggest companies in the world whose websites you use everyday use JavaScript, including Facebook, Google, Amazon and YouTube. Chances are, if you've ever seen any sort of interactive content on a website - automatically validating forms, photo slideshows, comments that post without reloading the page - it was built at least in part with JavaScript.
One of the main reasons you should learn JavaScript is that without understanding JavaScript on its own (sometimes called vanilla JavaScript) none of the frameworks or libraries that are based on JavaScript will make sense. Before going further though, let's define what frameworks and libraries are.
Both frameworks and libraries are collections of functions and blocks of reusable code that make software development faster and easier. They are developed in response to many developers who need to accomplish the same tasks becoming tired of rewriting the same code over and over. To solve the problem, eventually a developer decides to wrap all their repetitive code up in a collection of functions and makes it available for use to everyone who wants to accomplish the same thing...thus a framework or a library is born. There are frameworks and libraries for many languages too, not just JavaScript. While they have similar purposes though, frameworks and libraries are different:
A library is simply a collection of functions and utilities grouped together which you can use at will, without much regard to adapting your project to fit the library. With a library you are in control of when to use each component, and you can use as many of those components as you wish or none at all. Two of the most popular JavaScript libraries include jQuery and ReactJS.
A framework, on the other hand is like a blueprint or a set of rules used to develop an application. When using a framework, you are filling in the blanks in order to complete the story, but the framework dictates the things that must be filled in, and how. The framework gives you the structure and handles all the interaction, and requires you to tell it the details. Popular JavaScript frameworks include Vue.js, Ember, and AngularJS.
There's a good analogy for understanding the difference between frameworks and libraries: A library is like Ikea: you've already got a house, but you need some furniture. You don't want to make all your furniture from scratch, so you go to the store to buy some pre-made or easy-to-assemble furniture. In this way you are treating the furniture store as a library of furniture. On the other hand, a framework is like someone handing you a few blueprints for a home and asking you to choose between different floorplans, layouts, and sizes. You have a few options to choose from, but the framework decides how the home will be constructed and will allow you to input specific properties such as what color the walls should be, what type of counter tops you'd like, the layout of the cabinetry and so on. The main reason you need to know JavaScript is that without understanding vanilla JavaScript, any frameworks or libraries you want to use in the future which depend on it will be filled with code you don't understand. Once you're comfortable with vanilla JavaScript, learning to use advanced libraries and frameworks like jQuery, React and Angular becomes much easier.
console.log()
¶Every modern web browser includes its own set of developer tools.
Firefox Developer Tools allow you to examine, edit, and debug HTML, CSS, and JavaScript on the desktop and on mobile. Also, you can download a version of of Firefox called Firefox Developer Edition that is tailored for developers, featuring the latest Firefox features and experimental developer tools. Learn more about Mozilla Firefox DevTools here.
To open Firefox Developer Tools, either right-click on any page element and select Inspect Element or open the Firefox settings menu in the top-right corner of your browser window and select Developer. Alternatively, you can use the shortcuts:
Ctrl + Shift + i (Windows/Linux).
The Chrome DevTools are a set of web authoring and debugging tools built into Google Chrome. Use the DevTools to iterate, debug and profile your site. Learn more about Chrome DevTools here.
We'll begin with explaining dev tools, because that's where the output of console.log()
will end up anyway. To access Chrome's developer tools, on any webpage simply right click and select "Inspect", or just press F12 on your keyboard, or open the Chrome settings menu in the top-right corner of your browser window and select More Tools > Developer Tools. Alternatively, you can use the shortcuts:
Ctrl + Shift + i (Windows/Linux).
This will open a new panel in your browser with several tabs in it. Depending on your configuration it may also open in a new window. Once it's open you can choose to dock it on the top, bottom, left, right or open it in a new window. If this is your first time using it, be sure to spend a bit of time exploring and experimenting to become familiar with all the menus and basic navigation around the window. Nothing you change in dev tools will have any permanent effect so it's a great way to learn and explore.
Dev tools is divided into several tabs and has a lot of functionality, but don't be intimidated; you won't need it all and anything you will need frequently will be explained right here. The main tabs you'll find useful as you learn JavaScript and a brief description of their purpose follows:
console.log()
function a place to go. This means that any time you need to see what's going on in your JavaScript code you can console.log()
it and it will show up in the console tab when you load the page with dev tools open. Because JavaScript runs directly in the browser, you can also use the console to test JavaScript code right in the browser!console.log
is used to display content to the JavaScript console. The message you log is "hiya friend!". hiya friend!
is a string (a sequence of characters).
console.log("hiya friend!");
hiya friend!
2+2
4
Let’s use console.log to do something a little more interesting. Here’s a block of JavaScript code that loops through the numbers 0 through 9 and prints them out to the console. This is called a loop.
for (var i = 0; i < 5; i++) {
console.log(i);
}
0 1 2 3 4
So you saw how to use console.log
to print a message to the JavaScript console. Now, let’s see how you can use the console as a sandbox to test a new line of JavaScript in the browser.
Open the following site in a new tab and in that tab also open up developer tools. Then paste the following code:
document.getElementsByTagName("h1")[0].style.color = "#ff0000";
This line of code changes the text in the first <h1>
tag to red!
Styling elements on the page is great, but you could also do that by just modifying the CSS. What makes JavaScript so special in this case? Refresh the page, then paste this line of code in the JavaScript console.
document.body.addEventListener('click', function () {
var myImage = document.createElement("img");
myImage.src = 'https://thecatapi.com/api/images/get?format=src&type=gif';
myImage.style.marginLeft = "160px";
var myParent = document.getElementsByTagName("h1")[0];
myParent.appendChild(myImage);
});
If you’re confused because nothing happened. Don’t worry. Click somewhere on the page to see the effect. You can refresh the page to return the page its original state.
What happened? A cat image was added to the page! But this only happened after the page was clicked.
The alert()
method in JavaScript is usually one of the first methods new JavaScript developers learn, because it immediately demonstrates something interactive that can be done with JavaScript. To illustrate, right click this page, click inspect, and select the console tab of the developer tools. Once in the console tab, type alert("hello!");
at the bottom of the console at the prompt, and press enter. Your alert should be displayed as a pop up message in the browser with an OK button.
The alert()
method is a built in part of JavaScript and is attached to the global window
object. You might remember that in JavaScript and other object oriented programming languages, everything is an object, even the browser window itself! Window (MDN Link) represents the currently selected browser tab and it makes a plethora of attributes, methods and other objects available to the user via JavaScript. A few things that will probably already be familiar to you which are part of the global Window object are:
window.innerHeight
and window.innerWidth
- The height and width of the browser not including any menus/scrollbars.window.location.href
- The current URL in the address bar.
*window.history
- An object representing the history of this tab, providing methods like history.back()
, the equivalent of pressing the back button.window.alert()
- The alert method you just used.window.prompt()
and window.confirm()
- Similar popups as window.alert()
, but for prompting for user input and getting user confirmation, respectively.Also included in the global window
object is a representation of the entire HTML document loaded in the current tab, accessible with window.document
. The point of understanding all this is that in JavaScript, everything you'll need for manipulating the DOM (Document Object Model) and making your webpages interactive stems from this window
object. It provides a huge portion of what you'll need as you learn JavaScript, including this alert()
method, so take some time and peruse the MDN link above to see what else you've got available to work with. The alert()
utilized here can be handy for logging or alerting items as well, similar to using console.log
, if you'd like to display some data in an alert box either for testing purposes or to provide some sort of feedback to the user in an obvious way.
debugger
Keyword¶The debugger
keyword allows you to stop execution of your code and show its current state in the Sources tab in Chrome's dev tools. There is also a Debugger tab in Firefox which has a similar function.
Sometimes you need more granular feedback when debugging JavaScript code than what console.log()
can provide you. When you need to step through your code line by line, you can use the debugger
keyword. With dev tools open, whenever the browser encounters the debugger
keyword, it will stop execution of the code and jump to the sources tab, providing you with a number of functions that can help you debug your code.
In the image below there are a few significant things:
debugger.html
file containing a <p id="body-paragraph">
paragraph.<scrip>
tags that will log the numbers 0 through 9 to the console when the body-paragraph
on line 9 is clickeddebugger
keyworddebugger
keyword is encountered. The blue "play" button will resume the code and run the rest of it normally unless it encounters the debugger
keyword again, and the other one with a curved arrow over a small dot will move forward to the next line of codeAlong with using the debugger
keyword to stop the execution of your JavaScript code, you can set specific breakpoints in the debugger window to stop the execution at any point you wish even if the debugger keyword doesn't show up in your code at that point. This allows you to add even more granularity to your debugging process by stopping the code on any individual line to check variable values, scope, the status of various requests and responses, and many other things.
To set a breakpoint in the debugger window, once you've stopped the execution with the debugger keyword just click on any line number to set a breakpoint there. When you resume the code or begin stepping through it again, the debugger will stop at the breakpoint. You can also right click to edit the breakpoint and it will allow you to execute a JavaScript expression before it gets there, so you can test in real time whether a fix you're thinking about might work! Just set the breakpoint, add a little JavaScript to execute before it, and then step through the code. Of course, since this is all in dev tools, as soon as you refresh the page everything will be back to normal so you don't need to worry about breaking anything.
In the image below, the red dots on the line numbers indicate breakpoints and they will show up as soon as you click on the number to indicate there's a breakpoint at that spot in the code. As you use dev tools more, remember that if you ever need to stop your code at a specific point to see what's going on, you can do it with a combination of console.log()
, the debugger
keyword and setting breakpoints.
You can use comments to help explain your code and make things clearer. In JavaScript, comments are marked with a double forward-slash //
called single-line comment. Anything written on the same line after the //
will not be executed or displayed. To have the comment span multiple lines, mark the start of your comment with a forward-slash and star, and then enclose your comment inside a star and forward-slash /* … */
called multi-line comment.
// this is a single-line comment
let f = 0; // also single-line comment
/*
this is
a multi-line
comment
*/
Docstrings are similar to multi-line comments except that they are not ignored by the intrepreter. In fact, docstrings are used in some languages to provide help to users of the application, not just developers. In general, docstrings should be used to document major parts of your application, such as functions, classes, methods and modules (all of which you'll learn a lot more about later on). You'll see these show up more later, but for now just make a mental note of the syntax for declaring a docstring, and remember that it's a good idea to include them for all functions, modules, classes, methods and so on.
/** Docstring syntax
* begins with a forward slash
* and two asterisks
* and every subsequent line
* begins with a space and an
* asterisk.
*/
The key takeaway should be when to use them. The more documentation your code has, the easier it will be for both you and other developers to understand in the future, so comment often and concisely.
Some programming languages indentation is critical to the functionalilty of the code. JavaScript is not one of those languages, but there are still some common indentation and minification conventions you should follow when writing JavaScript code. Every developer has their own coding style so you will see some slight variations when looking at publicly available code, and this is not an exhaustive list, but below are several conventions you should try to follow as much as possible:
myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ...] // if this gets too long, do this:
myArray = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
...
]
if (condition) {
// if statement code is indented
} else {
// as is the else block
}
while (condition) {
// loop blocks are always indented
if (condition) {
// nested blocks get another indentation level
if (otherCondition) {
// And so on...each nested block
// is indented another level
}
}
}
function myFunction(params) {
// function blocks are always indented
}
// Object properties and values should be indented
let myObject = {
key1: 'val1',
key2: 'val2',
key3: 'val3',
...
}
As you code more and more the indentation rules will become more obvious to you. When you write more advanced code, you will also begin to use minification software to minify your code. The only thing you need to know about minification for now is that production code should always be minified in order to reduce file size and decrease loading times. You'll learn how to do this in the future.
In JavaScript, like all programming languages, there are certain words that are used in the syntax of the language itself. Examples of these words include if, break, Date, this and many others. These are known as reserved words because they are reserved for use within the syntax of the language itself and should never be used to name variables, functions, classes or any other type of object. The reason you should never use these words outside their intended context is that JavaScript won't know how to tell the difference between your intended use and the normal intended use.
Along with these reserved words, there are reserved object names such as Array, Object, Math and so on. You should never use these or any other built in objects or function names to name your variables or your own objects. Below is a list of common reserved words in JavaScript (note that this list is extensive but not exhaustive):
. | . | . | . | . | . | . | . |
---|---|---|---|---|---|---|---|
abstract | boolean | break | byte | case | catch | char | class |
const | continue | debugger | default | delete | do | double | else |
enum | export | extends | final | finally | float | for | function |
goto | if | implements | import | in | instanceof | int | interface |
let | long | native | new | package | private | protected | public |
return | short | static | super | switch | synchronized | this | throw |
throws | transient | try | typeof | var | void | volatile | while |
with | yield |
The Number is one of the primitive data types in JavaScript. It is a wrapper that represents any kind of number, or when used as a function, can convert another value into a number. While other languages have different types of numbers such as integers (whole numbers), floating point (decimal) numbers and so on, in JavaScript every number is just a number.
Defining a number in JavaScript is actually pretty simple. The number
data type includes any positive or negative integer, as well as decimals. Entering a number into the console will return it right back to you.
3
3
The typeof
operator can be used either as a function or as an operator. To determine the type of data you're working with, all you need to do is provide typeof
with the data, and it will return its type.
typeof 3;
'number'
In JavaScript, every number is a floating point number.
typeof 3.1;
'number'
When used as a function, Number()
will convert another value such as a string, boolean, or other data type into a number. Falsy values will be converted to 0
, truthy values will be converted to 1
, values that cannot be converted to a number will be converted to NaN
, and numbers greater than or equal to 1.8 x 10^308
will be converted to the constant Infinity
(or -Infinity
if the number is negative):
console.log(Number("123")); // 123
console.log(Number(null)); // 0
console.log(Number(true)); // 1
console.log(Number("Hello!")); // NaN (Not a Number)
console.log(Number(1.8e308)); // Infinity
console.log(Number(-1.8e308)); // -Infinity
123 0 1 NaN Infinity -Infinity
Numbers also have a number of methods and properties available for manipulating them and retrieving information about them. A table of the methods you'll most likely use in your everyday JavaScript development is below:
Method/Property | Purpose | Usage | Result |
---|---|---|---|
Number.isNaN() | Returns whether the passed value is Not a Number | isNaN("Hello!"); | true |
Number.isFinite() | Returns whether the passed value is finite | isFinite(Infinity); | false |
Number.isInteger() | Returns whether the passed value is an integer | isInteger(123); | true |
Number.parseFloat() | Attempts to convert the passed value to a float | parseFloat("123.45"); | 123.45 |
Number.parseInt() | Attempts to convert the passed value to an integer | parseInt("123.45"); | 123 |
The following are instance methods which operate on a Number instance | |||
toFixed() | Returns a string representing the number with the passed number of decimal places | 123.45.toFixed(4); | "123.4500" |
toPrecision() | Returns a string representing the number with the passed precision | 123.45.toPrecision(4); | "123.5" |
toString() | Returns a string representing the number in the specified base (10 by default) | 123.45.toString(); | "123.45" |
23.45.toFixed(4); // Number to String, fixed number of decimal digits
'23.4500'
123.4567890.toPrecision(); // Number to String, strip 0 from the end
'123.456789'
123.4567890.toPrecision(2); // Number to String, 2 significant digit precision
'1.2e+2'
123.4567890.toPrecision(4); // Number to String, 4 significant digit precision
'123.5'
See list od locales here.
123456.789.toLocaleString('en-GB'); // Number to String, thousands delimiter ',' decimal point is '.'
'123,456.789'
123456.789.toLocaleString('ru-RU'); // Number to String, thousands delimiter ' ' decimal point is ','
'123,456.789'
As every number is a floating point number, this means that JavaScript math (or any floating-point math, for that matter), isn't 100% accurate. This is because there are some decimal numbers that cannot be represented perfectly in binary (the language of computers). You cannot represent the fraction 1/3 perfectly in the base 10 (decimal) system: 0.333333... is always an aproximation no matter how many decimal places you add, because 1 is not evenly divisible by 3. This same problem exists in the base 2 system (binary), so in programming languages that use floating point arithmetic, values that cannot be represented perfectly are rounded:
console.log(0.1 + 0.2);
0.30000000000000004
You don't need to know the exact intracacies of why it happens, but you can read more about this well-known problem here. Most importantly, a common way to solve the problem and get an accurate result is to scale the numbers up to have the last significant digit above the decimal point and after the operation scale the result down with the same number:
console.log((0.1 * 10 + 0.2 *10 )/10);
0.3
undefined
, null
and NaN
¶null
refers to the "value of nothing", while undefined
refers to the "absence of value".
The JavaScript undefined
property is used to represent a variable or other object that has either not been assigned a value or has not been declared at all. It is a property of the global object and is one of JavaScript's primitive data types. undefined
is always available as a variable in the global scope, but is not configurable, manipulatable or enumerable.
undefined
means that an object does exist but it doesn't have any value associated with it. When you define a variable without giving it a value (e.g. let item;
), JavaScript will give it a value of undefined
by default:
var signedIn;
console.log(signedIn);
undefined
JavaScript methods, statements and functions also return undefined
if either a) the object being returned has not been assigned a value or b) nothing is returned at all.
The most common usage of undefined
is checking whether something is undefined
. There are two ways to check:
if (signedIn === undefined) {
// this will execute
}
if (typeof signedIn === "undefined") {
// this will execute
}
You'll learn later why we're using ===
as opposed to ==
above, but for now, just understand that as a best practice you should always check equality using ===
because it verifies that the type of the objects is equal also. undefined is falsy in a boolean context, so like null, you can use it to determine truth and make decisions.
null
is another of JavaScript's primitive data types and is a representation of the intentional absense of any object value. This means that, effectively, null
is used when you want to explicitly represent "nothing". It is often used as the indication that an object could exist, but currently explicitly does not.
When you explicitly set a variable to null
, though, that variable then explicitly had a value of null
rather than undefined
:
var signedIn = null;
console.log(signedIn + '; Type: '+typeof(signedIn));
null; Type: object
Above, the declaration of null
means that the variable signedIn
points to no object. null is falsy in a boolean context and can be used to determine truth:
if (null) {
// will not run; null is falsy
}
In a practical sense, you will almost always use null
as the condition in a conditional statement, using it to make decisions based on whether a variable is null
or not. It can, however, be used to "wipe out" a variable's value. If you want to eliminate a variable's value, just assign null
to it. In the future, you may also use it as a substitute for zero-values when writing APIs, because in many cases (for example in a financial context), if a value doesn't exist, representing it as zero is not accurate and may create inaccuracies in financial calculations. Instead, API developers often use null
where an object could be expected, but none currently exists.
The difference between null
and undefined
is that null
represents an object which has explicitly been assigned a value of nothing, while undefined
is a representation that something has either not been declared or has not been assigned a value. It is "unclear" what it is, so it is considered to be undefined
.
Infinity
, -Infinity
and NaN
¶Infinity
, -Infinity
and NaN
are also properties of the global object, like undefined
and null
.
Infinity
: a special constant representing any number larger than about 1.8x10^308
-Infinity
: a special constant representing any number smaller than about -1.8x10^308
These values are often used in flow control and decision making like null
and undefined
, by checking another value to see if it is/is not finite or is/is not a number. They can also be useful to know when debugging, since you'll sometimes see a value you expected to be a number turn out to be NaN
or one of the other values. This can help to identify exactly what's going wrong in your code. In JavaScript, since you can convert various data types between one another, it's good to know that there will be a consistent value returned NaN
if you try to convert something into a number which cannot be converted.
isFinite()
: returns true if the number is not Infinity
or -Infinity
isFinite(12345);
true
isFinite(1.9e308);
false
NaN
: any value which is Not a Number, it's often returned indicating an error with number operations. For instance, if you wrote some code that performed a math calculation, and the calculation failed to produce a valid number, NaN
might be returned.
// calculating the square root of a negative number will return NaN
Math.sqrt(-10)
NaN
// trying to divide a string by 5 will return NaN
"hello" / 5
NaN
isNaN()
: returns true
if the passed value is not a number (Note: if you pass a number as a string (e.g. '12345'
) to isNaN()
, it will return false
because the string will be coerced, or converted, to a number before it is evaluated.
isNaN("hello!");
true
isNaN(12345);
false
isNaN('12345');
false
Just like in mathematics, you can compare two numbers to see if one’s greater than, less than, or equal to the other. Comparisons between numbers will either evaluate to true or false. The values true
and false
have significant importance in JavaScript, these values are called Booleans
Operator | Meaning |
---|---|
< |Less than| |> |Greater than| |<= |Less than or Equal to| |>= |Greater than or Equal to| |== |Equal to| |!= |Not Equal to| |===|Strict Equal to (with comparison of data type)| |!==|Strict Not Equal (with comparison of data type)|
5 > 10
false
5 < 10
true
5 == 10
false
Type coercion is the implicit/automatic conversion of one data type to another. Since JavaScript is a weakly typed language, JavaScript will automatically convert data types to different data types as needed. For example, in JavaScript when you add a string to a number, the number is implicitly converted to a string, and the result is actually the concatenation of the two strings:
1 + "1"
'11'
Obviously in the above case, the result is not correct. This also happens in other situations even if two of the same data type are involved in an operation that would normally return a different type. Consider the addition of a series of booleans:
true + false
1
true + true + true
3
Because the addition operator normally returns a number, and the sum of some number of booleans can't be represented any other way, JavaScript converts the booleans to their binary values (1
for true
and 0
for false
) and then adds them together. Thus, the first line is converted to 1 + 0
, which equals 1
, and the second is converted to 1 + 1 + 1
which equals 3
. This even happens when adding two numbers together, in some situations.
To resolve the above issues, sometimes you'll need to explicitly convert the values you're working with into the proper data type before manipulating them. To resolve the issue with adding a number to a string, either remove the quotation marks from around the string or use the built-in Number() method to convert it to a number:
1 + Number("1")
2
One extremely important consideration with respect to JavaScript's type coercion is the issue of equality. You might recall that there are two ways to check if two things are equal in JavaScript: ==
and ===
. The important thing to realize is that using ==
implicitly coerces data types! This means that 1 == "1"
will return true
:
1 == "1"
true
In reality, these two things are not equal: one is a string and the other is a number. To get the proper result, you must use ===
. This is why it's considered a best practice to always use ===
when checking equality unless you have a specific reason not to. Doing so will ensure that you will never end up with two things that are not really equal being treated as equal because JavaScript coerced them to the same data type.
Type coercion is a double edged sword. It is helpful because it allows us to assume things and write less verbose code, allowing JavaScript to do some of the work for us, but it also opens the door to difficult to detect bugs since unlike its counterparts that do not implicitly coerce data types, JavaScript will not throw an error when you try to perform operations on data types that are really not compatible.
x + y // 3+5 Addition Operator
x + y // '10'+8 (='108') Concatenation Operator
x - y // 5-3 (=2) Subtraction Operator
x * y // 2*3 (=6) Multiplication Operator
x / y // 5/3 (=1.66666) Division Operator
x % y // 5%3 (=2) Modulo Operator (returns the remainder)
Math.floor(x / y) // Floor Division
x ** y // 2**3 (=8) Exponentiation
x++ or ++x // same as x = x + 1 Increment Operator
x-- or --x // same as x = x - 1 Decrement Operator
3 + 2.1
5.1
5%3
2
The increment ++
and decrement --
operators offer a shortcut for adding/subtracting 1
to/from a variable. The following two pieces of code are equivalent:
Without Increment Operator:
let x = 1;
x = x + 1;
With Increment Operator:
let x = 1;
x++;
In both samples above, x is incremented by 1 and becomes 2.
If used postfix, with operator after operand (for example, x++
), the increment operator increments and returns the value before incrementing.
let v = 3;
w = v++;
console.log('v='+v, 'w='+w)
v=4 w=3
If used prefix, with operator before operand (for example, ++x
), the increment operator increments and returns the value after incrementing:
v = 3;
w = ++v;
console.log('v=' + v, 'w='+w)
v=4 w=4
To a computer, any piece of data can ultimately be broken down into its individual binary bits - a series of ones and zeros. For example, the decimal number 1
in binary notation can be represented as 1
, decimal 2
can be represented as binary 10
and decimal 3
can be represented as binary 11
.
In computing the basic storage block is a byte which consists of 8 bits e.g. 00000000
, where the decimal values of the bits from left-to-right are: 128
64
32
16
8
4
2
1
.
To convert from binary to decimal, you simply add the decimal values of each 1
bit together, hence 101
becomes 4+0+1 = 5. You can think of the binary bits like light switches: they are either on
(=1
) or off
(=0
). If they're on
their value is counted.
Decimal value of bits | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|---|---|---|---|
decimal 35 in binary | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 |
35 = 0*128 + 0*64 + 1*32 + 0*16 + 0*8 + 0*4 + 1*2 + 1*1
Why is this important? Sometimes you might want to manipulate data on a binary level, such as shifting bits that are on
to the left or right, swapping bits with one another, or comparing bits together using logical operators like AND, OR or NOT. For these purposes, in most programming languages there exist the bitwise operators below:
Operator | Name | Purpose | |
---|---|---|---|
& |
AND | Sets each bit to 1 if both bits are 1 | |
` | ` | OR | Sets each bit to 1 if one of two bits is 1 |
^ |
XOR | Exclusive OR: Sets each bit to 1 if only one of two bits is 1 | |
~ |
NOT | Inverts all the bits | |
<< |
Left shift | Shifts left by pushing zeros in from the right, and lets the leftmost bits fall off | |
>> |
Right shift | Shifts right by pushing copies of the leftmost bit in from the left, and lets the rightmost bits fall off | |
>>> |
Zero-fill (unsigned) right shift | Shifts right by pushing zeros in from the left, and lets the rightmost bits fall off |
You can also perform calculations with numbers pretty easily. Basically type out an expression the way you would type it in a calculator.
Math
object¶The Math
object in JavaScript is a built-in utility for working with the Number
data type. It contains several useful predefined functions, constants and properties which can be used for all sorts of mathematical operations. Some examples of things included in the Math
object are:
method/property | description |
---|---|
Math.PI: | The constant pi |
Math.random() | returns a random number between 0 and 1 |
Math.abs() | determines the absolute value of a number |
Math.min() | returns the minimum of a series of numbers |
Math.max() | returns the maximum of a series of numbers |
Math.floor() | returns the largest integer which is less than or equal to the passed argument |
Math.ceil() | returns the smallest integer which is greater than or equal to the passed argument |
Math.round() | returns the nearest integer to the passed argument |
Math.pow() | takes two arguments and returns the first argument raised to the power of the second |
Math.sqrt() | returns the square root of the passed argument |
there are many useful functions and properties of the Math
object. If you need a reference of all the available methods and their functionality, you can use the MDN Website.
Math.random(); // random number between 0 and 1
0.64326241617333
Math.floor(Math.random()*6+1); //roll the dice! between 1 and 6
4
[Math.floor(12.56), Math.floor(-12.56)];
[ 12, -13 ]
[Math.ceil(12.56), Math.ceil(-12.56)];
[ 13, -12 ]
[Math.round(12.56), Math.round(-12.56)];
[ 13, -13 ]
[Math.round(12.46), Math.round(-12.46)];
[ 12, -12 ]
Math.pow(2,3);
8
Math.sqrt(9);
3
Date
object¶The built in global object Date
represents a single moment in time. Specifically, a JavaScript date represents the number of milliseconds that have elapsed since the UNIX Epoch, or midnight on January 1, 1970, UTC. This date is the universally accepted standard base value for computer-based date and time values. Dates and times are an integral part of any computer application or system and can be used for a multitude of things ranging from tracking user login and logout timestamps to triggering system backups, gaming applications to automated payment systems and beyond.
The Date()
object is a contructor and can be used to create a JavaScript date:
var now = new Date();
now;
2021-02-18T18:16:00.523Z
now.toString();
'Thu Feb 18 2021 18:16:00 GMT+0000 (Greenwich Mean Time)'
now.toUTCString();
'Thu, 18 Feb 2021 18:16:00 GMT'
now.toLocaleString('de-DE');
'2/18/2021, 6:16:00 PM'
The Date.now()
static method returns the integer representation of the current date relative to the UNIX Epoch. Because it's an integer representation, it's possible to add a number to it. To get the exact same time tomorrow we could add the number of milliseconds in a day (24 60 60 * 1000):
var today = Date.now();
var tomorrow = today + 86400000; // 86400000 milliseconds in 1 day
[today, tomorrow]
[ 1613672160597, 1613758560597 ]
You can also create a Date
object from a timestamp like those above by passing the timestamp to the Date()
contructor, and then use the toDateString()
method to get a human readable date:
var todayDate = new Date(today);
var tomorrowDate = new Date(tomorrow);
[todayDate.toDateString(), tomorrowDate.toDateString()];
[ 'Thu Feb 18 2021', 'Fri Feb 19 2021' ]
console.log(now.getFullYear()); // The 4 digit year
console.log(now.getMonth()); // The month index (0-11)
console.log(now.getDate()); // The day of the month (1-31)
console.log(now.getDay()); // The day of the week index (0-6)
console.log(now.getHours()); // The hour (0-23)
console.log(now.getMinutes()); // The minute (0-59)
console.log(now.getSeconds()); // The seconds (0-59)
console.log(now.getMilliseconds()); // The milliseconds (0-999)
2021 1 18 4 18 16 0 523
All of the above methods return the date/time part in your local time. If you need UTC time, there is a UTC counterpart for each one:
console.log(now.getUTCFullYear()); // The 4 digit year
console.log(now.getUTCMonth()); // The month (0-11)
console.log(now.getUTCDate()); // The day of the month (1-31)
console.log(now.getUTCDay()); // The day of the week (0-6)
console.log(now.getUTCHours()); // The hour (0-23)
console.log(now.getUTCMinutes()); // The minute (0-59)
console.log(now.getUTCSeconds()); // The seconds (0-59)
console.log(now.getUTCMilliseconds()); // The milliseconds (0-999)
2021 1 18 4 18 16 0 523
var birthday = new Date(2000,0,31); //31st January, 2000
birthday.toLocaleString('en-GB');
'1/31/2000, 12:00:00 AM'
birthday.setYear(1975);
birthday.toLocaleString('de-DE');
'1/31/1975, 12:00:00 AM'
You can also format the date according to your local format, and there are several methods for producing human readable dates. For a full listing of the Date object's methods and functionality, see the MDN Reference.
Strings are a collection of characters enclosed inside double or single quotes. You can use strings to represent data like sentences, names, addresses, and more. It is correct to either use double " or single ' quotes with strings, as long as you're consistent. The JavaScript Udacity style guide for labs and projects suggests using single quotes to define string literals.
Strings can be created by surrounding a string of text in either single quotes or double quotes, like this:
'This is a string!'
"This is a string, too!"
The type of quotation marks you use doesn't matter, though most developers these days prefer single quotes since it's one less keystroke (you don't have to hold down shift).
If you want to include a literal quotation mark in your string of text, you must enclose the string in the opposite kind:
"This string's got a single quote in it."
'The developer said, "Strings are awesome!"'
The reason for this is obvious if we look at a couple strings:
'This one doesn't work'
JavaScript doesn't know whether the single quote in doesn't
is the closing quote from the beginning of the string, or the apostrophe in the word itself.
"He said "My name is Bob""
JavaScript will interpret the double quote just before "My name is
as the closing quote matching the one at the beginning of the string.
Both of the above situations will generate syntax errors, so you do need to be strategic in which quotes you choose, depending on the text of your string.
There is an other way to use quotes inside a string, and have JavaScript not misunderstand your intentions, you’ll need a different way to write quotes using the backslash character \
.
In JavaScript, you use the backslash to escape other characters. Escaping a character tells JavaScript to ignore the character's special meaning and just use the literal value of the character. This is helpful for characters that have special meanings like in our previous example with quotes "…"
.
Because quotes are used to signify the beginning and end of a string, you can use the backslash character to escape the quotes in order to access the literal quote character. This guarantees that the JavaScript engine doesn’t misinterpret the string and result in an error.
console.log("The man whispered, \"can you hear me?\"")
The man whispered, "can you hear me?"
Quotes aren’t the only special characters that need to be escaped, there’s actually quite a few. However, to keep it simple, here’s a list of some common special characters in JavaScript.
Code | Character |
---|---|
\|backslash | |
\" | double quote |
\' | single quote |
\r | carriage return |
\n | newline |
\t | horizontal tabulator |
The last two characters listed in the table, newline \n
and tabulator \t
are unique because they add additional whitespace to your Strings. A newline character will add a line break and a tab character will advance your line to the next tab stop.
Note: the \r
character is not commonly used. It is part of a special Windows sequence \r\n
, which signifies that the enter key was pressed, but modern Windows understands simply \n
to mean "new line", so you can use that universally to render new lines.
console.log('This string has\nmultiple lines.\nHooray!')
This string has multiple lines. Hooray!
Did you know you can even add strings together? In JavaScript, this is called concatenating. Concatenating two strings together is actually pretty simple!
You will see other ways to concatenate and do even more with strings later in this course.
"Hello," + " New York City"
'Hello, New York City'
typeof 'Hello'
'string'
As with most things in code when something becomes too complex or tedious developers will eventually find a way to simplify it. Luckily, that's the case with strings too. When you need to use a lot of special characters in your string, or maybe even include a variable, you can use a template literal instead.
Creating a template literal is easy. All you have to do is surround the string with backticks(``) instead of single or double quotes. Between the backticks you can use any characters you want including newlines, tabs, quotes and even variables! The interpreter will interpret the string as it's written in the code, which makes this type of string ideal for creating strings of HTML in your JavaScript, while keeping the HTML itself easy to read. A good example of this would be if you wanted to use JavaScript to render a list of something in an HTML list:
let fourthItem = 'Item 4';
let myHtml = `
<ol class="item-list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>${fourthItem}</li>
</ol>
`;
In the above, everything between the opening backtick and the closing backtick including the quotes, newlines and indentation will all be interpreted as part of the string. The variable, fourthItem
, will be evaluated and included as Item 4
. This is the preferred method of creating complex strings in modern JavaScript, but for simple strings you can stick with regular quotes and escape characters.
You can use template literals to inject variable content directly into the string without using the additional concatenation. The process of injecting variables into strings using template literals is called string interpolation.
The $(...)
template within a string enables you to include variables or expressions:
let bName = "Bilbo Baggins";
let bHome = "Bag End";
let bHouse = 2;
console.log(`${bName} lives at ${bHome} in the ${2*bHouse}th house`);
Bilbo Baggins lives at Bag End in the 4th house
A method is a function that is associated with a particular type of object (such as a String
, a Number
, or a Class
). For all intents and purposes, methods are the same as functions, and can be invoked, or called, in an identical way. Many of the objects you'll work with in JavaScript also have methods which can be called in order to operate on them in some way.
String objects have a number of methods and properties that can be used to manipulate them. These methods and properties are small functions or characteristics that allow you to easily do things like getting the length of the string, converting the whole string to upper/lowercase, searching the string to find a substring within it, duplicating or repeating it, replacing characters in it and so on.
To use the different string methods and properties all you need to do is call them on a string variable.
Method/Property | Description |
---|---|
length | Returns the length of a string |
charAt() | Returns the character at the specified index (position) |
charCodeAt() | Returns the Unicode of the character at the specified index |
concat() | Joins two or more strings, and returns a new joined strings |
endsWith() | Checks whether a string ends with specified string/characters |
fromCharCode() | Converts Unicode values to characters |
includes() | Checks whether a string contains the specified string/characters |
indexOf() | Returns the position of the first found occurrence of a specified value in a string |
lastIndexOf() | Returns the position of the last found occurrence of a specified value in a string |
localeCompare() | Compares two strings in the current locale |
match() | Searches a string for a match against a regular expression, and returns the matches |
repeat() | Returns a new string with a specified number of copies of an existing string |
replace() | Searches a string for a specified value, or a regular expression, and returns a new string where the specified values are replaced |
search() | Searches a string for a specified value, or regular expression, and returns the position of the match |
slice() | Extracts a part of a string and returns a new string |
split() | Splits a string into an array of substrings |
startsWith() | Checks whether a string begins with specified characters |
substr() | Extracts the characters from a string, beginning at a specified start position, and through the specified number of character |
substring() | Extracts the characters from a string, between two specified indices |
toLocaleLowerCase() | Converts a string to lowercase letters, according to the host's locale |
toLocaleUpperCase() | Converts a string to uppercase letters, according to the host's locale |
toLowerCase() | Converts a string to lowercase letters |
toString() | Returns the value of a String object |
toUpperCase() | Converts a string to uppercase letters |
trim() | Removes whitespace from both ends of a string |
valueOf() | Returns the primitive value of a String object |
toLowerCase()
and toUpperCase()
¶The toLowerCase()
method converts every letter in the string to a lowercase letter. As you might have guessed, there's also a toUpperCase()
method:
"This is the Title of a Book".toLowerCase()
'this is the title of a book'
"This is the Title of a Book".toUpperCase();
'THIS IS THE TITLE OF A BOOK'
Did you know that you can access individual characters in a string? To access an individual character, you can use the character's location in the string, called its index. Characters within a string are indexed starting from 0
, where the first character is at position 0
, to n-1
, where the last character is at position n-1
(n
represents the total number of characters within a string).
Just put the index of the character inside square brackets [...]
(starting with 0
as the first character) immediately after the string. For example:
"James"[0];
'J'
the length of a string is stored in the string's length
attribute:
"James".length;
5
The last character in a string has index length - 1
:
"James"["James".length-1];
's'
charAt()
¶Another useful method related to string indexing is the charAt()
method, which returns the character at a given index. If the index number is not found, this method will return an empty string:
'James'.charAt(0);
'J'
slice()
¶if you need to slice a string to get only certain characters you can use the slice()
method, which takes two parameters: a starting index and an ending index. If you pass only the starting index, slice()
will return that index plus rest of the string:
console.log('James'.slice(1)); // ames
console.log('James'.slice(0, 3)); // Jam (last index is not included)
console.log('James'.slice(1, 4)); // ame
ames Jam ame
indexOf()
and lastIndexOf()
¶Because strings can be indexed as demonstrated above, you can also search them to find where a particular string or character shows up in them using the indexOf()
and lastIndexOf()
methods. Both of these methods return the index where the specified string appears, and will return -1
if the string you're looking for doesn't appear in the string you're searching:
'abracadabra'.indexOf('ra');
2
second parameter oders the start of seacrh from a given index:
'abracadabra'.indexOf('ra',5);
9
Last occurence with lastIndexOf()
'abracadabra'.lastIndexOf('ra');
9
substr()
and substring()
¶'Hello World!'.substr(6,3); // start, length
'Wor'
'Hello World!'.substring(6,90); // start, end
'World!'
trim()
¶' Hello World! '.trim();
'Hello World!'
var str = "This is a sample string";
var re = /Sample/i; //regular expression between slashes, and a modifyier 'i': ignore case
//var re= new RegExp("Sample", "i")
re.exec(str);
[ 'sample', index: 10, input: 'This is a sample string', groups: undefined ]
re.test(str);
true
With variables, you no longer need to work with one-time-use data. At the beginning of this course, you declared the value of a string, but you didn't have a way to access or reuse the string later.
"Hello"; // Here's a String "Hello"
"Hello" + " World"; // Here's a new String (also with the value "Hello") concatenated with " World"
'Hello World'
Storing the value of a string in a variable is like packing it away for later use.
var greeting = "Hello";
Now, if you want to use "Hello"
in a variety of sentences, you don't need to duplicate "Hello"
strings. You can just reuse the greeting
variable.
greeting + " World!";
'Hello World!'
In order to make your JavaScript code easy to read by you as well as other developers, you should always make sure to follow the various naming conventions for variables and also the rules for casing and spacing. For JavaScript you should always remember the follwing rules:
+
-
/
*
and equals signs to make sure your code is evenly spacedNot using camelCase for your variables names is not going to necessarily break anything in JavaScript. But there are recommended style guides used in all programming languages that help keep code consistent, clean, and easy-to-read. This is especially important when working on larger projects that will be accessed by multiple developers.
You can read more about Google's JavaScript StyleGuide here.
var tip = 8; // uses lowercase if the variable name is one word
var totalAfterTax = 53.03; // uses camelCase if the variable name is multiple words
To understand the difference between types of variable declarations, first you need to understand the term scope. What is a "Scope"?
The scope is defined as a specific portion of the code. There are three types of scope in Javascript
{ ... }
(block of code) only. This is similar to local scope, but refers specifically to the code block the variable exists in. This is relevant when there is nesting in your code, where a variable inside a function might be local, but inside an if statement or loop within the function, anything declared is local only to the loop code blocks. Another way to think about it is that the furthest level out in your code is global scope, the next level in is local, but if within the local scope there is another level of code like a loop, if statement, etc, then the "local" scope in the function is now somewhat like the function's "global" scope, and anything further in is block scope.let globalVar = 'a global variable';
function myFunction() {
let localVar = 'a local variable';
if (localVar) {
let blockVar = 'a block-scoped variable';
} else {
let otherBlockVar = 'a different block-scoped variable';
}
}
Above, the two block-scoped variables are accessible only within their respective code blocks, bounded by the curly braces. The local variable is accessible anywhere in the function, and the global variable is accessible anywhere in the entire script. Scope is not just a construct of JavaScript either. As you learned in the comparative programming module, Python also uses code blocks, but instead of being bounded by curly braces they are bounded by the indentation level. That means that in Python, anything at the outermost indentation level is considered global and anything indented is local to that specific indentation level. As you work with these languages more and more, scope will become more clear. When you're writing code, remember to always keep in mind the scope in which you're writing, especially when defining variables. Your code's functionality is dependent on everything having the proper scope.
There are three ways to declare a variable:
let
- It is a new way to declare a variable in any scope - Global, Local, or Block. The value of this variable can be changed or reassigned anytime within its scope.const
- It is a way to declare constants in any scope - Global, Local, or Block. Once you are assigned a value to a const variable, the value of this variable CAN NOT be changed or reassigned throughout the code.var
- This is the old way of declaring variables in only two scope - Global, or Local. Variables declared with the var
keyword can not have Block scope. The value of this variable can be changed or reassigned anytime within its scope.
/*
* Global scope.
* This variable declared outside of any function is called Global variable.
* Hence, you can use this anywhere in the code
*/
var opinion = "This nanodegree is amazing";
// Function scope
function showMessage() {
// Local variable, visible within the function `showMessage`
var message = "I am an Udacian!";
// Block scope
{
let greet = "How are you doing?";
/*
We have used the keyword `let` to declare a variable `greet`
because variables declared with the `var` keyword can not have Block Scope
*/
} // Block scope ends
console.log( message ); // OK
console.log( greet ); // ERROR.
// Variable greet can NOT be used outside the block
console.log( opinion ); // OK to use the gobal variable anywhere in the code
} // function scope ends
The most common way you will declare variables is using the let
keyword. This keyword creates a variable which has block scope. This means that the variable will only be available for use within the code block in which it is declared. For example, if you declare a variable inside a conditional statement, such as an if
statement the variable will only be defined in the context of the opening and closing curly braces (which you may recall define the boundaries of the current code block):
if (true) {
let myVar = "hello!";
console.log(myVar);
} else {
console.log("Inside the else block: ", myVar);
}
hello!
The above will work just fine, and will log hello!
to the console. Because the condition is true
, the variable myVar
is declared in the if
block and then immediately logged to the console. What if we were to change it to if (false)
though?
if (false) {
let myVar = "hello!";
console.log(myVar);
} else {
console.log("Inside the else block:", myVar);
}
ReferenceError: myVar is not defined
Now, because the condition is false
the if
statement will attempt to execute the else
block and log "Inside the else block: hello!"
to the console. However, because myVar
is declared with let
and thus has block scope, it is restricted to the if
block, that is, it cannot exist outside the closing curly brace }
just before the else
. Running this code will produce a ReferenceError
, because in the context of the else
block, myVar
is not defined.
Variables declared with let
have three defining characteristics:
{
curly braces}
)let myVar = "a variable";
let myVar = "a different variable"; // this will throw a SyntaxError. myVar is already declared.
let myVar = "a variable";
myVar = "a different variable"; // We're not redeclaring it, rather giving the same variable a new value.
let
keyword was introduced in 2015 in order to combat issues with variables being overwritten accidentally when their values "leaked" into other scopes. The restrictions on let-declared variables with regards to scope, reassignment and redeclaration prevent these issues from happening. You'll learn about two other ways to declare variables in the upcoming units, but the one you'll use most commonly is this one.The second way to declare a variable in JavaScript is using the const
keyword. const
is an abbreviation for constant, an allusion to the idea that these variables should be constant, that is, they shouldn't change. In JavaScript, const variables are typically used to store data that won't be modified at any point in the code - things like settings, URLs which might be called, filenames, and the like.
You can define a variable with const
in the exact same way as you would using let
, and it will behave almost identically:
const google = "https://www.google.com";
Variables declared with const
are similar to those declared with let
in some ways. They have block scope like let
variables and will behave identically in that respect, but they do have a few key differences:
Unlike let
variables, constants cannot be reassigned or redeclared. Attempting to redeclare the variable or change its value in any way will fail:
const url = "www.google.com";
const url = "www.youtube.com"; // SyntaxError: Identifier 'url' has already been declared
url = "www.google.com"; // TypeError: Assignment to constant variable.
const number = 4;
number = number + 2; // TypeError: Assignment to constant variable.
number += 2; // TypeError: Assignment to constant variable.
Unlike let
variables, constants cannot be declared without being assigned a value:
let url; // Ok (url is undefined, but declared)
const url; // SyntaxError: Missing initializer in const declaration
Arrays and objects stored in constants can be modified, but you can't reassign a new object or array to the same constant:
// Define a constant object:
const john = {name: "John", age: 30, location: "US"};
john.age = 31; // Ok, we're changing the object property, not the constant itself
john['birthday'] = 'April 25'; // Ok, we're modifying the object, not the constant itself
john = {name: "John", age: 31, location: "Ireland"}; // NOT ok, we're changing the constant's value
// Define a constant array:
const cars = ["Saab", "Volvo", "BMW"];
cars[0] = "Toyota"; // Ok, we're updating the array element, not the constant itself
cars.push("Audi"); // Ok, we're updating the array, not the constant itself
cars = ["Toyota", "Volvo", "BMW", "Audi"]; // NOT ok, we're changing the constant's value
Constants should be used to store data that won't change. If the data might change, use let
instead. It's important to remember also that because both let
and const
are block scoped, and you cannot redeclare either type of variable, you cannot declare a constant with the same name as a variable declared with let
, or vice versa, within the same scope.
The final way to declare a variable in JavaScript is using the var
keyword. In modern JavaScript it's usually considered a bad practice to use this declaration, because it creates a variable which has global scope, which means it can be unintentionally changed outside of its own scope. Despite this, there are some situations where this type of variable is actually required, so it's good to know it exists and how to use it.
You can define a variable with var
in the exact same way as you would using let
or const
:
var x = 3;
Variables declared with var
are accessible outside the scope in which they are declared. This means that they can be inadvertently overwritten and can create bugs in your code that are difficult to detect.
Consider the following code:
var i = 0;
for(var i = 0; i <= 5; i++) {
console.log("Inside the loop:", i);
}
console.log("Outside the loop:", i);
Inside the loop: 0 Inside the loop: 1 Inside the loop: 2 Inside the loop: 3 Inside the loop: 4 Inside the loop: 5 Outside the loop: 6
Do you see the problem? If not, you're not alone, but look closely at what happened: On line 1 we declared a variable, i
, using var
, and gave it a value of 0
. Then on line 3 we initiated a for
loop, declaring a variable i
beginning with zero and iterating through 5
, incrementing i
after each iteration. When the i
in the loop is equal to 5
, iteration stops, and the final increment takes place i++
, incrementing it to 6
. However, when we logged i
to the console outside the loop, i
has a value of 6
! It has been overwritten by the loop's i
. This is because variables declared with var
are not restricted with regard to being reassigned, redeclared, or reused in another scope.
The first variable declared on line 1 should have remained 0
within the global scope, that is, the code outside of the loop, because the loop's code block is bounded by the curly braces. Nothing that happened inside that loop should have affected anything outside the loop, but it did, inadvertently overwriting the original i
variable and then incrementing it each time the loop executed. The original variable, which should have maintained a value of 0
was lost. If you were to declare the first variable with let
or const
, you would have received an error when you tried to redeclare it with the same name in the for loop, preventing this issue. Here are some more examples to drive the point home:
let t = 0;
for (let t = 0; t <= 5; t++) {
console.log("Inside the loop:", t);
}
console.log("Outside the loop:", t);
Inside the loop: 0 Inside the loop: 1 Inside the loop: 2 Inside the loop: 3 Inside the loop: 4 Inside the loop: 5 Outside the loop: 0
Allowed: This is ok because the variable defined for use in the for
loop is declared with let
, which means it won't affect the outer one due to its scope being restricted to the loop.
The following reassigns the let
variable declared outside of the loop
:
let s = 0;
for (s = 0; s <= 5; s++) {
console.log("Inside the loop:", s);
}
console.log("Outside the loop:", s);
Inside the loop: 0 Inside the loop: 1 Inside the loop: 2 Inside the loop: 3 Inside the loop: 4 Inside the loop: 5 Outside the loop: 6
Allowed in some cases we need the final value obtained in the loop.
The following is not allowed because the t
variable in the loop
could potentially overwrite the one outside it. Its scope is not restricted.
let t = 0;
for (var t = 0; t <= 5; t++) {
console.log("Inside the loop:", t);
}
console.log("Outside the loop:", t);
Rejected: SyntaxError: Identifier 't' has already been declared.
The following example uses var
global variable and let
local variable with the same name:
var q = 0;
for (let q = 0; q <= 5; q++) {
console.log("Inside the loop:", q);
}
console.log("Outside the loop:", q);
Inside the loop: 0 Inside the loop: 1 Inside the loop: 2 Inside the loop: 3 Inside the loop: 4 Inside the loop: 5 Outside the loop: 0
Allowed: The variable declared with var
can be redeclared with let
within the loop, because once again let restricts that variable's scope to the for
loop.
The bottom line is that var
creates a variable which can be easily overwritten anywhere in your code, so in general it is dangerous to use. If you explicitly need to be able to access a variable outside of the scope in which it is declared, however, var
is the only way.
There is almost always an alternative to using var
, so use it sparingly because it can create bugs in your code that are difficult to detect. In general, you should use let
to declare most variables. For those that won't change, you should use const
, and in rare cases where you need to access or modify a variable outside the scope in which it is defined, use var
.
Assignment operators in JavaScript are used to assign or reassign values to a variable. This can be done using any of the assignment operators, one of the variable assignment keywords (let
, const
or var
) and a value to assign:
let x = 1;
const x = 1;
var x = 1;
Which keyword you use depends on the scope you need the variable to have. If you don't remember the difference, review that unit as needed. As you can see the generic syntax for using an assignment operator is:
[keyword] [variableName] [operator] [value];
let x = 1 ;
If you are reassigning a new value to an already declared variable, you can simply leave off the assignment keyword. To reassign the value 2 to the variable x above, simply use x = 2;
There are several different assignment operators, but the one you'll use most commonly is the simple equals sign =
. When you need to modify a variable's value you can either reassign it as demonstrated above, or use one of the compound assignment operators such as +=
or -=
, which take in the original value, modify it accordingly, and then return the new value. Here are the assignment operators you'll use most often (note that the compound assignment operators require that you first use the regular assignment operator to declare the variable and assign its initial value):
x += 3 // same as x = x + 3 Addition Assignment
x -= 6 // same as x = x - 6 Subtraction Assignment
x *= 2 // same as x = x * 2 Multiplication Assignment
x /= 5 // same as x = x / 5 Division Assignment
x %= 7 // same as x = x % 7 Remainder Assignment
x **= 3 // same as x = x ** 3 Exponentiation Assignment
x &= 3 // same as x = x & 3 Bitwise AND Assignment
x |= 3 // same as x = x | 3 Bitwise OR Assignment
x ^= 3 // same as x = x ^ 3 Bitwise XOR Assignment
x >>= 3 // same as x = x >> 3 Bitwise Right Shift Assignment
x <<= 3 // same as x = x << 3 Bitwise Left Shift Assignment
x >>>= 3 // same as x = x >>> 3 Bitwise Unsigned Right Shift Assignment
Another way to work with strings is by comparing them. You've seen the comparison operators ==
and !=
when you compared numbers for equality. You can also use them with strings! For example, let’s compare the string "Yes"
to "yes"
. When you run this in the console, it returns false, because they are different!
"Yes" == "yes"
false
When you compare strings, case matters. While both string use the same letters (and those letters appear in the same order), the first letter in the first string is a capital Y
while the first letter in the second string is a lowercase y
.
'Y' != 'y'
true
'A' < 'a'
true
In Javascript, strings are compared character-by-character in alphabetical order. Each character has a specific numeric value, coming from ASCII value of Printable characters. For example, the character 'A'
has a value 65
, and 'a'
has a value 97
. You can notice that a lowercase letter has a higher ASCII value than the uppercase character. If you want to know the ASCII value of a particular character, you can try running the code below:
// get the ASCII value of the first character, i.e. the character at the position 0.
"A".charCodeAt(0);
65
// get the ASCII value of the first character, i.e. the character at the position 0.
"a".charCodeAt(0);
97
In the example above, if you wish to print ASCII values of all the characters in your string, you would have to use Loops that we will study in later part of this course. Just for reference, here is how you can use a loop to print the ASCII value of all characters in a string.
var my_string = "Udacity";
// Iterate using a Loop
for (var i = 0; i < my_string.length; i++) {
console.log(my_string.charCodeAt(i));
}
85 100 97 99 105 116 121
The ASCII values of [A-Z] fall in the range [65-90], whereas, the ASCII values of [a-z] fall in the range [97-122]. Therefore, when we compare strings, the comparison happens character-by-character for the ASCII values.
A boolean variable can take either of two values - true
or false
. For example,
var studentName = "John";
var haveEnrolledInCourse = true;
var haveCompletedTheCourse = false;
typeof haveEnrolledInCourse
'boolean'
A boolean variable is mainly essential in evaluating the outcome of conditionals (comparisons). The result of a comparison is always a boolean variable. We'll study conditionals in our upcoming lesson, but let's look at our previous example to understand the role of boolean in conditional:
if (haveEnrolledInCourse){
console.log("Welcome "+studentName+" to Udacity!"); // Will run only if haveEnrolledInCourse is true
}
Welcome John to Udacity!
var a = 10;
var b = 20;
// a comparison - we will study this in detail in upcoming lesson
if (a>b) // The outcome of a>b will be a boolean
console.log("Variable `a` has higher value"); // if a>b is true
else
console.log("Variable `b` has higher value"); // if a>b is false
Variable `b` has higher value
In general cases (regular equality check), a true
corresponds to number 1
, whereas false
represents a number 0
. For example:
if(1){
console.log("This statement will always execute because conditional is set to 1 i.e., true");
}
if(0){
console.log("This statement will NEVER execute because conditional is set to 0 i.e., false");
}
This statement will always execute because conditional is set to 1 i.e., true
Boolean(0);
false
Boolean(1);
true
Equal is not the same as identical, at least in terms of computing. When coding, it's important to remember that the computer will always treat things 100% literally unless you tell it not to. When checking equality, developers can check not only the equality of the values but also whether the data types are the same.
== // checks whether two objects are equal
=== // checks whether two objects are equal and have the same data type
So far, you’ve seen how you can use ==
and !=
to compare numbers and strings for equality. However, if you use ==
and !=
in situations where the values that you're comparing have different data-types, it can lead to some interesting results. For example,
"1" == 1
true
0 == false
true
' ' == false
true
Both the operands on either side of the ==
operator are first converted to zero
, before comparison.
All of the above three evaluate to true
. The reason for such interesting outcomes is Type Conversion. In the case of regular comparison, the operands on either side of the ==
operator are first converted to numbers, before comparison. Therefore, a ' '
, false
, and 0
are all considered equal. Similarly, a '1'
and 1
are also considered equal. If we don't want to convert the operands, before comparison, we have to use a strict comparison ===
, that is explained below.
JavaScript is known as a loosely typed language.
Basically, this means that when you’re writing JavaScript code, you do not need to specify data types. Instead, when your code is interpreted by the JavaScript engine it will automatically be converted into the "appropriate" data type. This is called implicit type coercion and you’ve already seen examples like this before when you tried to concatenate strings with numbers.
"julia" + 1
'julia1'
In this example, JavaScript takes the string "julia"
and adds the number 1
to it resulting in the string "julia1"
. In other programming languages, this code probably would have returned an error, but in JavaScript the number 1
is converted into the string "1"
and then is concatenated to the string "julia"
.
It’s behavior like this which makes JavaScript unique from other programming languages, but it can lead to some quirky behavior when doing operations and comparisons on mixed data types.
Number("Hello") % 10
NaN
DEFINITION: A strongly typed language is a programming language that is more likely to generate errors if data does not closely match an expected type. Because JavaScript is loosely typed, you don’t need to specify data types; however, this can lead to errors that are hard to diagnose due to implicit type coercion.
int count = 1;
string name = "Julia";
double num = 1.2932;
float price = 2.99;
var count = 1;
var name = "Julia";
var num = 1.2932;
var price = 2.99;
In the example below, JavaScript takes the string "1"
, converts it to true
, and compares it to the boolean true
.
"1" == true
true
When you use the ==
or !=
operators, JavaScript first converts each value to the same type (if they’re not already the same type); this is why it's called type coercion! This is often not the behavior you want, and it’s actually considered bad practice to use the ==
and !=
operators when comparing values for equality.
Instead, in JavaScript it’s better to use strict equality to see if numbers, strings, or booleans, etc. are identical in type and value without doing the type conversion first. To perform a strict comparison, simply add an additional equals sign =
to the end of the ==
and !=
operators.
"1" == 1
true
"1" === 1
false
When using ==
, the second number is coerced to a string before the comparison takes place, which causes JavaScript to determine that 1
(a number) and "1"
(a string) are equal. In reality, they are not equal. The two values were coerced to the same type. The strict equality operator does not do this, and thus returns false because a string and a number are different data types.
0 === false
false
0 !== true
true
If you need to determine whether two objects are exactly the same object, you can also use Object.is()
, which is similar to the Python is
operator. The Object.is()
operator takes two parameters which are the two objects to compare, and returns a boolean depending on whether they are the same object. While this method is not commonly used, you can read more about it here
let str1 = "Foo";
let str2 = "Foo";
Object.is(str1,str2);
true
if...else
statements¶if...else
statements allow you to execute certain pieces of code based on a condition, or set of conditions, being met.
if (/* this expression is true */) {
// run this code
} else {
// run this code
}
This is extremely helpful because it allows you to choose which piece of code you want to run based on the result of an expression. For example,
var a = 1;
var b = 2;
if (a > b) {
console.log("a is greater than b");
} else {
console.log("a is less than or equal to b");
}
a is less than or equal to b
The value inside the if
statement is always converted to true
or false
. Depending on the value, the code inside the if
statement is run or the code inside the else
statement is run, but not both. The code inside the if
and else
statements are surrounded by curly braces {...}
to separate the conditions and indicate which code should be run.
TIP: When coding, sometimes you may only want to use an if
statement. However, if you try to use only an else
statement, then you will receive the error SyntaxError: Unexpected token else. You’ll see this error because else
statements need an if
statement in order to work. You can’t have an else
statement without first having an if
statement.
else if
statements¶In JavaScript, you can represent this secondary check by using an extra if statement called an else if
statement.
var weather = "sunny";
if (weather === "snow") {
console.log("Bring a coat.");
} else if (weather === "rain") {
console.log("Bring a rain jacket.");
} else {
console.log("Wear what you have on.");
}
Wear what you have on.
Logical operators can be used in conjunction with boolean values (true and false) to create complex logical expressions.
By combining two boolean values together with a logical operator, you create a logical expression that returns another boolean value. Here’s a table describing the different logical operators:
Operator | Meaning | Example | How it works |
---|---|---|---|
&& |
Logical AND | value1 && value2 |
Returns true if both value1 and value2 evaluate to true . |
|| |
Logical OR | value1 || value2 |
Returns true if either value1 or value2 (or even both!) evaluates to true . |
! |
Logical NOT | !value |
Returns the opposite of value . If value is true , then !value is false . |
By using logical operators, you can create more complex conditionals
Truth tables are used to represent the result of all the possible combinations of inputs in a logical expression. a
represents the boolean value on the left-side of the expression and b
represents the boolean value on the right-side of the expression. Truth tables can be helpful for visualizing the different outcomes from a logical expression.
a | b | a && b |
---|---|---|
true | true | true |
true | false | false |
false | true | false |
false | false | false |
a | b | a \ | \ | b |
---|---|---|---|---|
true | true | true | ||
true | false | true | ||
false | true | true | ||
false | false | false |
a | !a |
---|---|
true | false |
false | true |
true && false
false
true || false
true
1. (..) - Parenthesis
2. ! - Logical NOT
3. && - Logical AND
4. || - Logical OR
false || !false && false
false
false || !(false && false)
true
In both truth tables there are specific scenarios where regardless of the value of B, the value of A is enough to satisfy the condition.
For example, if you look at A AND B, if A is false, then regardless of the value B, the total expression will always evaluate to false because both A and B must be true in order for the entire expression to be true.
This behavior is called short-circuiting because it describes the event when later arguments in a logical expression are not considered because the first argument already satisfies the condition.
Every value in JavaScript has an inherent boolean value. When that value is evaluated in the context of a boolean expression, the value will be transformed into that inherent boolean value.
A value is falsy if it converts to false
when evaluated in a boolean context. For example, an empty String ""
is falsy because, ""
evaluates to false
. You already know if...else
statements, so let's use them to test the truthy-ness of ""
.
if ("") {
console.log("the value is truthy");
} else {
console.log("the value is falsy");
}
the value is falsy
Here’s the list of all* of the falsy values**:
false
null
typeundefined
type0
""
NaN
(stands for "not a number", check out the NaN MDN article)That's right, there are only six falsy values in all of JavaScript!
A value is truthy if it converts to true
when evaluated in a boolean context. For example, the number 1
is truthy because, 1
evaluates to true
. Let's use an if...else
statement again to test this out:
if (1) {
console.log("the value is truthy");
} else {
console.log("the value is falsy");
}
the value is truthy
Here are some other examples of truthy values:
true
42
"pizza"
"0"
"null"
"undefined"
{}
[]
Essentially, if it's not in the list of falsy values, then it's truthy!
Boolean(1);
true
Boolean("");
false
Sometimes, you might find yourself with the following type of conditional.
var isGoing = true;
var color;
if (isGoing) {
color = "green";
} else {
color = "red";
}
console.log(color);
green
In this example, the variable color
is being assigned to either "green"
or "red"
based on the value of isGoing
. This code works, but it’s a rather lengthy way for assigning a value to a variable. Thankfully, in JavaScript there’s another way.
TIP: Using if(isGoing)
is the same as using if(isGoing === true)
. Alternatively, using if(!isGoing)
is the same as using if(isGoing === false)
.
The ternary operator provides you with a shortcut alternative for writing lengthy if...else
statements.
let myvvar = someTest ? resultIfTrue : resultIfFalse;
To use the ternary operator, first provide a conditional statement on the left-side of the ?
. Then, between the ?
and :
write the code that would run if the condition is true
and on the right-hand side of the :
write the code that would run if the condition is false
. For example, you can rewrite the example code above as:
isGoing = true;
color = isGoing ? "green" : "red";
console.log(color);
green
This code not only replaces the conditional, but it also handles the variable assignment for color
.
If you breakdown the code, the condition isGoing
is placed on the left side of the ?
. Then, the first expression, after the ?
, is what will be run if the condition is true and the second expression after the, :
, is what will be run if the condition is false.
These expressions can be chained together to test multiple conditions as well, demonstrated here in a ternary conditional that adds a couple more plans to the above logic. This is the ternary version of a conditional statement that tests multiple conditions, which you'll learn about in the next unit:
let memberType = 'elite';
let price = memberType === 'basic' ? 5
: memberType === 'pro' ? 10
: memberType === 'elite' ? 20
: 0;
console.log(price);
20
If you find yourself repeating else if
statements in your code, where each condition is based on the same value, then it might be time to use a switch statement.
if (option === 1) {
console.log("You selected option 1.");
} else if (option === 2) {
console.log("You selected option 2.");
} else if (option === 3) {
console.log("You selected option 3.");
} else if (option === 4) {
console.log("You selected option 4.");
} else if (option === 5) {
console.log("You selected option 5.");
} else if (option === 6) {
console.log("You selected option 6.");
}
A switch statement is an another way to chain multiple else if
statements that are based on the same value without using conditional statements. Instead, you just switch which piece of code is executed based on a value.
Here, each else if
statement (option === [value]) has been replaced with a case
clause (case [value]:
) and those clauses have been wrapped inside the switch
statement.
When the switch
statement first evaluates, it looks for the first case
clause whose expression evaluates to the same value as the result of the expression passed to the switch
statement. Then, it transfers control to that case
clause, executing the associated statements.
So, if you set option equal to 3
...
var option = 3;
switch (option) {
case 1:
console.log("You selected option 1.");
case 2:
console.log("You selected option 2.");
case 3:
console.log("You selected option 3.");
case 4:
console.log("You selected option 4.");
case 5:
console.log("You selected option 5.");
case 6:
console.log("You selected option 6.");
}
You selected option 3. You selected option 4. You selected option 5. You selected option 6.
...then the switch
statement prints out options 3
, 4
, 5
and 6
.
But that’s not exactly like the original if...else
code at the top? So what’s missing?
The break
statement can be used to terminate a switch
statement and transfer control to the code following the terminated statement. By adding a break
to each case clause, you fix the issue of the switch
statement falling-through to other case
clauses.
var option = 3;
switch (option) {
case 1:
console.log("You selected option 1.");
break;
case 2:
console.log("You selected option 2.");
break;
case 3:
console.log("You selected option 3.");
break;
case 4:
console.log("You selected option 4.");
break;
case 5:
console.log("You selected option 5.");
break;
case 6:
console.log("You selected option 6.");
break; // technically, not needed
}
You selected option 3.
In some situations, you might want to leverage the "falling-through" behavior of switch statements to your advantage.
var month = 12;
var days = 0
switch(month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 1
case 4:
case 6:
case 9:
case 11:
days = days + 2;
default:
days = days + 28;
}
console.log("There are " + days + " days in this month.");
There are 31 days in this month.
In this example, each successive tier builds on the next by adding more to the output without any break
statements in the code. After the switch
statement jumps to any of the case
statements and continues to fall-through until reaching the end of the switch
statement, including the default
case.
You can add a default
case to a switch
statement and it will be executed when none of the values match the value of the switch expression.
var education = 'no high school diploma';
var salary = 0;
switch (education) {
case "no high school diploma":
salary = 25636;
break;
case "a high school diploma":
salary = 35256;
break;
case "an Associate's degree":
salary = 41496;
break;
case "a Bachelor's degree":
salary = 59124;
break;
case "a Master's degree":
salary = 69732;
break;
case "a Professional degree":
salary = 89960;
break;
case "a Doctoral degree":
salary = 84396;
break;
}
console.log("In 2015, a person with "+education+" earned an average of $"+salary.toLocaleString("en-US")+"/year.");
In 2015, a person with no high school diploma earned an average of $25,636/year.
Most of the time you will see a switch
case used to return the result (e.g. the day of the week in this example) from a function
based on the value passed into it. That would look like this:
function findDayOfWeek(dayNumber) {
switch (dayNumber) {
case 0:
return 'Sunday';
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
default:
return 'Invalid day number';
}
}
let day = findDayOfWeek(2);
console.log(day); // Tuesday
Tuesday
Switch cases use strict equality when checking the cases, so the expression result must be both the same value and the same type for the case to be triggered. If you don't define a default
case and no other case matches, the code will continue along outside the switch statement. as if it wasn't even there.
There are many different kinds of loops, but they all essentially do the same thing: they repeat an action some number of times. Three main pieces of information that any loop should have are:
x=x*3
or x=x-1
A while
loop will repeat the operation or block of code indefinitely until a specified condition is false
. The syntax for a while
loop is simpler, all you need to provide is the condition and the code you'd like to repeat. While loops are great when you don't know how many times you need to loop. You might have a case where you are taking input from a user and only want to stop when a certain condition is met, i.e. a user inputs a correct value to a question; he might need numerous attempts to get it right.
Here's a basic while
loop example that includes all three parts.
var start = 0; // when to start
while (start < 5) { // when to stop
console.log(start);
start = start + 2; // how to get to the next item
}
0 2 4
6
If a loop is missing any of these three things, then you might find yourself in trouble. For instance, a missing stop condition can result in a loop that never ends!
Don't run this code!
while (true) {
console.log("true is never false, so I will never stop!");
}
If you did try to run that code in the console, you probably crashed your browser tab.
Here's an example where a loop is missing how to get to the next item; the variable x is never incremented. x will remain 0 throughout the program, so the loop will never end.
Don't run this code!
var x = 0;
while (x < 1) {
console.log('Oops! x is never incremented from 0, so it will ALWAYS be less than 1');
}
"Fizzbuzz" is a famous interview question used in programming interviews. It goes something like this:
var x = 1;
while (x<=10) {
var out = "";
if (x%2===0) { out = "Fizz"; }
if (x%3===0) { out = out + "Buzz"; }
if (!out) {out=x}
console.log(out)
x = x + 1;
}
1 Fizz Buzz Fizz 5 FizzBuzz 7 Fizz Buzz Fizz
11
A while loop does not guarantee that the code within its statement block will execute. If the condition is never evaluated to true, the code will never be executed. Consider this example:
let counter = 10;
while (counter < 10) {
console.log(counter);
}
Here, the console.log
will never happen because counter
is assigned above as 10
which makes the while
condition false
from the start. Sometimes you might want the code to always be executed at least once, though. For this purpose you have the do ... while
loop:
let counter = 10;
do {
console.log(counter);
} while (counter < 10);
10
In this case, the loop will always be executed at least once because the do
statement comes before the while
condition is checked.
You learned in the comparative programming module that there are a few different ways to iterate (loop) in software development. One of those ways is by using the for loop. A for loop repeats the same operation or block of code until a specified condition is false. They can be used for any operation that needs to be repeated multiple times based on a conditional expression, such as looping through all the rows in an HTML table or adding a CSS class to a series of HTML elements.
The for loop explicitly forces you to define the start point, stop point, and each step of the loop. In fact, you'll get an Uncaught SyntaxError: Unexpected token
) if you leave out any of the three required pieces.
for (initializingExpression, condition, incrementingExpression) {
// code to repeat
}
The loop works as follows:
Here's an example of a for
loop that prints out the values from 0
to 5
. Notice the semicolons separating the different statements of the for loop: var i = 0;
i < 6;
i++
for (let i = 0; i < 6; i++ ) {
console.log("Printing out i = " + i);
}
Printing out i = 0 Printing out i = 1 Printing out i = 2 Printing out i = 3 Printing out i = 4 Printing out i = 5
Did you know you can also nest loops inside of each other? Paste this nested loop in your browser and take a look at what it prints out:
for (let x = 0; x < 5; x = x + 1) {
for (let y = 0; y < 3; y = y + 1) {
console.log(x + "," + y);
}
}
0,0 0,1 0,2 1,0 1,1 1,2 2,0 2,1 2,2 3,0 3,1 3,2 4,0 4,1 4,2
Notice the order that the output is being displayed.
For each value of x
in the outer loop, the inner for loop executes completely. The outer loop starts with x = 0
, and then the inner loop completes its cycle with all values of y
:
x = 0 and y = 0, 1, 2 // corresponds to (0, 0), (0, 1), and (0, 2)
Once the inner loop is done iterating over y
, then the outer loop continues to the next value, x = 1
, and the whole process begins again.
x = 0 and y = 0, 1, 2 // (0, 0) (0, 1) and (0, 2)
x = 1 and y = 0, 1, 2 // (1, 0) (1, 1) and (1, 2)
x = 2 and y = 0, 1, 2 // (2, 0) (2, 1) and (2, 2)
etc.
The break
and continue
statements are used to control the execution of loops at a more granular level. They can be used in any sort of loop. A common use of break is to break out of an infinite while
loop, like one below:
let r=0;
while (true){
if (r==5){
break;
}
console.log(r);
r++;
}
0 1 2 3 4
You might also want to break out of a loop when a specific condition is met, like in this for
loop which would print the numbers 0 through 9 to the console, but stops before printing 5 because the if
statement will be true
and the break
statement will execute, breaking the loop:
for (let r = 0; r < 10; r++) {
if (r === 5) {
break;
} else {
console.log(r);
}
}
0 1 2 3 4
While the break
statement breaks out of a loop entirely, the continue
statement makes it possible to skip the actual iteration. The following for
loop will print the numbers 0
through 3
, but skip 2
, continuing with 3
:
for(let r = 0; r < 4; r++) {
if(r === 2) {
continue;
} else {
console.log(r);
}
}
0 1 3
let outer = 0;
outerLoop: while (outer <= 1000000) {
let inner = 0;
innerLoop: while (inner <= 100) {
if (inner === 3) {
console.log('Breaking the outer loop from the inner loop.');
break outerLoop;
}
console.log('inner is', inner);
inner++;
}
if (outer === 5) {
console.log('Breaking!');
break;
}
console.log('outer is', outer);
outer++;
}
console.log('Loop has been broken.');
inner is 0 inner is 1 inner is 2 Breaking the outer loop from the inner loop. Loop has been broken.
This example is a little more complex, but we can break it down: The outer loop is set to iterate from 0
to 1000000
. If it encounters 5
, the if
statement within the outer loop will break it. However, before it hits this if
statement, there is another while
loop set to iterate a variable inner
from 0
to 100
. Within that loop is an if
statement that checks each iteration to see if inner === 3
. If that condition is true it will break the outer loop using the label outerLoop
!
When the outer loop is originally created it is given a label of outerLoop
. If we had simply used break;
inside the inner loop rather than break outerLoop;
, it would have broken the inner loop and continued with the outer one. The if
statement checking whether outer === 5
would still have broken the outer loop at that point, but using a label allowed us to break it from inside another loop. This technique is not widely used, but if you find yourself in a situation where you need to break out of a specific loop, remember that you can label your loops and attach that label to a break
or continue
statement later on.
Functions allow you to package up lines of code that you can use (and often reuse) in your programs. The generic syntax for defining a function in JavaScript is:
function someFunctionName() {
// code to execute
}
You need only three things to define a function in JavaScript:
Sometimes they take parameters like the pizza button from the beginning of this lesson. reheatPizza()
had one parameter: the number of slices.
function reheatPizza(numSlices) {
// code that figures out reheat settings!
}
The parameter is listed as a variable after the function name, inside the parentheses. And, if there were multiple parameters, you would just separate them with commas.
function doubleGreeting(name, otherName) {
// code to greet two people!
}
But, you can also have functions that don't have any parameters. Instead, they just package up some code and perform some task. In this case, you would just leave the parentheses empty. Take this one for example. Here's a simple function that just prints out "Hello!"
.
function sayHello() {
console.log("Hello!");
}
In the definition of a function
, a parameter is a variable that goes in the parentheses after the function name and allows the function to take some input from its caller. If you wanted to redefine the function above such that the string it logs to the console is dynamically chosen by the user, all you need to do is define the function with a parameter:
function sayHello(message) {
console.log(message);
}
Inside the function, the parameter message
becomes a variable that can be used throughout the function's code block. The variable is set when the user calls the function, by passing the message
they would like printed in the parentheses when they call the function, e.g. sayHello('Hi');
.
If you tried pasting any of the functions above into the JavaScript console, you probably didn't notice much happen. In fact, you probably saw undefined
returned back to you. undefined
is the default return value on the console when nothing is explicitly returned using the special return
keyword.
In the sayHello()
function above, a value is printed to the console with console.log
, but not explicitly returned with a return statement. You can write a return statement by using the return
keyword followed by the expression or value that you want to return.
// declares the sayHello function
function sayHello() {
return "Hello!"; // returns value instead of printing it
}
Your functions can have as many parameters as you wish (separated by commas) and they can be called whatever you want. You should, however, stick with standard JavaScript naming conventions and give them names that make sense and which use camelCase. Also keep in mind that the more parameters there are the more complex the function is to use. In general it's a good idea to try to keep your functions as small and simple as possible. If one gets to be too complex, it might mean that some of its code could also be split off into its own function. You can also return
anything you want. Some functions return a single value, but it's not uncommon to return other data types as well, such as boolean results, arrays, objects, and even other functions. The parameters and return value for your function depend entirely on its intended functionality.
Now, to get your function to do something, you have to invoke or call the function using the function name, followed by parentheses with any arguments that are passed into it. Functions are like machines. You can build the machine, but it won't do anything unless you also turn it on.
function add1(num) {
return num + 1;
}
Here's how you would call the add1()
function. It requires you to match the function definition by passing it the arguments it expects to receive.
add1(2);
3
return
value¶The final thing to understand when it comes to defining and using functions is what to do with the return value. In the above function you simply call it and that's it. You're not actually doing anything with the return value. It's much more common to store the result of a function call in a variable so you can use it later. To do this, just set a variable equal to the result of the function call:
var newNum = add1(4);
console.log(newNum);
5
At first, it can be a bit tricky to know when something is either a parameter or an argument. The key difference is in where they show up in the code.
function findAverage(x, y) {
var answer = (x + y) / 2;
return answer;
}
var avg = findAverage(5, 9);
x
and y
are parameters in the declaration of the function findAverage()
5
and 9
are arguments in the invoke of function findAverage()
It’s important to understand that return and print are not the same thing. Printing a value to the JavaScript console only displays a value (that you can view for debugging purposes), but the value it displays can't really be used for anything more than that. For this reason, you should remember to only use console.log
to test your code in the JavaScript console.
Paste the following function declaration and function invocation into the JavaScript console to see the difference between logging (printing) and returning:
function isThisWorking(input) {
console.log("Printing: isThisWorking was called and " + input + " was passed in as an argument.");
return "Returning: I am returning this string!";
}
isThisWorking(3);
Printing: isThisWorking was called and 3 was passed in as an argument.
'Returning: I am returning this string!'
If you don't explicitly define a return value, the function will return undefined
by default.
function isThisWorking(input) {
console.log("Printing: isThisWorking was called and " + input + " was passed in as an argument.");
}
isThisWorking(3);
Printing: isThisWorking was called and 3 was passed in as an argument.
function test() {
return 1;
return 2;
}
test();
1
1
was returned! Once the code evaluates the first return
statement, the function finishes. The second return statement will never be reached.
However, it is possible to have multiple return
statements in a function.
For instance, you could use a conditional to specify when each of the return
statements is evaluated. You could, for example, only return the value of 1
if the string "one"
was passed into the test()
function. Else, you could return the value of 2
.
Returning a value from a function is great, but what's the use of a return value if you're not going to use the value to do something?
A function's return value can be stored in a variable or reused throughout your program as a function argument. Here, we have a function that adds two numbers together, and another function that divides a number by 2
. We can find the average of 5
and 7
by using the add()
function to add a pair of numbers together, and then by passing the sum of the two numbers add(5, 7)
into the function divideByTwo()
as an argument.
And finally, we can even store the final answer in a variable called average
and use the variable to perform even more calculations in more places!
// returns the sum of two numbers
function add(x, y) {
return x + y;
}
// returns the value of a number divided by 2
function divideByTwo(num) {
return num / 2;
}
var sum = add(5, 7); // call the "add" function and store the returned value in the "sum" variable
var average = divideByTwo(sum); // call the "divideByTwo" function and store the returned value in the "average" variable
console.log(average);
6
OR
divideByTwo(add(5, 7));
6
So you might be wondering: "Why wouldn't I always use global variables? Then, I would never need to use function arguments since ALL my functions would have access to EVERYTHING!"
Well... Global variables might seem like a convenient idea at first, especially when you're writing small scripts and programs, but there are many reasons why you shouldn't use them unless you have to. For instance, global variables can conflict with other global variables of the same name. Once your programs get larger and larger, it'll get harder and harder to keep track and prevent this from happening.
There are also other reasons you'll learn more about in more advanced courses. But for now, just work on minimizing the use of global variables as much as possible.
Sometimes your JavaScript code will produce errors that may seem counterintuitive at first. Hoisting is another one of those topics that might be the cause of some of these tricky errors you're debugging.
Let's take a look at an example:
sayHi("Julia");
function sayHi(name) {
console.log(greeting + " " + name);
var greeting;
}
undefined Julia
sayHi("Julia");
function sayHi(name) {
console.log(greeting + " " + name);
var greeting = "Hello";
}
undefined Julia
function sayHi(name) {
var greeting = "Hello";
console.log(greeting + " " + name);
}
sayHi("Julia");
Hello Julia
Declare functions and variables at the top of your scripts, so the syntax and behavior are consistent with each other.
Once you know how to declare a function, a whole new set of possibilities will open up to you.
For instance, remember how you can store anything you want in a variable? Well, in JavaScript, you can also store functions in variables. When a function is stored inside a variable it's called a function expression.
var catSays = function (max) {
var catMessage = "";
for (var i = 0; i < max; i++) {
catMessage += "meow ";
}
return catMessage;
};
Notice how the function
keyword no longer has a name.
It's an anonymous function, a function with no name, and you've stored it in a variable called catSays
.
And, if you try accessing the value of the variable catSays
, you'll even see the function returned back to you.
catSays
[Function: catSays]
the stored function can be called with the name of the variable, which stores it:
catSays(2)
'meow meow '
Deciding when to use a function expression and when to use a function declaration can depend on a few things, and you will see some ways to use them in the next section. But, one thing you'll want to be careful of is hoisting.
All function declarations are hoisted and loaded before the script is actually run. Function expressions are not hoisted, since they involve variable assignment, and only variable declarations are hoisted. The function expression will not be loaded until the interpreter reaches it in the script.
Being able to store a function in a variable makes it really simple to pass the function into another function. A function that is passed into another function is called a callback. Let's say you had a helloCat()
function, and you wanted it to return "Hello"
followed by a string of "meows"
like you had with catSays
. Well, rather than redoing all of your hard work, you can make helloCat()
accept a callback function, and pass in catSays
.
// function expression catSays
var catSays = function (max) {
var catMessage = "";
for (var i = 0; i < max; i++) {
catMessage += "meow ";
}
return catMessage;
};
// function declaration helloCat accepting a callback
function helloCat(callbackFunc) {
return "Hello " + callbackFunc(3);
}
// pass in catSays as a callback function
helloCat(catSays);
'Hello meow meow meow '
the function stored in a variable can have a name, but that name is not usable for calling:
var favoriteMovie = function movie() {
return "The fountain";
};
favoriteMovie()
'The fountain'
movie()
ReferenceError: movie is not defined
Named functions are great for a smoother debugging experience, since those functions will have a useful name to display in stack traces. They're completely optional, however, and you'll often read code written by developers who prefer one way or the other.
A function expression is when a function is assigned to a variable. And, in JavaScript, this can also happen when you pass a function inline as an argument to another function. Take the favoriteMovie example for instance:
// Function expression that assigns the function displayFavorite
// to the variable favoriteMovie
var favoriteMovie = function displayFavorite(movieName) {
console.log("My favorite movie is " + movieName);
};
// Function declaration that has two parameters: a function for displaying
// a message, along with a name of a movie
function movies(messageFunction, name) {
messageFunction(name);
}
// Call the movies function, pass in the favoriteMovie function and name of movie
movies(favoriteMovie, "Finding Nemo");
My favorite movie is Finding Nemo
But you could have bypassed the first assignment of the function, by passing the function to the movies()
function inline.
// Function declaration that takes in two arguments: a function for displaying
// a message, along with a name of a movie
function movies(messageFunction, name) {
messageFunction(name);
}
// Call the movies function, pass in the function and name of movie
movies(function displayFavorite(movieName) {
console.log("My favorite movie is " + movieName);
}, "Finding Nemo");
My favorite movie is Finding Nemo
This type of syntax, writing function expressions that pass a function into another function inline, is really common in JavaScript. It can be a little tricky at first, but be patient, keep practicing, and you'll start to get the hang of it!
Using an anonymous inline function expression might seem like a very not-useful thing at first. Why define a function that can only be used once and you can't even call it by name?
Anonymous inline function expressions are often used with function callbacks that are probably not going to be reused elsewhere. Yes, you could store the function in a variable, give it a name, and pass it in like you saw in the examples above. However, when you know the function is not going to be reused, it could save you many lines of code to just define it inline.
function TestTry(){
console.log("TestTry method run successfully");
}
try {
TestTry();
// Invalid call: non existent function
TestT();
} catch(exception) {
console.log("There was an error in the code. The details are:\n"+exception.message);
}
TestTry method run successfully There was an error in the code. The details are: TestT is not defined
function checkValue(value){
try {
if (value <=5){
throw new Error("Please submit a minimum value of at least 5.")
} else if (value >=10) {
throw new Error("Please submit a value of that does not exceeed 10.")
}
} catch(e) {
console.log("There was an error. The details are:\n"+e.message);
}
}
checkValue(2);
There was an error. The details are: Please submit a minimum value of at least 5.
function traceIt(){
try {
throw new Error("myError1");
} catch(e) {
console.log("There was an error. The details are:\n"+e.stack);
}
}
function b(){traceIt();}
function a(){
b(3, 4, "\n\n", undefined, {});
}
a("first call, first argument");
There was an error. The details are: Error: myError1 at traceIt (evalmachine.<anonymous>:3:15) at b (evalmachine.<anonymous>:9:14) at a (evalmachine.<anonymous>:12:5) at evalmachine.<anonymous>:15:1 at Script.runInThisContext (vm.js:96:20) at Object.runInThisContext (vm.js:303:38) at run ([eval]:1054:15) at onRunRequest ([eval]:888:18) at onMessage ([eval]:848:13) at process.emit (events.js:182:13)
var codedURL = encodeURIComponent('difficult &$%@# to transmit');
console.log("Coded: "+codedURL);
console.log("Decoded: "+decodeURIComponent(codedURL));
Coded: difficult%20%26%24%25%40%23%20to%20transmit Decoded: difficult &$%@# to transmit
The array is one of the most useful data structures in JavaScript. At its core, an array is just an ordered collection of elements, enclosed by square brackets (i.e., [
and ]
).
Arrays can have as many values in them as you like, and the values can be of any data type: integers, strings, objects, functions, even other arrays. Arrays themselves are considered objects, but this is in the generic sense that "everything is an object" in JavaScript - they do not have named keys to access their elements.
Arrays are considered to be iterable, which means you can loop through them and perform an action on each array element, and you can access their values by index, beginning with 0.
An array stores multiple values into a single, organized data structure. You can define a new array by listing values separated with commas between square brackets [].
For example, imagine the following spread of donuts.
You can represent the spread of donuts using an array.
// creates a `donuts` array with six strings
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller", "cinnamon sugar", "sprinkled"];
Or declare the array using the Array()
constructor:
var donuts = new Array("glazed","chocolate frosted","Boston creme","glazed cruller","cinnamon sugar","sprinkled");
It's recommended that you always use the first method above to declare arrays, since the second method can produce unexpected results. You might think, for example, that writing let myArray = new Array(20)
would create an array with the number 20
in it, but in fact it will create an array with 20 undefined
elements.
You can store strings, numbers, booleans… and really anything! Each stored piece called element.
// creates a `mixedData` array with mixed data types
var mixedData = ["abcd", 1, true, undefined, null, "all the things"];
You can even store an array in an array to create a nested array!
// creates a `arraysInArrays` array with three arrays
var arraysInArrays = [[1, 2, 3], ["Julia", "James"], [true, false, true, false]];
Nested arrays can be particularly hard to read, so it's common to write them on one line, using a newline after each comma:
var arraysInArrays = [
[1, 2, 3],
["Julia", "James"],
[true, false, true, false]
];
Remember that elements in an array are indexed starting at the position 0
. To access an element in an array, use the name of the array immediately followed by square brackets containing the index of the value you want to access.
console.log(donuts[0]); // "glazed" is the first element in the `donuts` array
glazed
One thing to be aware of is if you try to access an element at an index that does not exist, a value of undefined
will be returned back.
Avoid accessing elements outside the bounds of an array.
console.log(donuts[6]); // the sixth element in `donuts` array does not exist!
undefined
Finally, if you want to change the value of an element in array, you can do so by setting it equal to a new value.
donuts[1] = "glazed cruller"; // changes the second element in the `donuts` array to "glazed cruller"
console.log(donuts);
[ 'glazed', 'glazed cruller', 'Boston creme', 'glazed cruller', 'cinnamon sugar', 'sprinkled' ]
you can store variables in an array:
var captain = "Mal";
var second = "Zoe";
var pilot = "Wash";
var companion = "Inara";
var mercenary = "Jayne";
var mechanic = "Kaylee";
var crew = [captain,second,pilot,companion,mercenary,mechanic];
console.log(crew);
[ 'Mal', 'Zoe', 'Wash', 'Inara', 'Jayne', 'Kaylee' ]
JavaScript provides a large number of built-in methods for modifying arrays and accessing values in an array, check out the MDN Documentation, or type []. into the JavaScript console for a list of all the available Array methods.
crew;
[ 'Mal', 'Zoe', 'Wash', 'Inara', 'Jayne', 'Kaylee' ]
slice()
¶You can also slice an array to obtain a subset of it by using the slice()
method. The following slices the array and returns only indices 2
up to but not including 4
. This does not modify the original array:
crew.slice(2,4);
[ 'Wash', 'Inara' ]
indexOf()
¶crew.indexOf("Inara");
3
includes()
¶f you need to know whether a specific element exists in an array, you can test it using the includes()
method, which will return true
if the element exists in the array, and false
otherwise:
crew.includes("Inara");
true
reverse()
¶crew.reverse();
crew;
[ 'Kaylee', 'Jayne', 'Inara', 'Wash', 'Zoe', 'Mal' ]
sort()
¶crew.sort();
console.log(crew);
[ 'Inara', 'Jayne', 'Kaylee', 'Mal', 'Wash', 'Zoe' ]
You can find the length of an array by using its length
property. To access the length
property, type the name of the array, followed by a period .
(you’ll also use the period to access other properties and methods), and the word length
. The length
property will then return the number of elements in the array.
console.log(donuts.length);
6
TIP: *Strings have a length property too! You can use it to get the length of any string. For example,
"supercalifragilisticexpialidocious".length
returns 34
push()
¶You can use the push()
method to add elements to the end of an array.
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
donuts.push("powdered"); // pushes "powdered" onto the end of the `donuts` array
5
donuts;
[ 'glazed', 'chocolate frosted', 'Boston creme', 'glazed cruller', 'powdered' ]
Notice, with the push()
method you need to pass the value of the element you want to add to the end of the array. Also, the push()
method returns the length of the array after an element has been added.
You can add more elements at once:
var doctor = "Simon";
var sister = "River";
var shepherd = "Book";
crew.push(doctor,sister,shepherd);
9
pop()
¶Alternatively, you can use the pop()
method to remove elements from the end of an array.
With the pop()
method you don’t need to pass a value; instead, pop()
will always remove the last element from the end of the array. Also, pop()
returns the element that has been removed in case you need to use it.
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
// the pop() method removes and returns "glazed cruller" because that was the LAST element of `donuts` array
donuts.pop();
'glazed cruller'
shift()
and unshift()
¶While the pop()
method pops an item off the end of the array, you can pop an item off the front of the array using the shift()
method. It returns the item you "shifted". Likewise, you can add one or more items to the beginning of the array using the unshift()
method:
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
// the shift() method removes and returns "glazed" because that was the FIRST element of `donuts` array
donuts.shift();
'glazed'
donuts;
[ 'chocolate frosted', 'Boston creme', 'glazed cruller' ]
// pushes "powdered" onto the beginning of the `donuts` array
donuts.unshift("powdered");
4
donuts;
[ 'powdered', 'chocolate frosted', 'Boston creme', 'glazed cruller' ]
delete()
¶You might think that you can delete an item by passing its index to the delete
keyword:
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
delete donuts[2];
typeof donuts[2];
'undefined'
But it doesn't actually delete the item, it replaces it with undefined
. The correct way to truly delete a specific item in an array is to use the splice()
method.
splice()
¶splice()
is another handy method that allows you to add and remove elements from anywhere within an array.
While push()
and pop()
limit you to adding and removing elements from the end of an array, splice()
lets you specify the index location to add new elements, as well as the number of elements you'd like to delete (if any).
splice()
is an incredibly powerful method that allows you to manipulate your arrays in a variety of ways. Any combination of adding or removing elements from an array can all be done in one simple line of code.
var donuts = ["glazed", "chocolate frosted", "Boston creme", "glazed cruller"];
// removes "chocolate frosted" at index 1 and adds "chocolate cruller" and "creme de leche" starting at index 1
donuts.splice(1, 1, "chocolate cruller", "creme de leche");
[ 'chocolate frosted' ]
donuts
[ 'glazed', 'chocolate cruller', 'creme de leche', 'Boston creme', 'glazed cruller' ]
Syntax of splice()
method:
arrayName.splice(arg1, arg2, item1, ....., itemX);
where,
arg1
= Mandatory argument. Specifies the starting index position to add/remove items. You can use a negative value to specify the position from the end of the array e.g., -1
specifies the last element.arg2
= Optional argument. Specifies the count of elements to be removed. If set to 0
, no items will be removed.item1, ....., itemX
are the items to be added at index position arg1
splice()
method returns the item(s) that were removedvar donuts = ["cookies", "cinnamon sugar", "creme de leche"];
donuts.splice(-2, 0, "chocolate frosted", "glazed");
[]
donuts;
[ 'cookies', 'chocolate frosted', 'glazed', 'cinnamon sugar', 'creme de leche' ]
/* Use only the splice() method to modify the rainbow variable:
* - remove "Blackberry"
* - add "Yellow" and "Green"
* - add "Purple" (to the end)
*/
var rainbow = ['Red', 'Orange', 'Blackberry', 'Blue'];
rainbow.splice(-2,1,"Yellow", "Green");
rainbow.splice(rainbow.length,0,"Purple")
rainbow
[ 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Purple' ]
concat()
¶You can merge two arrays together using the concat()
method (short for concatenate which means "to link things together in a chain or a series"). Just pass one or more arrays into it to merge them all together:
var fruits = ['apples', 'pears', 'cherries', 'bananas', 'peaches', 'oranges'];
var vegetables = ['carrots', 'peas', 'beans', 'lettuce'];
fruits.concat(vegetables);
[ 'apples', 'pears', 'cherries', 'bananas', 'peaches', 'oranges', 'carrots', 'peas', 'beans', 'lettuce' ]
Once the data is in the array, you want to be able to efficiently access and manipulate each element in the array without writing repetitive code for each element.
For instance, if this was our original donuts array:
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
and we decided to make all the same donut types, but only sell them as donut holes instead, we could write the following code:
donuts[0] += " hole";
donuts[1] += " hole";
donuts[2] += " hole";
donuts
[ 'jelly donut hole', 'chocolate donut hole', 'glazed donut hole' ]
But remember, you have another powerful tool at your disposal, loops!
To loop through an array, you can use a variable to represent the index in the array, and then loop over that index to perform whatever manipulations your heart desires.
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
// the variable `i` is used to step through each element in the array
for (let i = 0; i < donuts.length; i++) {
donuts[i] += " hole";
donuts[i] = donuts[i].toUpperCase();
}
donuts
[ 'JELLY DONUT HOLE', 'CHOCOLATE DONUT HOLE', 'GLAZED DONUT HOLE' ]
In this example, the variable i
is being used to represent the index of the array. As i
is incremented, you are stepping over each element in the array starting from 0
until donuts.length - 1
.
for...of
loop¶Using a for...of
loop you can access the values directly, which is significantly less verbose than the "standard" loop as you can see here. In this kind of for
loop, i
is the actual value of that array element, which makes it very easy to use:
for (let element of donuts) {
console.log(element);
}
JELLY DONUT HOLE CHOCOLATE DONUT HOLE GLAZED DONUT HOLE
for...in
loop¶The for...in
loop iterates over the enumerable properties of the array rather than its values. In a normal array this means that a for...in
loop iterates over the array's indices (like the standard for
loop) rather than its values (like the for...of
loop):
for (let index in donuts) {
console.log(donuts[index]);
}
JELLY DONUT HOLE CHOCOLATE DONUT HOLE GLAZED DONUT HOLE
forEach()
loop¶Arrays have a set of special methods to help you iterate over and perform operations on collections of data. You can view the MDN Documentation list of Array methods here, but a couple big ones to know are the forEach()
and map()
methods.
The forEach()
method gives you an alternative way to iterate over an array, and manipulate each element in the array with an inline function expression.
var donuts = ["jelly donut", "chocolate donut", "glazed donut"];
donuts.forEach(function(donut) {
donut += " hole";
donut = donut.toUpperCase();
console.log(donut);
});
JELLY DONUT HOLE CHOCOLATE DONUT HOLE GLAZED DONUT HOLE
donuts
[ 'jelly donut', 'chocolate donut', 'glazed donut' ]
Notice that the forEach()
method iterates over the array without the need of an explicitly defined index. In the example above, donut
corresponds to the element in the array itself. This is different from a for
or while
loop where an index is used to access each element in the array:
for (var i = 0; i < donuts.length; i++) {
donuts[i] += " hole";
donuts[i] = donuts[i].toUpperCase();
console.log(donuts[i]);
}
JELLY DONUT HOLE CHOCOLATE DONUT HOLE GLAZED DONUT HOLE
The function that you pass to the forEach()
method can take up to three parameters. We could call them as element
index
and array
, but you can call them whatever you like.
The forEach()
method will call this function once for each element in the array (hence the name forEach
). Each time, it will call the function with different arguments. The element
parameter will get the value of the array element. The index
parameter will get the index of the element (starting with zero). The array
parameter will get a reference to the whole array, which is handy if you want to modify the elements.
words = ["cat", "in", "hat"];
words.forEach(function(word, num, all) {
console.log("Word " + num + " in " + all.toString() + " is " + word);
});
Word 0 in cat,in,hat is cat Word 1 in cat,in,hat is in Word 2 in cat,in,hat is hat
var test = [12, 929, 11, 3, 199, 1000, 7, 1, 24, 37, 4,
19, 300, 3775, 299, 36, 209, 148, 169, 299,
6, 109, 20, 58, 139, 59, 3, 1, 139];
test.forEach(function(element,index,array){
if (element%3===0){
array[index] += 100;
}
});
test
[ 112, 929, 11, 103, 199, 1000, 7, 1, 124, 37, 4, 19, 400, 3775, 299, 136, 209, 148, 169, 299, 106, 109, 20, 58, 139, 59, 103, 1, 139 ]
map()
loop¶Using forEach()
will not be useful if you want to permanently modify the original array. forEach()
always returns undefined
. However, creating a new array from an existing array is simple with the powerful map()
method.
With the map()
method, you can take an array, perform some operation on each element of the array, and return a new array.
donuts;
[ 'JELLY DONUT HOLE', 'CHOCOLATE DONUT HOLE', 'GLAZED DONUT HOLE' ]
var improvedDonuts = donuts.map(function(donut,index,array) {
donut += "-"+array[(array.length+index-1)%array.length]+" donut";
return donut;
});
improvedDonuts;
[ 'JELLY DONUT HOLE-GLAZED DONUT HOLE donut', 'CHOCOLATE DONUT HOLE-JELLY DONUT HOLE donut', 'GLAZED DONUT HOLE-CHOCOLATE DONUT HOLE donut' ]
The map()
method accepts one argument, a function that will be used to manipulate each element in the array. In the above example, we used a function expression to pass that function into map()
. This function is taking in one argument, donut
which corresponds to each element in the donuts
array. You no longer need to iterate over the indices anymore, map()
does all that work for you.
Oftentimes, donuts are arranged in a grid like this:
You could use an array of arrays that has the name of each donut associated with its position in the box. Here's an example:
var donutBox = [
["glazed", "chocolate glazed", "cinnamon"],
["powdered", "sprinkled", "glazed cruller"],
["chocolate cruller", "Boston creme", "creme de leche"]
];
If you wanted to loop over the donut box and display each donut (along with its position in the box!) you would start with writing a for
loop to loop over each row of the box of donuts:
// here, donutBox.length refers to the number of rows of donuts
for (var row = 0; row < donutBox.length; row++) {
console.log(donutBox[row]);
}
[ 'glazed', 'chocolate glazed', 'cinnamon' ] [ 'powdered', 'sprinkled', 'glazed cruller' ] [ 'chocolate cruller', 'Boston creme', 'creme de leche' ]
Since each row is an array of donuts, you next need to set up an inner-loop to loop over each cell in the arrays.
for (var row = 0; row < donutBox.length; row++) {
// here, donutBox[row].length refers to the length of the donut array currently being looped over
for (var column = 0; column < donutBox[row].length; column++) {
console.log(donutBox[row][column]);
}
}
glazed chocolate glazed cinnamon powdered sprinkled glazed cruller chocolate cruller Boston creme creme de leche
...
¶The ...
unpacks an Array into a list.
var numbers = [1,2,3];
console.log(...numbers);
1 2 3
Find the minimum and maximum values in an array using Math.min()
and Math.max()
methods
Math.max(...numbers);
3
Math.min(...numbers);
1
Another useful data structure you will come across in many programming languages is the Set
. Sets are similar to lists, arrays, dictionaries and objects in that they are organizational structures used for housing data, but they are unique in a few ways:
define a set using the Set
object, which takes an iterable as its argument:
let mySet = new Set([1, 2, 3, 4, 5]);
Like the other data structures, sets can house multiple types of data at the same time. However, since set elements must be immutable, they cannot contain other iterables like lists, dictionaries or other sets.
let myMixedSet = new Set(1, 'Apple', true); // ok
let myMixedSet = new Set(1, 'Apple', [1, 2, 3]); // not ok, set contains a list
Sets are iterable, meaning you can loop through them. In JavaScript, however, iteration of a set is a bit more complex because sets have no indexes to reference.
Sets have a variety of methods you can use to modify them. Some common ones you may need are listed in the following table. Note: this list is not intended to be all-inclusive as different languages have different method availability.
Method/Function | Purpose |
---|---|
add() | Add an element to the set |
delete() | Remove a specific element from the set |
clear() | Clear all elements from the set |
pop() | Pop a random element out of the set |
has() | Is the value present in the Set object? |
update() | Add the items of another set to this set |
length | Get the number of items in the set |
Objects are unordered collections. They contain key/value pairs, vhere key is a string, value can be either a primitive (e.g., strings, numbers, booleans, etc.) or another object (or a function). Each distinct key/value pair is known as property of that object.
The keys, the names of the object's properties, are strings, but quotation marks surrounding these strings are optional as long as the string is also a valid Javascript identifier. You'll commonly find quotation marks omitted from property names. Certain situations require them to be included, especially if the property name:
for
, if
, let
, true
, etc.).$
, and _
-- most accented characters).To properly create an object (dictionary), properties (keys) must be immutable, meaning that they should not be able to change. You can think of this concept as if it were a real dictionary, where each word has an associated definition. In a real dictionary, if the words were constantly changing or if there were duplicate words for the same definition or other such oddities, it would make the dictionary unreliable and effectively useless. Similarly in software development, using things like lists and other dictionaries for the keys of a dictionary is generally not allowed. Typically, developers use things that are easily identifiable and constant, such as strings and integers, as dictionary keys. However, you can fill the dictionary's values with anything you want:
let sister = {
name: "Sarah",
age: 23,
parents: [ "alice", "andy" ],
siblings: ["julia"],
favoriteColor: "purple",
pets: true
};
The syntax you see above is called object-literal notation. There are some important things you need to remember when you're structuring an object literal:
key: value
pairs are separated from each other by commas{ }
.And, kind of like how you can look up a word in the dictionary to find its definition, the key
in a key:value
pair allows you to look up a piece of information about an object. Here are a couple of examples how you can retrieve information about my sister's parents using the object you created.
// two equivalent ways to use the key to return its value
sister["parents"] // returns [ "alice", "andy" ]
sister.parents // also returns ["alice", "andy"]
sister["parents"]
is called bracket notation (because of the brackets!) and sister.parents
is called dot notation (because of the dot!).The sister
object above contains a bunch of properties about my sister, but doesn't really say what my sister does. For instance, let's say my sister likes to paint. You might have a paintPicture()
method that returns "Sarah paints a picture!"
whenever you call it. The syntax for this is pretty much exactly the same as how you defined the properties of the object. The only difference is, the value in the key:value
pair will be a function.
var sister = {
name: "Sarah",
age: 23,
parents: [ "alice", "andy" ],
siblings: ["julia"],
favoriteColor: "purple",
pets: true,
paintPicture: function() { return "Sarah paints!"; }
};
sister.paintPicture();
'Sarah paints!'
and you can access the name of my sister by accessing the name
property:
sister.name
'Sarah'
Feel free to use upper and lowercase numbers and letters, but don't start your property name with a number. You don't need to wrap the string in quotes! If it's a multi-word property, use camel case. Don't use hyphens in your property names
var richard = {
"1stSon": true;
"loves-snow": true;
};
richard.1stSon // error
richard.loves-snow // error
create an empty object with the Object()
constructor function:
let stone = new Object();
create an empty object with literal notation:
let brick = {};
examine the type of variable:
typeof "hello" // returns "string"
typeof true // returns "boolean"
typeof [1, 2, 3] // returns "object" (Arrays are a type of object)
typeof function hello() { } // returns "function"
typeof brick
'object'
While both methods ultimately return an object without properties of its own, the Object()
constructor function is a bit slower and more verbose. As such, the recommended way to create new objects in JavaScript is to use literal notation.
// create object with property
var tree = {
type: "oak",
height: 15,
age: 120,
};
Properties can be added to objects simply by specifying the property name, then giving it a value. The below example uses dot notation to add properties, but keep in mind that square bracket notation works just as well:
// add property to object
tree.isLeavesOn = true;
// now the object has 4 properties
tree
{ type: 'oak', height: 15, age: 120, isLeavesOn: true }
Keep in mind that data within objects are mutable, meaning that data can be changed. There are a few exceptions to this, but for now, let's see how we can modify/reassign existing properties in an object.
// change existing property of object
tree.isLeavesOn = false;
// the property has changed
tree
{ type: 'oak', height: 15, age: 120, isLeavesOn: false }
Recall that since objects are mutable, not only can we modify existing properties (or even add new ones) -- we can also delete properties from objects.
Say that an object actually doesn't have a property. We can go ahead and remove that property using the delete
operator.
Note that delete
directly mutates the object at hand. If we try to access a deleted property, the JavaScript interpreter will no longer be able to find that property because the key along with its value have been deleted:
delete tree.height;
//check tha state of the object:
tree
{ type: 'oak', age: 120, isLeavesOn: false }
Note: Trying to access a property which doesn't exist will not throw an error; it will return undefined
. Technically, any property that doesn't exist on an object will be undefined
, so while accessing a property that has been deleted will still return undefined
, if you log the object to the console the property is gone. For all intents and purposes, deleting an object property does effectively remove the property from the object.
for (let property in tree){
console.log("The tree's "+property+" is: "+tree[property])
}
The tree's type is: oak The tree's age is: 120 The tree's isLeavesOn is: false
Objects in JavaScript have three types of methods:
Static methods are methods that exist on the Object
constructor itself. They usually take an object as an argument and return some property or characteristic of that object. Common static methods you may use include Object.keys()
and Object.values()
, which return the passed object's properties and values, respectively. Another common static method is Object.entries()
, which returns an array of the object's property/value pairs.
At its core, an object is just a collection of key/value pairs. What if we want to extract only the keys from an object? Say we have this object representing a real-life dictionary:
var auto = {
location: 'garage',
ignition: 'off',
fueled: true,
};
Having a collection of just the words (i.e., the dictionary object's keys) may be particularly useful. While we could use a for...in
loop to iterate through an object and build our own list of keys.
const result = [];
for (const key in auto){result.push(key)};
result
[ 'location', 'ignition', 'fueled' ]
It can get a bit messy and verbose. Thankfully, JavaScript provides an abstraction just for this!
When Object.keys()
is given an object, it extracts just the keys of that object, then returns those keys in an array:
Object.keys(auto);
[ 'location', 'ignition', 'fueled' ]
Likewise, if we want a list of the values of an object, we can use Object.values()
:
Object.values(auto);
[ 'garage', 'off', true ]
Or both with Object.entries()
method:
Object.entries(auto); // returns an array of arrays
[ [ 'location', 'garage' ], [ 'ignition', 'off' ], [ 'fueled', true ] ]
Instance methods on the other hand are methods that require a specific object instance to operate on. You will probably use Object.instance.hasOwnProperty()
, which returns whether an object has a property in its own definition (rather than inheriting it from another object) and Object.instance.toString()
, which returns a string representation of the object.
auto.hasOwnProperty('ignition');
true
auto.hasOwnProperty('drive');
false
auto.toString();
'[object Object]'
Notice how the representation returned is [object Object]
. This is because we haven't defined the toString()
method on this specific object (the auto
), so it's inherited from the global object which all objects inherit from. You'll learn to override this later.
Methods you create are instance methods that yourself have added as properties on the object. This type of method is just a property on the object whose value is a function that you can call in order to execute some code.
At this point, we've mostly seen objects with properties that behave more like attributes. That is, properties such as name
or age
are data that describe an object, but they don't "do" anything. We can extend functionality to objects by adding methods to them.
const umbrella = {
color: "pink",
status: 'closed',
greetPerson : function (name){
console.log(`Hello ${name}!`);
},
open: function (){
this.status = 'opened';
console.log("The umbrella is opening")
},
};
add anonymus function to object:
umbrella.close = function (){
this.status = 'closed';
console.log("The umbrella is closing")
};
umbrella
{ color: 'pink', status: 'closed', greetPerson: [Function: greetPerson], open: [Function: open], close: [Function] }
This jumps ahead a little bit but it's pretty simple to understand. We just created a property called close
just like creating any other property, except this time its value is a function instead of a string, boolean, integer, or something else. The this
in the open
/ close
method refers to the umbrella
object itself, so when we call the function by using umbrella.open();
, status
is changed to opened
. The function doesn't return anything, it just changes the status
property.
this
Keyword¶Above you saw how you can create an object and give it a method. In that method there was a reference to the this
keyword.
this
refers to the object it is a part of. The this
keyword has different meanings depending on the context in which it is used, but you'll most likely see it used in two main ways:
this
refers to the object that owns the method. If the method isn't owned by any object, then this
refers to the global object.let car = { location: 'garage',
ignition: 'off',
fueled: true,
start: function() { this.ignition = 'on'; }
};
<button onclick="this.style.color='blue'">Click to Change My Text Color!</button>
We can access a function in an object using the property name. Again, another name for a function property of an object is a method. We can access it the same way that we do with other properties: by using dot notation or square bracket notation.
Just like calling a function, an object's method is called by adding parentheses at the end of the method's name. Note that both dot notation and square bracket notation return the same result!
call the umbrella
object's open
method:
umbrella.open()
The umbrella is opening
call the unbrella
object's close
method:
umbrella["close"]()
The umbrella is closing
If the method takes arguments, you can proceed the same way, too:
umbrella.greetPerson("Sumatra");
Hello Sumatra!
In JavaScript, a primitive (e.g., a string, number, boolean, etc.) is immutable. In other words, any changes made to an argument inside a function effectively creates a copy local to that function, and does not affect the primitive outside of that function. Check out the following example:
function changeToEight(n) {
n = 8; // whatever n was, it is now 8... but only in this function!
}
let n = 7;
changeToEight(n);
// n retains its value despite the attempt to change inside the function
console.log(n); //7
7
changeToEight()
takes in a single argument, n
, and changes it to 8
. However, this change only exists inside the function itself. We then pass the global variable n
(which is assigned the value 7
) into the function. After invoking it, n
is still equal to 7
.
On the other hand, objects in JavaScript are mutable. If you pass an object into a function, Javascript passes a reference to that object. Let's see what happens if we pass an object into a function and then modify a property:
let originalObject = { favoriteColor: 'red'};
function setToBlue(object) { object.favoriteColor = 'blue';}
setToBlue(originalObject);
// the property value has changed from "red" to "blue"
originalObject
{ favoriteColor: 'blue' }
In the above example, originalObject
contains a single property, favoriteColor
, which has a value of 'red'
. We pass originalObjec
t into the setToBlue()
function and invoke it. After accessing originalObject
's favoriteColor
property, we see that the value is now 'blue'
!
How did this happen? Well, since objects in JavaScript are passed by reference, if we make changes to that reference, we're actually directly modifying the original object itself!
What's more: the same rule applies when re-assigning an object to a new variable, and then changing that copy. Again, since objects are passed by reference, the original object is changed as well. Let's take a look at this more closely with another example.
Consider this iceCreamOriginal
object, which shows the amount of ice cream cones each instructor has eaten:
const iceCreamOriginal = {
Andrew: 3,
Richard: 15
};
Let's go ahead and assign a new variable to iceCreamOriginal
. We'll then check the value of its Richard
property:
const iceCreamCopy = iceCreamOriginal;
iceCreamCopy.Richard;
15
As expected, the expression iceCreamCopy.Richard
returns 15
(i.e., it is the same value as the Richard property in iceCreamOriginal
). Now, let's change the value in the copy, then check the results:
iceCreamCopy.Richard = 99;
iceCreamCopy.Richard;
99
iceCreamOriginal.Richard;
99
Since objects are passed by reference, making changes to the copy (iceCreamCopy
) has a direct effect on the original object (iceCreamOriginal
) as well. In both objects, the value of the Richard
property is now 99
.
On the topic of references, let's see what happens when we compare one object with another object. The following objects, parrot
and pigeon
, have the same methods and properties:
const parrot = {
group: 'bird',
feathers: true,
chirp: function () {console.log('Chirp chirp!');}
};
const pigeon = {
group: 'bird',
feathers: true,
chirp: function () {console.log('Chirp chirp!');}
};
Naturally, one might expect the parrot
object and pigeon
object to be equal. After all, both objects look exactly the same! Let's compare parrot
and pigeon
to find out:
parrot === pigeon;
false
What's going on here? As it turns out, the expression will only return true
when comparing two references to exactly the same object. Using what we now know about passing objects, let's confirm this.
let sameParrot = parrot;
As we've just learned, sameParrot
not only refers to the same object as parrot
-- they are the same object! If we make any updates to sameParrot
's properties, parrot
's properties will be updated with exactly the same changes as well. Now, the comparison will return true:
sameParrot == parrot
true
Recall that an object can contain data and the means to manipulate that data. But just how can an object reference its own properties, much less manipulate some of those properties itself? This is all possible with the this
keyword!
Using this
, methods can directly access the object that it is called on. The value of this
is the object “before dot”, the one used to call the method.
Consider the following object, triangle
:
const triangle = {
type: 'scalene',
identify: function () {
console.log(`This is a ${this.type} triangle.`);
}
};
Note that inside the identify()
method, the object this
is used. When you say this
, what you're really saying is "this object" or "the object at hand." this
is what gives the identify()
method direct access to the triangle
object's properties:
triangle.identify();
This is a scalene triangle.
When the identify()
method is called, the value of this
is set to the object it was called on: triangle
. As a result, the identify()
method can access and use triangle's type property, as seen in the above console.log()
expression.
Note that this
is a reserved word in JavaScript, and cannot be used as an identifier (e.g. variable names, function names, etc.).
Depending on how a function is called, this
can be set to different values! Later in this course, we'll take a deep dive into different ways that functions can be invoked, and how each approach influences the value of this
.
window
Object¶If you haven't worked with the window
object yet, this object is provided by the browser environment and is globally accessible to your JavaScript code using the identifier, window
. This object is not part of the JavaScript specification (i.e., ECMAScript); instead, it is developed by the W3C.
This window
object has access to a ton of information about the page itself, including:
window.location;
)window.scrollY
)window.scroll(0, window.scrollY + 200);
to scroll 200 pixels down from the current location)window.open("https://www.udacity.com/");
)function whoThis () {
debugger
this.trickyish = true
}
whoThis();
At invoking whoThis()
function the this
variable inside the function references the golobal window
object!
const car = {
numberOfDoors: 4,
drive: function () {
console.log(`Get in one of the ${this.numberOfDoors} doors, and let's go!`);
}
};
const letsRoll = car.drive;
letsRoll();
Get in one of the undefined doors, and let's go!
Even though car.drive
is a method, we're storing the function itself in the a variable letsRoll
. Because letsRoll()
is invoked as a regular function, inside of it this
will refer to the window
object.
window
¶Since the window
object is at the highest (i.e., global) level, an interesting thing happens with global variable declarations. Every variable declaration that is made at the global level (outside of a function) automatically becomes a property on the window
object!
Here we can see that the currentlyEating
variable is set to 'ice cream'
. Then, we immediately see that the window now has a currentlyEating
property! Checking this property against the currentlyEating
variable shows us that they are identical.
var currentlyEating = 'ice cream';
window.currentlyEating === currentlyEating
// true
var
, let
, and const
¶The keywords var
, let
, and const
are used to declare variables in JavaScript. var
has been around since the beginning of the language, while let
and const
are significantly newer additions (added in ES6).
Only declaring variables with the var
keyword will add them to the window
object. If you declare a variable outside of a function with either let
or const
, it will not be added as a property to the window
object.
let currentlyEating = 'ice cream';
window.currentlyEating === currentlyEating
// false!
window
¶Similarly to how global variables are accessible as properties on the window
object, any global function declarations are accessible on the window
object as methods:
function learnSomethingNew() {
window.open('https://www.udacity.com/');
}
window.learnSomethingNew === learnSomethingNew
// true
Declaring the learnSomethingNew()
function as a global function declaration (i.e., it's globally accessible and not written inside another function) makes it accessible to your code as either learnSomethingNew()
or window.learnSomethingNew()
.
We've seen that declaring global variables and functions add them as properties to the window object. Globally-accessible code sounds like something that might be super helpful, right? I mean, wouldn't it be great if you could always be within arms reach of some ice cream (or is that just my lifelong dream)?
Counterintuitively, though, global variables and functions are not ideal. There are actually a number of reasons why, but the two we'll look at are:
Tight coupling is a phrase that developers use to indicate code that is too dependent on the details of each other. The word "coupling" means the "pairing of two items together." In tight coupling, pieces of code are joined together in a way where changing one unintentionally alters the functioning of some other code:
var person = 'Richard';
function richardSaysHi() {
console.log(`${person} says 'hi!'`);
}
In the code above, note that the instructor
variable is declared globally. The richardSaysHi()
function does not have a local variable that it uses to store the instructor's name. Instead, it reaches out to the global variable and uses that. If we refactored this code by changing the variable from instructor
to teacher
, this would break the richardSaysHi()
function (or we'd have to update it there, too!). This is a (simple) example of tightly-coupled code.
A name collision occurs when two (or more) functions depend on a variable with the same name. A major problem with this is that both functions will try to update the variable and or set the variable, but these changes are overridden by each other!
Let's look at an example of name collision with this DOM manipulation code:
let counter = 1;
function addDivToHeader () {
const newDiv = document.createElement('div');
newDiv.textContent = 'div number ' + counter;
counter = counter + 1;
const headerSection = document.querySelector('header');
headerSection.appendChild(newDiv)
}
function addDivToFooter() {
const newDiv = document.createElement('div');
newDiv.textContent = 'div number ' + counter;
counter = counter + 1;
const footerSection = document.querySelector('footer');
footerSection.appendChild(newDiv)
}
In this code, we have an addDivToHeader()
function and a addDivToFooter()
function. Both of these functions create a <div>
element and increment a counter variable.
This code looks fine, but if you try running this code and adding a few <div>
s to the <header>
and <footer>
elements, you'll find that the numbering will get off! Both addDivToHeader()
and addDivToFooter() expect a global counter variable to be accessible to them -- not change out from under them!
Since both functions increment the counter
variable, if the code alternates between calling addDivToHeader()
and addDivToFooter()
, then their respective <div>
s will not have numerically ascending numbers. For example, if we had the following calls:
addDivToHeader();
addDivToHeader();
addDivToFooter();
addDivToHeader();
The developer probably wanted the <header>
to have three <div>
elements with the numbers 1
, 2
, and 3
and the <footer>
element to have a single <div>
with the number 1
. However, what this code will produce is a <header>
element with three <div>
but with the numbers 1
, 2
, and 4
(not 3
) and a <footer>
element with the number 3
...these are very different results. But it's happening because both functions depend on the counter
variable and both update it.
So what should you do instead? You should write as few global variables as possible. Write your variables inside of the functions that need them, keeping them as close to where they are needed as possible. Now, there are times when you'll need to write global variables, but you should only write them as a last resort.
A class in software development is like a blueprint. It provides the structure for all objects of a particular kind. It might be helpful to think of "class" as being short for "classification" - a grouping of things into a particular classification.
Classes are used for many things in programming, they are a special kind of object that defines the structure for other objects. You can define a class using the class
keyword.
Classes have a couple of unique characteristics when compared with other objects in object-oriented programming languages. For one, they usually require a constructor or some sort of initialization method which defines how the class should be structured and which attributes and properties it should have. Here is an example of a class:
class Mammal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' says "Hello!"');
}
}
In the above, the this
keyword refers to the class itself. The constructor
method ensures that all Animal
s will have a name
. The speak()
function, called a method when used inside a class, gives all Mammal
instances the ability to speak, and by default they will say "Hello!":
let human = new Mammal('John');
human.speak(); // logs 'John says "Hello!"'
John says "Hello!"
The above Mammal
(a human named John
), says "Hello!". Obviously though, some animals cannot talk. The real power of classes comes when you use them to inherit the functionality of other classes. In this way, the functionality of the Mammal
class above can be extended so that we gain the name
and the speak
function in any class that extends it, and can override whichever parts of the class we prefer. For example, if you want to create another type of MammaL
that still has a name
, but says something different (like a dog), you can easily do this using the extends
keyword to extend the Mammal
class's functionality into a new class:
class Dog extends Mammal {
speak() {
console.log(this.name + ' says "Woof!"');
}
}
let myDog = new Dog('Fido');
myDog.speak(); // logs 'Fido says "Woof!"'
Fido says "Woof!"
Because the Dog
class extends the Animal
class, it inherits the constructor
method (including the ability to pass the dog a name
) as well as the speak
method. However, since dogs cannot say "Hello", we decided to override the speak()
method with one specific to a dog. In this way the dog still has all the characteristics common to all animals, but has a speak method which is specific to this particular type of animal.
For now hold onto the idea that a class is simply a blueprint for defining objects of a particular type, and classes can be extended to allow more specificity within that type.
Classes are the fundamental unit of abstraction in object-oriented programming languages like JavaScript. In other words, they give the languages structure. For example, the Array
is actually a class! Earlier you saw, that they have methods available such as sort()
, reverse()
and so on.
These methods and this functionality is not there by accident. They are blueprinted within the definition of the Array
class itself, and this is only one example of a class. In object-oriented languages, classes - both those fundamental ones that are predefined like arrays, lists, strings, and so on - and those you create yourself, make up a large majority of the language's functionality.
In JavaScript, functions are first-class functions. This means that you can do with a function just about anything that you can do with other elements, such as numbers, strings, objects, arrays, etc. JavaScript functions can:
Note that while we can, say, treat a function as an object, a key difference between a function and an object is that functions can be called (i.e., invoked with ()
), while regular objects cannot.
let averageFN = function averageOf3(num1,num2,num3){
return (num1+num2+num3)/3;
}
Functions have a name
property:
averageFN.name
'averageOf3'
Functions have a length
property:
averageFN.length
3
Recall that a function must always return a value. Whether the value is explicitly specified in a return statement (e.g., returning a string, boolean, array, etc.), or the function implicitly returns undefined
(e.g., a function that simply logs something to the console), a function will always return just one value.
Since we know that functions are first-class functions, we can treat a function as a value and just as easily return a function from another function! A function that returns another function is known as higher-order function. Consider this example:
function alertThenReturn() {
console.log('Message 1!');
return function () {
console.log('Message 2!');
};
}
If alertThenReturn()
is invoked, we'll first see an alert message that says 'Message 1!'
, followed by the alertThenReturn()
function returning an anonymous function. However, we don't actually see an alert that says 'Message 2!'
, since none of the code from the inner function is executed. How do we go about executing the returned function?
const innerFN = alertThenReturn()
Message 1!
We can then use the innerFunction variable like any other function!
innerFN()
Message 2!
Likewise, this function can be invoked immediately without being stored in a variable. We'll still get the same outcome if we simply add another set of parentheses to the expression alertThenReturn();
:
alertThenReturn()()
Message 1! Message 2!
Notice the double set of parentheses (i.e. ()()
) in that function call! The first pair of parentheses executes the alertThenReturn()
function. The return value of this invocation is a function, which then gets invoked by the second pair of parentheses!
Recall that JavaScript functions are first-class functions. We can do with functions just about everything we can do with other values -- including passing them into other functions! A function that takes other functions as arguments (and/or returns a function) is known as a higher-order function. A function that is passed as an argument into another function is called a callback function.
Callback functions are great because they can delegate calling functions to other functions. They allow you to build your applications with composition, leading to cleaner and more efficient code.
function callAndAdd(n, callbackFunction) {
return n + callbackFunction();
}
function returnsThree() {
return 3;
}
callAndAdd(2, returnsThree);
5
function each(array, callbackSelect) {
for (let i = 0; i < array.length; i++) {
if (callbackSelect(array[i])) {
console.log(array[i]);
}
}
}
function isPositive(n) {
return n > 0;
};
each([-2, 7, 11, -4, -10], isPositive);
7 11
The each()
function takes in two arguments: an array, and callback function. The code within comprises of a for loop and a conditional: it first iterates through all the values of a supplied array argument, then prints out that values only if its callback function returns true.
The isPositive()
function returns a boolean depending on the argument passed in (i.e., true
if the number passed in is positive, and false
if not).
As such, when each([-2, 7, 11, -4, -10], isPositive);
is executed, the each()
function iterates through the entire array and only prints out values to the console that return true
when tested against the callback function: 7
and 11
.
Where have you probably seen callback functions used? In array methods! Functions are commonly passed into array methods and called on elements within an array (i.e., the array on which the method was called).
Let's check out a couple in detail:
forEach()
map()
filter()
Array's forEach()
method takes in a callback function and invokes that function for each element in the array. In other words, forEach()
allows you to iterate (i.e., loop) through an array, similar to using a for
loop. Check out its signature:
array.forEach(function callback(currentValue, index, array) {
// function code here
});
The callback function itself receives the arguments: the current array element, its index, and the entire array itself.
Let's say we have a simple function, logIfOdd()
, that takes in a single number and logs it to the console if that number is an odd number:
function logIfOdd(n) {
if (n % 2 !== 0) {
console.log(n);
}
}
logIfOdd(2); // (nothing is logged)
logIfOdd(3); // 3 is logged
3
We can iterate through an array with forEach()
and simply pass it the logIfOdd()
function!
[1, 5, 2, 4, 6, 3].forEach(logIfOdd)
1 5 3
Keep in mind that it's quite common to pass an anonymous function as an argument in forEach()
as well:
[1, 5, 2, 4, 6, 3].forEach(function (n) {
if (n % 2 !== 0) {
console.log(n);
}
});
1 5 3
map()
¶Array's map()
method is similar to forEach()
in that it invokes a callback function for each element in an array. However, map()
returns a new array based on what's returned from the callback function. Check out the following:
function length(name) {
return name.length;
};
let names = ['David', 'Richard', 'Veronika'];
const nameLengths = names.map(length);
nameLengths
[ 5, 7, 8 ]
So nameLengths
will be a new array: [5, 7, 8]
. Again, it is important to understand that the map()
method returns a new array; it does not modify the original array.
This was just a brief overview of how the map()
method works. For a deeper dive, check out map()
on MDN.
const musicData = [
{ artist: 'Adele', name: '25', sales: 1731000 },
{ artist: 'Drake', name: 'Views', sales: 1608000 },
{ artist: 'Beyonce', name: 'Lemonade', sales: 1554000 },
{ artist: 'Chris Stapleton', name: 'Traveller', sales: 1085000 },
{ artist: 'Pentatonix', name: 'A Pentatonix Christmas', sales: 904000 },
{ artist: 'Original Broadway Cast Recording',
name: 'Hamilton: An American Musical', sales: 820000 },
{ artist: 'Twenty One Pilots', name: 'Blurryface', sales: 738000 },
{ artist: 'Prince', name: 'The Very Best of Prince', sales: 668000 },
{ artist: 'Rihanna', name: 'Anti', sales: 603000 },
{ artist: 'Justin Bieber', name: 'Purpose', sales: 554000 }
];
const albumSalesStrings = musicData.map(function mstring(album){
return `${album.name} by ${album.artist} sold ${album.sales} copies`;
});
console.log(albumSalesStrings);
[ '25 by Adele sold 1731000 copies', 'Views by Drake sold 1608000 copies', 'Lemonade by Beyonce sold 1554000 copies', 'Traveller by Chris Stapleton sold 1085000 copies', 'A Pentatonix Christmas by Pentatonix sold 904000 copies', 'Hamilton: An American Musical by Original Broadway Cast Recording sold 820000 copies', 'Blurryface by Twenty One Pilots sold 738000 copies', 'The Very Best of Prince by Prince sold 668000 copies', 'Anti by Rihanna sold 603000 copies', 'Purpose by Justin Bieber sold 554000 copies' ]
filter()
¶Array's filter()
method is similar to the map()
method:
The difference is that the function passed to filter()
is used as a test, and only items in the array that pass the test are included in the new array. Consider the following example:
const shortNames = names.filter(function(name) {
return name.length < 6;
});
shortNames;
[ 'David' ]
just like with map()
, the function that's passed to filter()
gets called for each item in the names array. The first item (i.e., 'David'
) is stored in the name
variable. Then the test is performed -- and this is what's doing the actual filtering. First, it checks the length
of the name
. If it's 6
or greater, then it's skipped (and not included in the new array!). But, if the length of the name is less than 6
, then name.length < 6
returns true
and the value of name
is included in the new array!
And lastly, just like with map()
, the filter()
method returns a new array instead of modifying the original array.
This was just a brief overview of how the filter()
method works. For a deeper dive, check out filter()
on MDN.
const results = musicData.filter( function mfilter(album){
return (10<=album.name.length && album.name.length<=25);
});
results;
[ { artist: 'Pentatonix', name: 'A Pentatonix Christmas', sales: 904000 }, { artist: 'Twenty One Pilots', name: 'Blurryface', sales: 738000 }, { artist: 'Prince', name: 'The Very Best of Prince', sales: 668000 } ]
Array Methods on MDN
A function's runtime scope describes the variables available for use inside a given function. The code inside a function has access to:
You may be wondering why scope is so heavily associated with functions in JavaScript.
This is all because variables in JavaScript are traditionally defined in the scope of a function, rather than in the scope of a block. Since entering a function will change scope, any variables defined inside that function are not available outside of that function. Inside a function, all var
variables are in scope wether they are defined inside or outside of a block (e.g. { }
. If a variable is declared with let
in a block, then that variable is not available ooutside of that block.
Let's see an example of how function-scoping in JavaScript works:
var globalNumber = 2;
function globalIncrementer() {
globalNumber += 1;
const localConstNumber = 3;
{var blockVarNumber = 5}
{let blockLetNumber = 7}
return globalNumber
+localConstNumber
+blockVarNumber
+blockLetNumber;
}
globalIncrementer()
ReferenceError: blockLetNumber is not defined
Whenever your code attempts to access a variable during a function call, the JavaScript interpreter will always start off by looking within its own local variables. If the variable isn't found, the search will continue looking up what is called the scope chain. Let's now revisit the image from the beginning of this section, and visualize the entire process:
💡 The Global window
Object💡
Recall that when JavaScript applications run inside a host environment (e.g., a browser), the host provides a window
object, otherwise known as the global object. Any global variables declared are accessed as properties of this object, which represents the outermost level of the scope chain.
What happens when you create a variable with the same name as another variable somewhere in the scope chain?
JavaScript won't throw an error or otherwise prevent you from creating that extra variable. In fact, the variable with local scope will just temporarily "shadow" the variable in the outer scope. This is called variable shadowing. Consider the following example:
const symbol = '¥';
function displayPrice(price) {
const symbol = '$';
console.log(symbol + price);
}
displayPrice('80');
// '$80'
In the above snippet, note that symbol
is declared in two places:
displayPrice()
function, as a global variable.displayPrice()
function, as a local variable.
After invoking displayPrice()
and passing it an argument of '80'
, the function outputs '$80'
to the console.How does the JavaScript interpreter know which value of symbol to use? Well, since the variable pointing to '$'
is declared inside a function (i.e., the "inner" scope), it will override any variables of the same name that belong in an outer scope -- such as the global variable pointing to '¥'
. As a result, '$80'
is displayed rather than '¥80'
.
All in all, if there are any naming overlaps between variables in different contexts, they are all resolved by moving through the scope chain from inner to outer scopes (i.e., local all the way to global). This way, any local variables that have the same name take precedence over those with a wider scope.
In this section, we've seen quite a few examples of a nested function being able to access variables declared in its parent function's scope (i.e., in the scope in which that function was nested). These functions, combined with the lexical environment in which it was declared, actually have a very particular name: closure. Closures are very closely related to scope in JavaScript, and lead to some powerful and useful applications. We'll take a look at closures in detail next!
Variable identifier lookup and the scope chain are really powerful tools for a function to access identifiers in the code. In fact, this lets you do something really interesting: create a function now, package it up with some variables, and save it to run later. If you have five buttons on the screen, you could write five different click handler functions, or you could use the same code five times with different saved values.
Let's check out an example of a function retaining access to its scope. Consider the remember()
function below:
let globalNumber = 1
function remember(paramNumberOld) {
let localNumber = globalNumber;
globalNumber += localNumber;
function summarise(paramNumberNew) {
return globalNumber
+localNumber
+paramNumberOld
+paramNumberNew;
}
return summarise;
}
// globalNumber is not stored in the closure
const returnedFunction1 = remember(3);
// now globalNumber=2
const returnedFunction2 = remember(5);
// now globalNumber=4
globalNumber += 1;
// now globalNumber=5
// returnedFunction1 remembers localNumber=1 and paramNumberOld=3
console.log( returnedFunction1(7) ); // 5 + 1 + 3 + 7 = 16
// returnedFunction2 remembers localNumber=2 and paramNumberOld=5
console.log( returnedFunction2(11) );// 5 + 2 + 5 + 11 = 23
16 23
globalNumber
is a variable defined outside a function, hence it's a global variable in the global scope. In other words, globalNumber
is available for all functions to use.
But let's look closely at the other variable: localNumber
. It is referenced by summarise()
, even though it wasn't declared within summarise()
! This is possible because a nested function's scope includes variables declared in the scope where the function is nested (i.e., variables from its parent function's scope, where the function is defined).
When the Javascript engine enters remember()
, it creates a new execution scope that points back to the prior execution scope. This new scope includes a reference to the paramNumberOld
parameter (an immutable Number with the value 3
). When the engine reaches the inner function (a function expression), it attaches a link to the current execution scope.
This process of a function retaining access to its scope is called a closure. In this example, the inner function "closes over" number. A closure can capture any number of parameters and variables that it needs. MDN defines a closure as:
"the combination of a function and the lexical environment within which that function was declared."
This definition might not make a lot of sense if you don't know what the words "lexical environment" mean. The ES5 spec refers to a lexical environment as:
"the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code."
In this case, the "lexical environment" refers the code as it was written in the JavaScript file. As such, a closure is:
When a function is declared, it locks onto the scope chain. You might think this is pretty straightforward since we just looked at that in the previous section. What's really interesting about a function, though, is that it will retain this scope chain -- even if it is invoked in a location other than where it was declared. This is all due to the closure!
As it turns out, the summarise()
function and its lexical environment form a closure. This way, summarise()
has access to not only the global variable globalNumber
, but also the variable localNumber
, which was declared in the scope of its parent function, remember()
.
After remember(3)
is executed and returned, how is the returned function still able to access paramNumberOld
's value (i.e., 3
) and the actual value of globalNumber
? In this section, we'll investigate how closures allow us to store a snapshot of state at the time the function object is created.
Every time a function is defined, closure is created for that function. Strictly speaking, then, every function has closure! This is because functions close over at least one other context along the scope chain: the global scope. However, the capabilities of closures really shine when working with a nested function (i.e., a function defined within another function).
Recall that a nested function has access to variables outside of it. From what we have learned about the scope chain, this includes the variables from the outer, enclosing function itself (i.e., the parent function)! These nested functions close over (i.e., capture) variables that aren't passed in as arguments nor defined locally, otherwise known as free variables.
As we saw with the remember()
function earlier, it is important to note that a function maintains a reference to its parent's scope. If the reference to the function is still accessible, the scope persists!
The following myCounter()
function uses a closure to create a private state. The returned function has a private, but mutable state because it closes over the count
variable.
let globalCount = 10;
function myCounter() {
let count = 0;
return function () {
globalCount += 1;
count += 1;
console.log(count, globalCount);
};
}
let counter1 = myCounter();
let counter2 = myCounter();
counter1();
counter1();
1 11 2 12
counter2();
counter2();
1 13 2 14
Since count
is keep incrementing that proves that the value is retained betwen calls and also can be modified. Having different counts for counter1()
and counter2()
proves, that they point to two different closures.
Having the globalCount
incremented at each counter*()
call proves, that tha same global variable is accessed every time.
Let's try to access count
:
counter1.count
// undefined
The result is undefined
, that proves, that the count
variable is not accessible from outside - it is private.
JavaScript manages memory with automatic garbage collection. This means that when data is no longer referable (i.e., there are no remaining references to that data available for executable code), it is "garbage collected" and will be destroyed at some later point in time. This frees up the resources (i.e., computer memory) that the data had once occupied, making those resources available for re-use.
Let's look at garbage collection in the context of closures. We know that the variables of a parent function are accessible to the nested, inner function. If the nested function captures and uses its parent's variables (or variables along the scope chain, such as its parent's parent's variables), those variables will stay in memory as long as the functions that utilize them can still be referenced.
As such, referenceable variables in JavaScript are not garbage collected!
In the previous myCounter()
example the existence of the nested function keeps the count
variable from being available for garbage collection, therefore count
remains available for future access. After all, a given function (and its scope) does not end when the function is returned.
Further Research
A function declaration defines a function and does not require a variable to be assigned to it. It simply declares a function, and doesn't itself return a value. Here's an example:
function returnHello() {
return 'Hello!';
}
On the other hand, a function expression does return a value. Function expressions can be anonymous or named, and are part of another expression's syntax. They're commonly assigned to variables, as well. Here's the same function as a function expression:
// anonymous
const myFunction = function () {
return 'Hello!';
};
// named
const myFunction = function returnHello() {
return 'Hello!';
};
An immediately-invoked function expression, or IIFE (pronounced iffy), is a function that is called immediately after it is defined. Check out the following example:
(function sayHi(){
alert('Hi there!');
}
)();
// alerts 'Hi there!'
The syntax might seem a bit odd, but all we're doing is wrapping a function in parentheses, then adding a pair of parentheses at the end of that to invoke it!
There is another way we can write this to achieve the same results! The first set of parentheses can wrap around the entire expression. That is, we can move the first closing parenthesis to the very end:
(function sayHi(){
alert('Hi there!');
}());
// alerts 'Hi there!'
Again, using either approach will still produce the same result: alerting 'Hi there!'
in the browser.
Now, when would you choose one form over the other? Much of this is a stylistic choice; there is no "correct" way of auto-executing an anonymous function. Both are valid approaches for achieving the same result, and the JavaScript engine will still parse them each as a function expression (i.e., rather than as a function declaration).
Let's look into how we can go about passing arguments into IIFE's. Consider the following example of an anonymous function expression that takes in a single argument:
(function (name){
alert(`Hi, ${name}`);
}
)('Andrew');
// alerts 'Hi, Andrew'
The second pair of parentheses not only immediately executes the function preceding it -- it's also the place to put any arguments that the function may need! We pass in the string 'Andrew'
, which is stored in the function expression's name variable. It is then immediately invoked, alerting the message 'Hi, Andrew'
onto the screen.
Here's another example of an IIFE, this time taking two arguments and returning their product:
(function (x, y){
console.log(x * y);
}
)(2, 3);
// 6
Again -- the arguments passed into the anonymous function (i.e., 2
and 3
) belong in trailing set of parentheses.
(function (x, y){
console.log(x * y);
}
)(2, 3);
6
One of the primary uses for IIFE's is to create private scope (i.e., private state). Recall that variables in JavaScript are traditionally scoped to a function. Knowing this, we can leverage the behavior of closures to protect variables or methods from being accessed! Consider the following example of a simple closure within an IIFE, referenced by myFunction
:
const myFunction = (
function () {
const hi = 'Hi!';
return function () {
console.log(hi);
}
}
)();
Let's break myFunction
down and review the individual parts that make it up:
Above an immediately-invoked function expression is used to immediately run a function. This function runs and returns an anonymous function that is stored in the myFunction
variable.
Note that the function that is being returned closes over (i.e., captures) the hi
variable. This allows myFunction
to maintain a private, mutable state that cannot be accessed outside the function! What's more: because the function expressed is called immediately, the IIFE wraps up the code nicely so that we don't pollute the global scope.
If any of this sounds familiar -- it's because IIFE's are very closely related to everything you've learned about scope and closures!
Let's check out another example of an immediately-invoked function expression -- this time in the context of handling an event. Say that we want to create a button on a page that alerts the user on every other click. One way to begin doing this would be to keep track of the number of times that the button was clicked. But how should we maintain this data?
We could keep track of the count with a variable that we declare in the global scope (this would make sense if other parts of the application need access to the count data). However, an even better approach would be to enclose this data in event handler itself!
For one, this approach prevents us from polluting the global with extra variables (and potentially variable name collisions). What's more: if we use an IIFE, we can leverage a closure to protect the count
variable from being accessed externally! This prevents any accidental mutations or unwanted side-effects from inadvertently altering the count.
To begin, let's first create an HTML file containing a single button:
<!-- button.html -->
<html>
<body>
<button id='button'>Click me!</button>
<script src='button.js'></script>
</body>
</html>
No surprises here -- just a <button>
tag with ID of 'button'
. We also reference a button.js
file that we're now going to build. Within that file, let's retrieve a reference to that element via its ID, then save that reference to a variable, button
:
// button.js
const button = document.getElementById('button');
Next, we'll add an event listener to button
, and listen for a 'click'
event. Then, we'll pass in an IIFE as the second argument:
// button.js
button.addEventListener('click', (function() {
let count = 0;
return function() {
count = 1 - count;
if (count === 1) {
alert('This alert appears at every other press!');
}
};
})());
First, we declare a local variable, count
, which is initially set to 0. We then return a function from that function. The returned function alternates count
between 0
and 1
and alerts the user count
equals 1
.
What is important to note is that the returned function closes over the count
variable. That is, because a function maintains a reference to its parent's scope, count
is available for the returned function to use! As a result, we immediately invoke a function that returns that function. And since the returned function has access to the internal variable, count
, a private scope is created -- effectively protecting the data!
Containing count
in a closure allows us to retain the data between each click.
We've seen how using an immediately-invoked function expression creates a private scope that protects variables or methods from being accessed. IIFE's ultimately use the returned functions to access private data within the closure. This works out very well: while these returned functions are publicly-accessible, they still maintain privacy for the variables defined within them!
Another great opportunity to use an IFFE is when you want to execute some code without creating extra global variables. However, note that an IIFE is only intended to be invoked once, to create a unique execution context. If you have some code that is expected to be re-used (e.g., a function meant to be executed more than once in the application), declaring the function and then invoking it might be a better option.
All in all, if you simply have a one-time task (e.g., initializing an application), an IIFE is a great way to get something done without polluting the global environment with extra variables. Cleaning up the global namespace decreases the chance of collisions with duplicate variable names, after all.
Previously, we have created objects using the object literal notation. Likewise, we can even write functions that return objects. There is yet another way for us to create objects, and it is the foundation of object-oriented JavaScript: the constructor function. We saw a bit of it back when invoked the Object()
constructor function.
To instantiate (i.e., create) a new object, we use the new
operator to invoke the function:
new SoftwareDeveloper();
The first thing to note above is the use of the new
keyword. Second, note that the name of the constructor function, SoftwareDeveloper()
, is written with the first letter capitalized to visually distinguish it from a regular function.
Keep in mind that even though the function's name starts with a capital, that doesn't automatically make this a constructor function (i.e., though developers name constructor functions in CamelCase by convention, it is not enforced by the language). What does make SoftwareDeveloper()
a constructor function are:
new
operator to invoke the functionThis is what the internals of a constructor function looks like:
function SoftwareDeveloper(language) {
this.favoriteLanguage = language;
}
This might seem a bit different than the functions you've written up to this point, so let's break it down!
First, rather than declaring local variables, constructor functions persist data with the this
keyword. The above function will add a favoriteLanguage
property to any object that it creates, and assigns it a default value of 'JavaScript'
. Don't worry too much about this
in a constructor function for now; just know that this
refers to the new object that was created by using the new
keyword in front of the constructor function.
One last thing that might seem unusual is that this function doesn't seem to return anything! Constructor functions in JavaScript should not have an explicit return value (i.e., there should not be return
statement).
As we've seen above, let's use the new
operator to create a new object:
let developer = new SoftwareDeveloper('JavaScript');
developer;
SoftwareDeveloper { favoriteLanguage: 'JavaScript' }
We've saved the return value of this invocation to the variable developer
. Observe that the constructor function name precedes the object itself ( before the {...}
)
Let's write out an other developer object manually:
let otherDeveloper = { favoriteLanguage: 'JavaScript' };
otherDeveloper
{ favoriteLanguage: 'JavaScript' }
Observe, that no name is printed before {...}
We can directly access the constractor function via .constructor
attribute:
developer.constructor;
[Function: SoftwareDeveloper]
The manually created object has the default Object()
constructor function:
otherDeveloper.constructor;
[Function: Object]
What's more: we can even use the same constructor function to create as many objects as we'd like!
Let's invoke the same SoftwareDeveloper()
constructor two more times to instantiate two additional objects: engineer
and programmer
.
let engineer = new SoftwareDeveloper('Python');
let programmer = new SoftwareDeveloper('Julia');
console.log(engineer);
// SoftwareDeveloper { favoriteLanguage: 'Python' }
console.log(programmer);
// SoftwareDeveloper { favoriteLanguage: 'Julia' }
SoftwareDeveloper { favoriteLanguage: 'Python' } SoftwareDeveloper { favoriteLanguage: 'Julia' }
Just like regular functions, one benefit of using constructor functions is that they can also accept arguments. Let's update the constructor above to accept a single argument, and assign the name
property to it:
function SoftwareDeveloper(name) {
this.favoriteLanguage = 'JavaScript';
this.name = name;
this.introduce = function (){
console.log(`Hi, my name is ${this.name} and I like ${this.favoriteLanguage}`)
}
}
In the updated SoftwareDeveloper()
function, whatever value is passed into the function will be the value of the object's name property.
let instructor = new SoftwareDeveloper('Andrew');
instructor;
SoftwareDeveloper { favoriteLanguage: 'JavaScript', name: 'Andrew', introduce: [Function] }
As we've seen above, we can create different objects using the same constructor. Let's call the same constructor function but pass a different argument this time:
let teacher = new SoftwareDeveloper('Richard');
teacher;
SoftwareDeveloper { favoriteLanguage: 'JavaScript', name: 'Richard', introduce: [Function] }
Just to recap: above, we passed the string 'Richard'
into the SoftwareDeveloper()
constructor function, then instantiated a new object. 'Richard'
then became the value of the name
property in the teacher
object.
teacher.introduce();
Hi, my name is Richard and I like JavaScript
What happens if you inadvertently invoke a constructor function without using the new
operator?
let coder = SoftwareDeveloper('David');
console.log(coder);
undefined
Without using the new
operator, no object was created. The function was invoked just like any other regular function. Since the function doesn't return anything (except undefined
, which all functions return by default), the coder
variable ended up being assigned to undefined
.
One more thing to note: since this function was invoked as a regular function, the value of this
is also drastically different.
instanceof
)¶What if we want to see if an object was created with a constructor function in the first place? We can use the instanceof
(which returns a boolean
) to give us some insigh.
typeof teacher;
'object'
teacher instanceof SoftwareDeveloper;
true
instanceof
and the Prototype Chain 💡¶In the above example, instanceof
confirmed that a specific constructor function did in fact create a specific object. We know this because we directly instantiated the teacher
object after invoking the SoftwareDeveloper()
constructor function.
Many times, however, it's a bit more complex: the instanceof
operator actually tests whether or not that constructor appears in the prototype chain of an object. This means that we can't always check exactly which constructor created that object, but it does give us insight as to what other properties and methods an object may have access to.
Further Research
this
Keyword¶In the previous section, we saw this
right inside a constructor function. Here's another example:
function Cat(name) {
this.name = name;
this.sayName = function () {
console.log(`Meow! My name is ${this.name}`);
};
}
const bailey = new Cat('Bailey');
In the above Cat()
constructor, the function that sayName
references this.name
. Earlier, we saw this
used in methods. But in Cat()
's case, what exactly does this
refer to?
As it turns out, when invoking a constructor function with the new operator, this
gets set to the newly-created object! Let's check out what the new bailey object looks like:
{
name: 'Bailey',
sayName: function () {
console.log(`Meow! My name is ${this.name}`);
}
}
In the snippet above, notice that this
is outside a constructor function (i.e., in a method). As we saw earlier, when you say this
in a method, what you're really saying is "this object" or "the object at hand." As a result, the sayName()
method can use this to access the name property of that object! This makes the following method call possible:
bailey.sayName();
// 'Meow! My name is Bailey'
this
Assigned?¶A common misconception is that this
refers to the object where it is defined. This is not the case!
The value of this
is actually not assigned to anything until an object calls the method where this
is used. In other words, the value assigned to this
is based on the object that invokes the method where this
is defined. Let's look at an example:
const dog = {
bark: function () {
console.log('Woof!');
},
barkTwice: function () {
this.bark();
this.bark();
}
};
Let's go ahead and invoke both of dog's methods:
dog.bark();
// Woof!
dog.barkTwice();
// Woof!
// Woof!
We know that when we call dog.bark()
(or dog.barkTwice()
) a variable this
gets set. Since this
can access the object it was called on, barkTwice
can use this
to access the dog
object, which contains the bark
method.
But what if we just wrote bark()
instead of this.bark()
in barkTwice
? The function would have first looked for a local variable named bark
in the scope of barkTwice
. If bark
isn't found, it would have looked further up the scope chain.
To tie things all together: this.bark()
tells barkTwice
to look at dog
-- the object that the method was called on -- to find bark
.
this
Get Set To?¶At this point, we've seen this
in many different contexts, such as within a method, or referenced by a constructor function. Let's now organize our thoughts and bring it all together!
There are four ways to call functions, and each way sets this
differently.
calling a constructor function with the new
keyword sets this
to a newly-created object. Recall that creating an instance of Cat
earlier had set this to the new bailey
object.
calling a function that belongs to an object (i.e., a method) sets this
to the object itself. Recall that earlier, the dog object's barkTwice()
method was able to access properties of dog itself.
calling a function on its own (i.e., simply invoking a regular function) will set this
to window
, which is the global object if the host environment is the browser.
function funFunction() {
return this;
}
funFunction();
// (returns the global object, `window`)
apply()
, and with call()
. Both methods share quite a few similarities, and they each allow us to specify how we want to set this
. See it in next section.Further Research
this
operator on MDNthis
¶JavaScript provides three methods that allow us to set the value of this
for a given function:
call()
invokes the function and has arguments passed in individually, separated by commas.apply()
is similar to call()
, it invokes the function just the same, but arguments are passed in as an array.bind()
returns a new function with this bound to a specific object, allowing us to call it as a regular function.call()
and apply()
¶call()
and apply()
are methods directly invoked onto a function. We first pass into it a single value to set as the value of this
. Then, for call()
we pass in any of the receiving function's arguments one-by-one, separated by commas; or for apply()
the receiving function's arguments in an array.
Consider the following function, multiply()
, which simply returns the product of its two arguments:
function multiply(n1, n2) {
return n1 * n2;
}
window = this;
Object [global] { DTRACE_NET_SERVER_CONNECTION: [Function], DTRACE_NET_STREAM_END: [Function], DTRACE_HTTP_SERVER_REQUEST: [Function], DTRACE_HTTP_SERVER_RESPONSE: [Function], DTRACE_HTTP_CLIENT_REQUEST: [Function], DTRACE_HTTP_CLIENT_RESPONSE: [Function], COUNTER_NET_SERVER_CONNECTION: [Function], COUNTER_NET_SERVER_CONNECTION_CLOSE: [Function], COUNTER_HTTP_SERVER_REQUEST: [Function], COUNTER_HTTP_SERVER_RESPONSE: [Function], COUNTER_HTTP_CLIENT_REQUEST: [Function], COUNTER_HTTP_CLIENT_RESPONSE: [Function], global: [Circular], process: process { title: 'Jupyter Learning', version: 'v10.13.0', versions: { http_parser: '2.8.0', node: '10.13.0', v8: '6.8.275.32-node.36', uv: '1.23.2', zlib: '1.2.11', ares: '1.14.0', modules: '64', nghttp2: '1.34.0', napi: '3', openssl: '1.1.0i', icu: '62.1', unicode: '11.0', cldr: '33.1', tz: '2018e' }, arch: 'x64', platform: 'win32', release: { name: 'node', lts: 'Dubnium', sourceUrl: 'https://nodejs.org/download/release/v10.13.0/node-v10.13.0.tar.gz', headersUrl: 'https://nodejs.org/download/release/v10.13.0/node-v10.13.0-headers.tar.gz', libUrl: 'https://nodejs.org/download/release/v10.13.0/win-x64/node.lib' }, argv: [ 'C:\\ProgramData\\Anaconda3\\node.exe' ], execArgv: [ '--eval', '(function() {\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2018, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* eslint-disable no-unused-vars */\nvar console = require("console");\nvar stream = require("stream");\nvar util = require("util");\nvar vm = require("vm");\n/* eslint-enable no-unused-vars */\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global console */\n/* global stream */\n/* global util */\n\n/* global log */\n/* global Display */\n\nfunction Stdout(id, opt) {\n stream.Transform.call(this, opt);\n\n this._id = id;\n}\n\nStdout.prototype = Object.create(stream.Transform.prototype);\n\nStdout.prototype._transform = function(data, encoding, callback) {\n var response = {\n id: this._id,\n stdout: data.toString(),\n };\n log("STDOUT:", response);\n process.send(response);\n this.push(data);\n callback();\n};\n\nfunction Stderr(id, opt) {\n stream.Transform.call(this, opt);\n\n this._id = id;\n}\n\nStderr.prototype = Object.create(stream.Transform.prototype);\n\nStderr.prototype._transform = function(data, encoding, callback) {\n var response = {\n id: this._id,\n stderr: data.toString(),\n };\n log("STDERR:", response);\n process.send(response);\n this.push(data);\n callback();\n};\n\nfunction Context(requester, id) {\n this.requester = requester;\n this.id = id;\n\n this.stdout = new Stdout(this.id);\n this.stderr = new Stderr(this.id);\n this.console = new console.Console(this.stdout, this.stderr);\n\n this._capturedStdout = null;\n this._capturedStderr = null;\n this._capturedConsole = null;\n\n this._async = false;\n this._done = false;\n\n // `$$` provides an interface for users to access the execution context\n this.$$ = Object.create(null);\n\n this.$$.async = (function async(value) {\n this._async = (arguments.length === 0) ? true : !!value;\n return this._async;\n }).bind(this);\n\n this.$$.done = (function done(result) {\n this.send((arguments.length === 0) ? {\n end: true,\n } : {\n mime: toMime(result),\n end: true,\n });\n }).bind(this);\n\n this.$$.sendResult = resolvePromise.call(this,\n function sendResult(result, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: toMime(result),\n end: !keepAlive,\n });\n }\n );\n\n this.$$.sendError = resolvePromise.call(this,\n function sendError(error, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n error: formatError(error),\n end: !keepAlive,\n });\n }\n );\n\n this.$$.mime = resolvePromise.call(this,\n function sendMime(mimeBundle, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: mimeBundle,\n end: !keepAlive,\n });\n }\n );\n\n this.$$.text = resolvePromise.call(this,\n function sendText(text, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "text/plain": text,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.html = resolvePromise.call(this,\n function sendHtml(html, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "text/html": html,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.svg = resolvePromise.call(this,\n function sendSvg(svg, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/svg+xml": svg,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.png = resolvePromise.call(this,\n function sendPng(png, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/png": png,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.jpeg = resolvePromise.call(this,\n function sendJpeg(jpeg, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/jpeg": jpeg,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.json = resolvePromise.call(this,\n function sendJson(json, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "application/json": json,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.input = (function input(options, callback) {\n this.$$.async();\n\n var inputRequest = {\n input: options,\n };\n\n var inputCallback;\n if (typeof callback === "function") {\n inputCallback = function inputCallback(error, reply) {\n callback(error, reply.input);\n };\n }\n\n var promise = this.requester.send(this, inputRequest, inputCallback);\n if (promise) {\n return promise.then(function(reply) { return reply.input; });\n }\n }).bind(this);\n\n this.$$.display = (function createDisplay(id) {\n return (arguments.length === 0) ?\n new Display(this.id) :\n new Display(this.id, id);\n }).bind(this);\n\n this.$$.clear = (function clear(options) {\n this.send({\n request: {\n clear: options || {},\n },\n });\n }).bind(this);\n\n function isPromise(output) {\n if (!global.Promise || typeof global.Promise !== "function") {\n return false;\n }\n return output instanceof global.Promise;\n }\n\n function resolvePromise(outputHandler) {\n return function(output, keepAlive) {\n if (isPromise(output)) {\n this.$$.async();\n\n output.then(function(resolvedOutput) {\n outputHandler.call(this, resolvedOutput, keepAlive);\n }.bind(this)).catch(function(error) {\n this.send({\n error: formatError(error),\n end: true,\n });\n }.bind(this));\n\n return;\n }\n\n outputHandler.apply(this, arguments);\n }.bind(this);\n }\n}\n\nContext.prototype.send = function send(message) {\n message.id = this.id;\n\n if (this._done) {\n log("SEND: DROPPED:", message);\n return;\n }\n\n if (message.end) {\n this._done = true;\n this._async = false;\n }\n\n log("SEND:", message);\n\n process.send(message);\n};\n\nContext.prototype.captureGlobalContext = function captureGlobalContext() {\n this._capturedStdout = process.stdout;\n this._capturedStderr = process.stderr;\n this._capturedConsole = console;\n\n this.stdout.pipe(this._capturedStdout);\n this.stderr.pipe(this._capturedStderr);\n this.console.Console = this._capturedConsole.Console;\n\n delete process.stdout;\n process.stdout = this.stdout;\n\n delete process.stderr;\n process.stderr = this.stderr;\n\n delete global.console;\n global.console = this.console;\n\n delete global.$$;\n global.$$ = this.$$;\n\n if (typeof global.$$mimer$$ !== "function") {\n global.$$mimer$$ = defaultMimer;\n }\n\n delete global.$$mime$$;\n Object.defineProperty(global, "$$mime$$", {\n set: this.$$.mime,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$html$$;\n Object.defineProperty(global, "$$html$$", {\n set: this.$$.html,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$svg$$;\n Object.defineProperty(global, "$$svg$$", {\n set: this.$$.svg,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$png$$;\n Object.defineProperty(global, "$$png$$", {\n set: this.$$.png,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$jpeg$$;\n Object.defineProperty(global, "$$jpeg$$", {\n set: this.$$.jpeg,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$async$$;\n Object.defineProperty(global, "$$async$$", {\n get: (function() {\n return this._async;\n }).bind(this),\n set: (function(value) {\n this._async = !!value;\n }).bind(this),\n configurable: true,\n enumerable: false,\n });\n\n global.$$done$$ = this.$$.done.bind(this);\n\n if (!global.hasOwnProperty("$$defaultMimer$$")) {\n Object.defineProperty(global, "$$defaultMimer$$", {\n value: defaultMimer,\n configurable: false,\n writable: false,\n enumerable: false,\n });\n }\n};\n\nContext.prototype.releaseGlobalContext = function releaseGlobalContext() {\n if (process.stdout === this.stdout) {\n this.stdout.unpipe();\n\n delete process.stdout;\n process.stdout = this._capturedStdout;\n\n this._capturedStdout = null;\n }\n\n if (process.stderr === this.stderr) {\n this.stderr.unpipe();\n\n delete process.stderr;\n process.stderr = this._capturedStderr;\n\n this._capturedStderr = null;\n }\n\n if (global.console === this.console) {\n delete global.console;\n global.console = this._capturedConsole;\n\n this._capturedConsole = null;\n }\n};\n\nfunction formatError(error) {\n return {\n ename: (error && error.name) ?\n error.name : typeof error,\n evalue: (error && error.message) ?\n error.message : util.inspect(error),\n traceback: (error && error.stack) ?\n error.stack.split("\\n") : "",\n };\n}\n\nfunction toMime(result) {\n var mimer = (typeof global.$$mimer$$ === "function") ?\n global.$$mimer$$ :\n defaultMimer;\n return mimer(result);\n}\n\nfunction defaultMimer(result) { // eslint-disable-line complexity\n if (typeof result === "undefined") {\n return {\n "text/plain": "undefined"\n };\n }\n\n if (result === null) {\n return {\n "text/plain": "null"\n };\n }\n\n var mime;\n if (result._toMime) {\n try {\n mime = result._toMime();\n } catch (error) {}\n }\n if (typeof mime !== "object") {\n mime = {};\n }\n\n if (!("text/plain" in mime)) {\n try {\n mime["text/plain"] = util.inspect(result);\n } catch (error) {}\n }\n\n if (result._toHtml && !("text/html" in mime)) {\n try {\n mime["text/html"] = result._toHtml();\n } catch (error) {}\n }\n\n if (result._toSvg && !("image/svg+xml" in mime)) {\n try {\n mime["image/svg+xml"] = result._toSvg();\n } catch (error) {}\n }\n\n if (result._toPng && !("image/png" in mime)) {\n try {\n mime["image/png"] = result._toPng();\n } catch (error) {}\n }\n\n if (result._toJpeg && !("image/jpeg" in mime)) {\n try {\n mime["image/jpeg"] = result._toJpeg();\n } catch (error) {}\n }\n\n return mime;\n}\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2017, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\nfunction Display(context_id, display_id) { // eslint-disable-line no-unused-vars\n var send;\n\n this.mime = function mime(mimeBundle) {\n send(mimeBundle);\n };\n\n this.text = function text(text) {\n send({"text/plain": text});\n };\n\n this.html = function html(html) {\n send({"text/html": html});\n };\n\n this.svg = function svg(svg) {\n send({"image/svg+xml": svg});\n };\n\n this.png = function png(png) {\n send({"image/png": png});\n };\n\n this.jpeg = function jpeg(jpeg) {\n send({"image/jpeg": jpeg});\n };\n\n this.json = function json(json) {\n send({"application/json": json});\n };\n\n this.close = function close() {\n process.send({\n id: context_id,\n display: {\n close: display_id,\n },\n });\n };\n\n if (arguments.length < 2) {\n // case: without a display_id\n send = function send(mime) {\n process.send({\n id: context_id,\n display: {\n mime: mime,\n },\n });\n };\n } else {\n // case: with a display_id\n send = function send(mime) {\n process.send({\n id: context_id,\n display: {\n display_id: display_id,\n mime: mime,\n },\n });\n };\n\n // open the display_id\n process.send({\n id: context_id,\n display: {\n open: display_id,\n },\n });\n }\n}\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2017, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global Promise */\n\nfunction Requester() {\n // id for next request\n this.id = 0;\n\n // callback associated with a request (indexed by id)\n this.callbacks = {};\n\n // the Promise resolve callback associated with a request (indexed by id)\n this.resolves = {};\n\n // the Promise reject callback associated with a request (indexed by id)\n this.rejects = {};\n\n // the string to be returned to a request (indexed by id)\n this.responses = {};\n}\n\n// send a request\nRequester.prototype.send = function send(context, request, callback) {\n var id = this.id++;\n\n if (callback) {\n this.callbacks[id] = callback;\n }\n\n var promise;\n if (global.Promise) {\n promise = new Promise(function(resolve, reject) {\n if (!this.responses.hasOwnProperty(id)) {\n this.resolves[id] = resolve;\n this.rejects[id] = reject;\n return;\n }\n\n var response = this.responses[id];\n delete this.responses[id];\n resolve(response);\n }.bind(this));\n }\n\n request.id = id;\n\n context.send({\n request: request,\n });\n\n return promise;\n};\n\n// pass reply to the callbacks associated with a request\nRequester.prototype.receive = function receive(id, reply) {\n var callback = this.callbacks[id];\n if (callback) {\n delete this.callbacks[id];\n callback(null, reply);\n }\n\n var resolve = this.resolves[id];\n if (resolve) {\n delete this.resolves[id];\n delete this.rejects[id];\n resolve(reply);\n }\n};\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global util */\n/* global vm */\n\n/* global Context */\n/* global defaultMimer */\n/* global Requester */\n\n// Shared variables\nvar DEBUG = !!process.env.DEBUG;\nvar log;\nvar requester;\nvar initialContext;\n\n// Init IPC server\ninit();\n\nreturn;\n\nfunction init() {\n // Setup logger\n log = DEBUG ?\n function log() {\n process.send({\n log: "SERVER: " + util.format.apply(this, arguments),\n });\n } :\n function noop() {};\n\n // Create instance to send requests\n requester = new Requester();\n\n // Capture the initial context\n // (id left undefined to indicate this is the initial context)\n initialContext = new Context(requester);\n initialContext.captureGlobalContext();\n\n Object.defineProperty(global, "$$defaultMimer$$", {\n value: defaultMimer,\n configurable: false,\n writable: false,\n enumerable: false,\n });\n\n process.on("message", onMessage.bind(this));\n\n process.on("uncaughtException", onUncaughtException.bind(this));\n\n process.send({\n status: "online",\n });\n}\n\nfunction onUncaughtException(error) {\n log("UNCAUGHTEXCEPTION:", error.stack);\n process.send({\n stderr: error.stack.toString(),\n });\n}\n\nfunction onMessage(message) {\n log("RECEIVED:", message);\n\n var action = message[0];\n var code = message[1];\n var id = message[2];\n\n initialContext.releaseGlobalContext();\n var context = new Context(requester, id);\n context.captureGlobalContext();\n\n try {\n if (action === "getAllPropertyNames") {\n onNameRequest(code, context);\n } else if (action === "inspect") {\n onInspectRequest(code, context);\n } else if (action === "run") {\n onRunRequest(code, context);\n } else if (action === "reply") {\n onReply(message);\n } else {\n throw new Error("NEL: Unhandled action: " + action);\n }\n } catch (error) {\n context.$$.sendError(error);\n }\n\n context.releaseGlobalContext();\n initialContext.captureGlobalContext();\n initialContext._done = false;\n}\n\nfunction onReply(message) {\n var reply = message[1];\n var id = message[3];\n requester.receive(id, reply);\n}\n\nfunction onNameRequest(code, context) {\n var message = {\n id: context.id,\n names: getAllPropertyNames(run(code)),\n end: true,\n };\n context.send(message);\n}\n\nfunction onInspectRequest(code, context) {\n var message = {\n id: context.id,\n inspection: inspect(run(code)),\n end: true,\n };\n context.send(message);\n}\n\nfunction onRunRequest(code, context) {\n var result = run(code);\n\n // If a result has already been sent, do not send this result.\n if (context._done) {\n return;\n }\n\n // If the result is a Promise, send the result fulfilled by the promise\n if (isPromise(result)) {\n context.$$.sendResult(result);\n return;\n }\n\n // If async mode has been enabled (and the result is not a Promise),\n // do not send this result.\n if (context._async) {\n return;\n }\n\n // If no result has been sent yet and async mode has not been enabled,\n // send this result.\n context.$$.sendResult(result);\n\n return;\n\n function isPromise(output) {\n if (!global.Promise || typeof global.Promise !== "function") {\n return false;\n }\n return output instanceof global.Promise;\n }\n}\n\nfunction getAllPropertyNames(object) {\n var propertyList = [];\n\n if (object === undefined) {\n return [];\n }\n\n if (object === null) {\n return [];\n }\n\n var prototype;\n if (typeof object === "boolean") {\n prototype = Boolean.prototype;\n } else if (typeof object === "number") {\n prototype = Number.prototype;\n } else if (typeof object === "string") {\n prototype = String.prototype;\n } else {\n prototype = object;\n }\n\n var prototypeList = [prototype];\n\n function pushToPropertyList(e) {\n if (propertyList.indexOf(e) === -1) {\n propertyList.push(e);\n }\n }\n\n while (prototype) {\n var names = Object.getOwnPropertyNames(prototype).sort();\n names.forEach(pushToPropertyList);\n\n prototype = Object.getPrototypeOf(prototype);\n if (prototype === null) {\n break;\n }\n\n if (prototypeList.indexOf(prototype) === -1) {\n prototypeList.push(prototype);\n }\n }\n\n return propertyList;\n}\n\nfunction inspect(object) {\n if (object === undefined) {\n return {\n string: "undefined",\n type: "Undefined",\n };\n }\n\n if (object === null) {\n return {\n string: "null",\n type: "Null",\n };\n }\n\n if (typeof object === "boolean") {\n return {\n string: object ? "true" : "false",\n type: "Boolean",\n constructorList: ["Boolean", "Object"],\n };\n }\n\n if (typeof object === "number") {\n return {\n string: util.inspect(object),\n type: "Number",\n constructorList: ["Number", "Object"],\n };\n }\n\n if (typeof object === "string") {\n return {\n string: object,\n type: "String",\n constructorList: ["String", "Object"],\n length: object.length,\n };\n }\n\n if (typeof object === "function") {\n return {\n string: object.toString(),\n type: "Function",\n constructorList: ["Function", "Object"],\n length: object.length,\n };\n }\n\n var constructorList = getConstructorList(object);\n var result = {\n string: toString(object),\n type: constructorList[0] || "",\n constructorList: constructorList,\n };\n\n if ("length" in object) {\n result.length = object.length;\n }\n\n return result;\n\n function toString(object) {\n try {\n return util.inspect(object.valueOf());\n } catch (e) {\n return util.inspect(object);\n }\n }\n\n function getConstructorList(object) {\n var constructorList = [];\n\n for (\n var prototype = Object.getPrototypeOf(object);\n prototype && prototype.constructor;\n prototype = Object.getPrototypeOf(prototype)\n ) {\n constructorList.push(prototype.constructor.name);\n }\n\n return constructorList;\n }\n}\n\nfunction run(code) {\n return vm.runInThisContext(code);\n}\n\n})();' ], env: { ALLUSERSPROFILE: 'C:\\ProgramData', APPDATA: 'C:\\Users\\ruszk\\AppData\\Roaming', COMMONPROGRAMFILES: 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', COMMONPROGRAMW6432: 'C:\\Program Files\\Common Files', COMPUTERNAME: 'DESKTOP-KIU7VUB', COMSPEC: 'C:\\Windows\\system32\\cmd.exe', CONDA_PREFIX: 'C:\\ProgramData\\Anaconda3', DRIVERDATA: 'C:\\Windows\\System32\\Drivers\\DriverData', HOMEDRIVE: 'C:', HOMEPATH: '\\Users\\ruszk', IPY_INTERRUPT_EVENT: '1468', JPY_INTERRUPT_EVENT: '1468', JPY_PARENT_PID: '2172', LOCALAPPDATA: 'C:\\Users\\ruszk\\AppData\\Local', LOGONSERVER: '\\\\DESKTOP-KIU7VUB', NUMBER_OF_PROCESSORS: '4', ONEDRIVE: 'C:\\Users\\ruszk\\OneDrive', ONEDRIVECONSUMER: 'C:\\Users\\ruszk\\OneDrive', OS: 'Windows_NT', PATH: 'C:\\ProgramData\\Anaconda3;C:\\ProgramData\\Anaconda3\\Library\\mingw-w64\\bin;C:\\ProgramData\\Anaconda3\\Library\\usr\\bin;C:\\ProgramData\\Anaconda3\\Library\\bin;C:\\ProgramData\\Anaconda3\\Scripts;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\;C:\\Program Files\\Git\\cmd;C:\\Program Files\\nodejs\\;C:\\Program Files\\Microsoft VS Code\\bin;C:\\Program Files\\PuTTY\\;C:\\Users\\ruszk\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\ruszk\\AppData\\Roaming\\npm', PATHEXT: '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', PROCESSOR_ARCHITECTURE: 'AMD64', PROCESSOR_IDENTIFIER: 'Intel64 Family 6 Model 69 Stepping 1, GenuineIntel', PROCESSOR_LEVEL: '6', PROCESSOR_REVISION: '4501', PROGRAMDATA: 'C:\\ProgramData', PROGRAMFILES: 'C:\\Program Files', 'PROGRAMFILES(X86)': 'C:\\Program Files (x86)', PROGRAMW6432: 'C:\\Program Files', PROMPT: '$P$G', PSMODULEPATH: 'C:\\Program Files\\WindowsPowerShell\\Modules;C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules', PUBLIC: 'C:\\Users\\Public', SESSIONNAME: 'Console', SYSTEMDRIVE: 'C:', SYSTEMROOT: 'C:\\Windows', TEMP: 'C:\\Users\\ruszk\\AppData\\Local\\Temp', TMP: 'C:\\Users\\ruszk\\AppData\\Local\\Temp', USERDOMAIN: 'DESKTOP-KIU7VUB', USERDOMAIN_ROAMINGPROFILE: 'DESKTOP-KIU7VUB', USERNAME: 'ruszk', USERPROFILE: 'C:\\Users\\ruszk', WINDIR: 'C:\\Windows' }, pid: 9828, features: { debug: false, uv: true, ipv6: true, tls_alpn: true, tls_sni: true, tls_ocsp: true, tls: true }, ppid: 7208, _eval: '(function() {\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2018, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* eslint-disable no-unused-vars */\nvar console = require("console");\nvar stream = require("stream");\nvar util = require("util");\nvar vm = require("vm");\n/* eslint-enable no-unused-vars */\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global console */\n/* global stream */\n/* global util */\n\n/* global log */\n/* global Display */\n\nfunction Stdout(id, opt) {\n stream.Transform.call(this, opt);\n\n this._id = id;\n}\n\nStdout.prototype = Object.create(stream.Transform.prototype);\n\nStdout.prototype._transform = function(data, encoding, callback) {\n var response = {\n id: this._id,\n stdout: data.toString(),\n };\n log("STDOUT:", response);\n process.send(response);\n this.push(data);\n callback();\n};\n\nfunction Stderr(id, opt) {\n stream.Transform.call(this, opt);\n\n this._id = id;\n}\n\nStderr.prototype = Object.create(stream.Transform.prototype);\n\nStderr.prototype._transform = function(data, encoding, callback) {\n var response = {\n id: this._id,\n stderr: data.toString(),\n };\n log("STDERR:", response);\n process.send(response);\n this.push(data);\n callback();\n};\n\nfunction Context(requester, id) {\n this.requester = requester;\n this.id = id;\n\n this.stdout = new Stdout(this.id);\n this.stderr = new Stderr(this.id);\n this.console = new console.Console(this.stdout, this.stderr);\n\n this._capturedStdout = null;\n this._capturedStderr = null;\n this._capturedConsole = null;\n\n this._async = false;\n this._done = false;\n\n // `$$` provides an interface for users to access the execution context\n this.$$ = Object.create(null);\n\n this.$$.async = (function async(value) {\n this._async = (arguments.length === 0) ? true : !!value;\n return this._async;\n }).bind(this);\n\n this.$$.done = (function done(result) {\n this.send((arguments.length === 0) ? {\n end: true,\n } : {\n mime: toMime(result),\n end: true,\n });\n }).bind(this);\n\n this.$$.sendResult = resolvePromise.call(this,\n function sendResult(result, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: toMime(result),\n end: !keepAlive,\n });\n }\n );\n\n this.$$.sendError = resolvePromise.call(this,\n function sendError(error, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n error: formatError(error),\n end: !keepAlive,\n });\n }\n );\n\n this.$$.mime = resolvePromise.call(this,\n function sendMime(mimeBundle, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: mimeBundle,\n end: !keepAlive,\n });\n }\n );\n\n this.$$.text = resolvePromise.call(this,\n function sendText(text, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "text/plain": text,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.html = resolvePromise.call(this,\n function sendHtml(html, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "text/html": html,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.svg = resolvePromise.call(this,\n function sendSvg(svg, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/svg+xml": svg,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.png = resolvePromise.call(this,\n function sendPng(png, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/png": png,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.jpeg = resolvePromise.call(this,\n function sendJpeg(jpeg, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "image/jpeg": jpeg,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.json = resolvePromise.call(this,\n function sendJson(json, keepAlive) {\n if (keepAlive) this.$$.async();\n\n this.send({\n mime: {\n "application/json": json,\n },\n end: !keepAlive,\n });\n }\n );\n\n this.$$.input = (function input(options, callback) {\n this.$$.async();\n\n var inputRequest = {\n input: options,\n };\n\n var inputCallback;\n if (typeof callback === "function") {\n inputCallback = function inputCallback(error, reply) {\n callback(error, reply.input);\n };\n }\n\n var promise = this.requester.send(this, inputRequest, inputCallback);\n if (promise) {\n return promise.then(function(reply) { return reply.input; });\n }\n }).bind(this);\n\n this.$$.display = (function createDisplay(id) {\n return (arguments.length === 0) ?\n new Display(this.id) :\n new Display(this.id, id);\n }).bind(this);\n\n this.$$.clear = (function clear(options) {\n this.send({\n request: {\n clear: options || {},\n },\n });\n }).bind(this);\n\n function isPromise(output) {\n if (!global.Promise || typeof global.Promise !== "function") {\n return false;\n }\n return output instanceof global.Promise;\n }\n\n function resolvePromise(outputHandler) {\n return function(output, keepAlive) {\n if (isPromise(output)) {\n this.$$.async();\n\n output.then(function(resolvedOutput) {\n outputHandler.call(this, resolvedOutput, keepAlive);\n }.bind(this)).catch(function(error) {\n this.send({\n error: formatError(error),\n end: true,\n });\n }.bind(this));\n\n return;\n }\n\n outputHandler.apply(this, arguments);\n }.bind(this);\n }\n}\n\nContext.prototype.send = function send(message) {\n message.id = this.id;\n\n if (this._done) {\n log("SEND: DROPPED:", message);\n return;\n }\n\n if (message.end) {\n this._done = true;\n this._async = false;\n }\n\n log("SEND:", message);\n\n process.send(message);\n};\n\nContext.prototype.captureGlobalContext = function captureGlobalContext() {\n this._capturedStdout = process.stdout;\n this._capturedStderr = process.stderr;\n this._capturedConsole = console;\n\n this.stdout.pipe(this._capturedStdout);\n this.stderr.pipe(this._capturedStderr);\n this.console.Console = this._capturedConsole.Console;\n\n delete process.stdout;\n process.stdout = this.stdout;\n\n delete process.stderr;\n process.stderr = this.stderr;\n\n delete global.console;\n global.console = this.console;\n\n delete global.$$;\n global.$$ = this.$$;\n\n if (typeof global.$$mimer$$ !== "function") {\n global.$$mimer$$ = defaultMimer;\n }\n\n delete global.$$mime$$;\n Object.defineProperty(global, "$$mime$$", {\n set: this.$$.mime,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$html$$;\n Object.defineProperty(global, "$$html$$", {\n set: this.$$.html,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$svg$$;\n Object.defineProperty(global, "$$svg$$", {\n set: this.$$.svg,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$png$$;\n Object.defineProperty(global, "$$png$$", {\n set: this.$$.png,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$jpeg$$;\n Object.defineProperty(global, "$$jpeg$$", {\n set: this.$$.jpeg,\n configurable: true,\n enumerable: false,\n });\n\n delete global.$$async$$;\n Object.defineProperty(global, "$$async$$", {\n get: (function() {\n return this._async;\n }).bind(this),\n set: (function(value) {\n this._async = !!value;\n }).bind(this),\n configurable: true,\n enumerable: false,\n });\n\n global.$$done$$ = this.$$.done.bind(this);\n\n if (!global.hasOwnProperty("$$defaultMimer$$")) {\n Object.defineProperty(global, "$$defaultMimer$$", {\n value: defaultMimer,\n configurable: false,\n writable: false,\n enumerable: false,\n });\n }\n};\n\nContext.prototype.releaseGlobalContext = function releaseGlobalContext() {\n if (process.stdout === this.stdout) {\n this.stdout.unpipe();\n\n delete process.stdout;\n process.stdout = this._capturedStdout;\n\n this._capturedStdout = null;\n }\n\n if (process.stderr === this.stderr) {\n this.stderr.unpipe();\n\n delete process.stderr;\n process.stderr = this._capturedStderr;\n\n this._capturedStderr = null;\n }\n\n if (global.console === this.console) {\n delete global.console;\n global.console = this._capturedConsole;\n\n this._capturedConsole = null;\n }\n};\n\nfunction formatError(error) {\n return {\n ename: (error && error.name) ?\n error.name : typeof error,\n evalue: (error && error.message) ?\n error.message : util.inspect(error),\n traceback: (error && error.stack) ?\n error.stack.split("\\n") : "",\n };\n}\n\nfunction toMime(result) {\n var mimer = (typeof global.$$mimer$$ === "function") ?\n global.$$mimer$$ :\n defaultMimer;\n return mimer(result);\n}\n\nfunction defaultMimer(result) { // eslint-disable-line complexity\n if (typeof result === "undefined") {\n return {\n "text/plain": "undefined"\n };\n }\n\n if (result === null) {\n return {\n "text/plain": "null"\n };\n }\n\n var mime;\n if (result._toMime) {\n try {\n mime = result._toMime();\n } catch (error) {}\n }\n if (typeof mime !== "object") {\n mime = {};\n }\n\n if (!("text/plain" in mime)) {\n try {\n mime["text/plain"] = util.inspect(result);\n } catch (error) {}\n }\n\n if (result._toHtml && !("text/html" in mime)) {\n try {\n mime["text/html"] = result._toHtml();\n } catch (error) {}\n }\n\n if (result._toSvg && !("image/svg+xml" in mime)) {\n try {\n mime["image/svg+xml"] = result._toSvg();\n } catch (error) {}\n }\n\n if (result._toPng && !("image/png" in mime)) {\n try {\n mime["image/png"] = result._toPng();\n } catch (error) {}\n }\n\n if (result._toJpeg && !("image/jpeg" in mime)) {\n try {\n mime["image/jpeg"] = result._toJpeg();\n } catch (error) {}\n }\n\n return mime;\n}\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2017, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\nfunction Display(context_id, display_id) { // eslint-disable-line no-unused-vars\n var send;\n\n this.mime = function mime(mimeBundle) {\n send(mimeBundle);\n };\n\n this.text = function text(text) {\n send({"text/plain": text});\n };\n\n this.html = function html(html) {\n send({"text/html": html});\n };\n\n this.svg = function svg(svg) {\n send({"image/svg+xml": svg});\n };\n\n this.png = function png(png) {\n send({"image/png": png});\n };\n\n this.jpeg = function jpeg(jpeg) {\n send({"image/jpeg": jpeg});\n };\n\n this.json = function json(json) {\n send({"application/json": json});\n };\n\n this.close = function close() {\n process.send({\n id: context_id,\n display: {\n close: display_id,\n },\n });\n };\n\n if (arguments.length < 2) {\n // case: without a display_id\n send = function send(mime) {\n process.send({\n id: context_id,\n display: {\n mime: mime,\n },\n });\n };\n } else {\n // case: with a display_id\n send = function send(mime) {\n process.send({\n id: context_id,\n display: {\n display_id: display_id,\n mime: mime,\n },\n });\n };\n\n // open the display_id\n process.send({\n id: context_id,\n display: {\n open: display_id,\n },\n });\n }\n}\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2017, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global Promise */\n\nfunction Requester() {\n // id for next request\n this.id = 0;\n\n // callback associated with a request (indexed by id)\n this.callbacks = {};\n\n // the Promise resolve callback associated with a request (indexed by id)\n this.resolves = {};\n\n // the Promise reject callback associated with a request (indexed by id)\n this.rejects = {};\n\n // the string to be returned to a request (indexed by id)\n this.responses = {};\n}\n\n// send a request\nRequester.prototype.send = function send(context, request, callback) {\n var id = this.id++;\n\n if (callback) {\n this.callbacks[id] = callback;\n }\n\n var promise;\n if (global.Promise) {\n promise = new Promise(function(resolve, reject) {\n if (!this.responses.hasOwnProperty(id)) {\n this.resolves[id] = resolve;\n this.rejects[id] = reject;\n return;\n }\n\n var response = this.responses[id];\n delete this.responses[id];\n resolve(response);\n }.bind(this));\n }\n\n request.id = id;\n\n context.send({\n request: request,\n });\n\n return promise;\n};\n\n// pass reply to the callbacks associated with a request\nRequester.prototype.receive = function receive(id, reply) {\n var callback = this.callbacks[id];\n if (callback) {\n delete this.callbacks[id];\n callback(null, reply);\n }\n\n var resolve = this.resolves[id];\n if (resolve) {\n delete this.resolves[id];\n delete this.rejects[id];\n resolve(reply);\n }\n};\n\n/*\n * BSD 3-Clause License\n *\n * Copyright (c) 2015, Nicolas Riesco and others as credited in the AUTHORS file\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * 1. Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n *\n * 2. Redistributions in binary form must reproduce the above copyright notice,\n * this list of conditions and the following disclaimer in the documentation\n * and/or other materials provided with the distribution.\n *\n * 3. Neither the name of the copyright holder nor the names of its contributors\n * may be used to endorse or promote products derived from this software without\n * specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\n * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n * POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n/* global util */\n/* global vm */\n\n/* global Context */\n/* global defaultMimer */\n/* global Requester */\n\n// Shared variables\nvar DEBUG = !!process.env.DEBUG;\nvar log;\nvar requester;\nvar initialContext;\n\n// Init IPC server\ninit();\n\nreturn;\n\nfunction init() {\n // Setup logger\n log = DEBUG ?\n function log() {\n process.send({\n log: "SERVER: " + util.format.apply(this, arguments),\n });\n } :\n function noop() {};\n\n // Create instance to send requests\n requester = new Requester();\n\n // Capture the initial context\n // (id left undefined to indicate this is the initial context)\n initialContext = new Context(requester);\n initialContext.captureGlobalContext();\n\n Object.defineProperty(global, "$$defaultMimer$$", {\n value: defaultMimer,\n configurable: false,\n writable: false,\n enumerable: false,\n });\n\n process.on("message", onMessage.bind(this));\n\n process.on("uncaughtException", onUncaughtException.bind(this));\n\n process.send({\n status: "online",\n });\n}\n\nfunction onUncaughtException(error) {\n log("UNCAUGHTEXCEPTION:", error.stack);\n process.send({\n stderr: error.stack.toString(),\n });\n}\n\nfunction onMessage(message) {\n log("RECEIVED:", message);\n\n var action = message[0];\n var code = message[1];\n var id = message[2];\n\n initialContext.releaseGlobalContext();\n var context = new Context(requester, id);\n context.captureGlobalContext();\n\n try {\n if (action === "getAllPropertyNames") {\n onNameRequest(code, context);\n } else if (action === "inspect") {\n onInspectRequest(code, context);\n } else if (action === "run") {\n onRunRequest(code, context);\n } else if (action === "reply") {\n onReply(message);\n } else {\n throw new Error("NEL: Unhandled action: " + action);\n }\n } catch (error) {\n context.$$.sendError(error);\n }\n\n context.releaseGlobalContext();\n initialContext.captureGlobalContext();\n initialContext._done = false;\n}\n\nfunction onReply(message) {\n var reply = message[1];\n var id = message[3];\n requester.receive(id, reply);\n}\n\nfunction onNameRequest(code, context) {\n var message = {\n id: context.id,\n names: getAllPropertyNames(run(code)),\n end: true,\n };\n context.send(message);\n}\n\nfunction onInspectRequest(code, context) {\n var message = {\n id: context.id,\n inspection: inspect(run(code)),\n end: true,\n };\n context.send(message);\n}\n\nfunction onRunRequest(code, context) {\n var result = run(code);\n\n // If a result has already been sent, do not send this result.\n if (context._done) {\n return;\n }\n\n // If the result is a Promise, send the result fulfilled by the promise\n if (isPromise(result)) {\n context.$$.sendResult(result);\n return;\n }\n\n // If async mode has been enabled (and the result is not a Promise),\n // do not send this result.\n if (context._async) {\n return;\n }\n\n // If no result has been sent yet and async mode has not been enabled,\n // send this result.\n context.$$.sendResult(result);\n\n return;\n\n function isPromise(output) {\n if (!global.Promise || typeof global.Promise !== "function") {\n return false;\n }\n return output instanceof global.Promise;\n }\n}\n\nfunction getAllPropertyNames(object) {\n var propertyList = [];\n\n if (object === undefined) {\n return [];\n }\n\n if (object === null) {\n return [];\n }\n\n var prototype;\n if (typeof object === "boolean") {\n prototype = Boolean.prototype;\n } else if (typeof object === "number") {\n prototype = Number.prototype;\n } else if (typeof object === "string") {\n prototype = String.prototype;\n } else {\n prototype = object;\n }\n\n var prototypeList = [prototype];\n\n function pushToPropertyList(e) {\n if (propertyList.indexOf(e) === -1) {\n propertyList.push(e);\n }\n }\n\n while (prototype) {\n var names = Object.getOwnPropertyNames(prototype).sort();\n names.forEach(pushToPropertyList);\n\n prototype = Object.getPrototypeOf(prototype);\n if (prototype === null) {\n break;\n }\n\n if (prototypeList.indexOf(prototype) === -1) {\n prototypeList.push(prototype);\n }\n }\n\n return propertyList;\n}\n\nfunction inspect(object) {\n if (object === undefined) {\n return {\n string: "undefined",\n type: "Undefined",\n };\n }\n\n if (object === null) {\n return {\n string: "null",\n type: "Null",\n };\n }\n\n if (typeof object === "boolean") {\n return {\n string: object ? "true" : "false",\n type: "Boolean",\n constructorList: ["Boolean", "Object"],\n };\n }\n\n if (typeof object === "number") {\n return {\n string: util.inspect(object),\n type: "Number",\n constructorList: ["Number", "Object"],\n };\n }\n\n if (typeof object === "string") {\n return {\n string: object,\n type: "String",\n constructorList: ["String", "Object"],\n length: object.length,\n };\n }\n\n if (typeof object === "function") {\n return {\n string: object.toString(),\n type: "Function",\n constructorList: ["Function", "Object"],\n length: object.length,\n };\n }\n\n var constructorList = getConstructorList(object);\n var result = {\n string: toString(object),\n type: constructorList[0] || "",\n constructorList: constructorList,\n };\n\n if ("length" in object) {\n result.length = object.length;\n }\n\n return result;\n\n function toString(object) {\n try {\n return util.inspect(object.valueOf());\n } catch (e) {\n return util.inspect(object);\n }\n }\n\n function getConstructorList(object) {\n var constructorList = [];\n\n for (\n var prototype = Object.getPrototypeOf(object);\n prototype && prototype.constructor;\n prototype = Object.getPrototypeOf(prototype)\n ) {\n constructorList.push(prototype.constructor.name);\n }\n\n return constructorList;\n }\n}\n\nfunction run(code) {\n return vm.runInThisContext(code);\n}\n\n})();', execPath: 'C:\\ProgramData\\Anaconda3\\node.exe', debugPort: 9229, _debugProcess: [Function: _debugProcess], _debugEnd: [Function: _debugEnd], _startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier], _stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier], abort: [Function: abort], chdir: [Function: chdir], umask: [Function: umask], _getActiveRequests: [Function: _getActiveRequests], _getActiveHandles: [Function: _getActiveHandles], _kill: [Function: _kill], cwd: [Function: cwd], dlopen: [Function: dlopen], reallyExit: [Function: reallyExit], uptime: [Function: uptime], _rawDebug: [Function], moduleLoadList: [ 'Binding contextify', 'Internal Binding worker', 'NativeModule events', 'NativeModule internal/async_hooks', 'NativeModule internal/errors', 'Binding uv', 'Binding buffer', 'Binding async_wrap', 'Binding config', 'Binding icu', 'NativeModule util', 'NativeModule internal/util/inspect', 'Binding util', 'NativeModule internal/util', 'Binding constants', 'Internal Binding types', 'NativeModule internal/util/types', 'NativeModule internal/validators', 'NativeModule internal/encoding', 'NativeModule buffer', 'NativeModule internal/buffer', 'NativeModule internal/process/per_thread', 'NativeModule internal/process/main_thread_only', 'NativeModule internal/process/stdio', 'NativeModule assert', 'NativeModule internal/assert', 'NativeModule fs', 'NativeModule path', 'NativeModule internal/constants', 'Binding fs', 'NativeModule internal/fs/streams', 'NativeModule internal/fs/utils', 'NativeModule stream', 'NativeModule internal/streams/pipeline', 'NativeModule internal/streams/end-of-stream', 'NativeModule internal/streams/legacy', 'NativeModule _stream_readable', 'NativeModule internal/streams/buffer_list', 'NativeModule internal/streams/destroy', 'NativeModule internal/streams/state', 'NativeModule _stream_writable', 'NativeModule _stream_duplex', 'NativeModule _stream_transform', 'NativeModule _stream_passthrough', 'NativeModule internal/url', 'NativeModule internal/querystring', 'Binding url', 'NativeModule internal/process/warning', 'NativeModule internal/process/next_tick', 'NativeModule internal/process/promises', 'NativeModule internal/fixed_queue', 'Binding performance', 'Binding trace_events', 'NativeModule internal/inspector_async_hook', 'Binding inspector', 'Internal Binding options', 'NativeModule child_process', 'Binding pipe_wrap', 'NativeModule internal/child_process', 'NativeModule net', 'NativeModule internal/net', 'Binding tty_wrap', 'Binding stream_wrap', 'Binding tcp_wrap', 'NativeModule internal/stream_base_commons', 'NativeModule internal/timers', 'NativeModule dgram', 'NativeModule internal/dgram', 'Binding udp_wrap', 'Binding process_wrap', 'NativeModule internal/socket_list', 'Binding spawn_sync', 'Binding http_parser', 'NativeModule _http_common', 'NativeModule internal/freelist', 'NativeModule internal/http', 'NativeModule _http_incoming', 'NativeModule string_decoder', 'Internal Binding string_decoder', 'NativeModule timers', 'Binding timer_wrap', 'NativeModule internal/linkedlist', 'NativeModule internal/modules/cjs/loader', 'NativeModule vm', 'NativeModule internal/modules/cjs/helpers', 'NativeModule console', 'NativeModule internal/fs/sync_write_stream' ], binding: [Function: binding], _linkedBinding: [Function: _linkedBinding], _events: { newListener: [Array], removeListener: [Array], warning: [Function], internalMessage: [Function], message: [Function: bound onMessage], uncaughtException: [Function: bound onUncaughtException] }, _eventsCount: 6, _maxListeners: undefined, _fatalException: [Function], domain: null, _exiting: false, assert: [Function: deprecated], config: { target_defaults: [Object], variables: [Object] }, setUncaughtExceptionCaptureCallback: [Function], hasUncaughtExceptionCaptureCallback: [Function], emitWarning: [Function], nextTick: [Function: nextTick], _tickCallback: [Function: _tickCallback], stdin: [Getter], openStdin: [Function], hrtime: { [Function: hrtime] bigint: [Function] }, cpuUsage: [Function: cpuUsage], memoryUsage: [Function: memoryUsage], exit: [Function], kill: [Function], channel: Pipe { buffering: false, pendingHandle: null, onread: [Function], sockets: [Object] }, _channel: [Getter/Setter], _handleQueue: null, _pendingMessage: null, send: [Function], _send: [Function], connected: true, disconnect: [Function], _disconnect: [Function], argv0: 'C:\\ProgramData\\Anaconda3\\node.exe', allowedNodeEnvironmentFlags: [Getter/Setter], stdout: Transform { _readableState: [ReadableState], readable: true, _events: [Object], _eventsCount: 3, _maxListeners: undefined, _writableState: [WritableState], writable: true, allowHalfOpen: true, _transformState: [Object], _id: 288 }, stderr: Transform { _readableState: [ReadableState], readable: true, _events: [Object], _eventsCount: 3, _maxListeners: undefined, _writableState: [WritableState], writable: true, allowHalfOpen: true, _transformState: [Object], _id: 288 } }, Buffer: { [Function: Buffer] poolSize: 8192, from: [Function: from], of: [Function: of], alloc: [Function: alloc], allocUnsafe: [Function: allocUnsafe], allocUnsafeSlow: [Function: allocUnsafeSlow], isBuffer: [Function: isBuffer], compare: [Function: compare], isEncoding: [Function: isEncoding], concat: [Function: concat], byteLength: [Function: byteLength], [Symbol(kIsEncodingSymbol)]: [Function: isEncoding] }, clearImmediate: [Function: clearImmediate], clearInterval: [Function: clearInterval], clearTimeout: [Function: clearTimeout], setImmediate: { [Function: setImmediate] [Symbol(util.promisify.custom)]: [Function] }, setInterval: [Function: setInterval], setTimeout: { [Function: setTimeout] [Symbol(util.promisify.custom)]: [Function] }, __filename: '[eval]', exports: {}, module: Module { id: '[eval]', exports: {}, parent: undefined, filename: 'C:\\Users\\ruszk\\Google Drive\\Learning\\JavaScript\\[eval]', loaded: false, children: [], paths: [ 'C:\\Users\\ruszk\\Google Drive\\Learning\\JavaScript\\node_modules', 'C:\\Users\\ruszk\\Google Drive\\Learning\\node_modules', 'C:\\Users\\ruszk\\Google Drive\\node_modules', 'C:\\Users\\ruszk\\node_modules', 'C:\\Users\\node_modules', 'C:\\node_modules' ] }, __dirname: '.', require: { [Function: require] resolve: { [Function: resolve] paths: [Function: paths] }, main: undefined, extensions: { '.js': [Function], '.json': [Function], '.node': [Function] }, cache: {} }, '$$mimer$$': [Function: defaultMimer], '$$done$$': [Function: bound bound done], i: 3, signedIn: null, w: 4, now: 2021-02-18T18:16:00.523Z, today: 1613672160597, tomorrow: 1613758560597, todayDate: 2021-02-18T18:16:00.597Z, tomorrowDate: 2021-02-19T18:16:00.597Z, birthday: 1975-01-31T00:00:00.000Z, str: 'This is a sample string', re: /Sample/i, greeting: 'Hello', tip: 8, totalAfterTax: 53.03, opinion: 'This nanodegree is amazing', showMessage: [Function: showMessage], q: 0, my_string: 'Udacity', studentName: 'John', haveEnrolledInCourse: true, haveCompletedTheCourse: false, a: [Function: a], b: [Function: b], weather: 'sunny', isGoing: true, color: 'green', option: 3, month: 12, days: 31, education: 'no high school diploma', salary: 25636, findDayOfWeek: [Function: findDayOfWeek], start: 6, x: 11, out: 'Fizz', add1: [Function: add1], newNum: 5, isThisWorking: [Function: isThisWorking], test: [ 112, 929, 11, 103, 199, 1000, 7, 1, 124, 37, 4, 19, 400, 3775, 299, 136, 209, 148, 169, 299, 106, 109, 20, 58, 139, 59, 103, 1, 139 ], add: [Function: add], divideByTwo: [Function: divideByTwo], sum: 12, average: 6, sayHi: [Function: sayHi], catSays: [Function: catSays], helloCat: [Function: helloCat], favoriteMovie: [Function: displayFavorite], movies: [Function: movies], TestTry: [Function: TestTry], checkValue: [Function: checkValue], traceIt: [Function: traceIt], codedURL: 'difficult%20%26%24%25%40%23%20to%20transmit', donuts: [ 'JELLY DONUT HOLE', 'CHOCOLATE DONUT HOLE', 'GLAZED DONUT HOLE' ], mixedData: [ 'abcd', 1, true, undefined, null, 'all the things' ], arraysInArrays: [ [ 1, 2, 3 ], [ 'Julia', 'James' ], [ true, false, true, false ] ], captain: 'Mal', second: 'Zoe', pilot: 'Wash', companion: 'Inara', mercenary: 'Jayne', mechanic: 'Kaylee', crew: [ 'Inara', 'Jayne', 'Kaylee', 'Mal', 'Wash', 'Zoe', 'Simon', 'River', 'Book' ], doctor: 'Simon', sister: { name: 'Sarah', age: 23, parents: [ 'alice', 'andy' ], siblings: [ 'julia' ], favoriteColor: 'purple', pets: true, paintPicture: [Function: paintPicture] }, shepherd: 'Book', rainbow: [ 'Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Purple' ], fruits: [ 'apples', 'pears', 'cherries', 'bananas', 'peaches', 'oranges' ], vegetables: [ 'carrots', 'peas', 'beans', 'lettuce' ], words: [ 'cat', 'in', 'hat' ], improvedDonuts: [ 'JELLY DONUT HOLE-GLAZED DONUT HOLE donut', 'CHOCOLATE DONUT HOLE-JELLY DONUT HOLE donut', 'GLAZED DONUT HOLE-CHOCOLATE DONUT HOLE donut' ], donutBox: [ [ 'glazed', 'chocolate glazed', 'cinnamon' ], [ 'powdered', 'sprinkled', 'glazed cruller' ], [ 'chocolate cruller', 'Boston creme', 'creme de leche' ] ], row: 3, column: 3, numbers: [ 1, 2, 3 ], tree: { type: 'oak', age: 120, isLeavesOn: false }, auto: { location: 'garage', ignition: 'off', fueled: true }, changeToEight: [Function: changeToEight], setToBlue: [Function: setToBlue], whoThis: [Function: whoThis], trickyish: true, person: 'Richard', richardSaysHi: [Function: richardSaysHi], alertThenReturn: [Function: alertThenReturn], callAndAdd: [Function: callAndAdd], returnsThree: [Function: returnsThree], each: [Function: each], isPositive: [Function: isPositive], logIfOdd: [Function: logIfOdd], length: [Function: length], remember: [Function: remember], myCounter: [Function: myCounter], SoftwareDeveloper: [Function: SoftwareDeveloper], favoriteLanguage: 'JavaScript', name: 'David', introduce: [Function], console: Console { log: [Function: bound log], debug: [Function: bound log], info: [Function: bound log], dirxml: [Function: bound log], warn: [Function: bound warn], error: [Function: bound warn], dir: [Function: bound dir], time: [Function: bound time], timeEnd: [Function: bound timeEnd], timeLog: [Function: bound timeLog], trace: [Function: bound trace], assert: [Function: bound assert], clear: [Function: bound clear], count: [Function: bound count], countReset: [Function: bound countReset], group: [Function: bound group], groupCollapsed: [Function: bound group], groupEnd: [Function: bound groupEnd], table: [Function: bound ], Console: [Function: Console], [Symbol(counts)]: Map {}, [Symbol(kColorMode)]: 'auto' }, '$$': { async: [Function: bound async], done: [Function: bound done], sendResult: [Function: bound ], sendError: [Function: bound ], mime: [Function: bound ], text: [Function: bound ], html: [Function: bound ], svg: [Function: bound ], png: [Function: bound ], jpeg: [Function: bound ], json: [Function: bound ], input: [Function: bound input], display: [Function: bound createDisplay], clear: [Function: bound clear] }, multiply: [Function: multiply], window: [Circular] }
example of call()
multiply.call(window, 3, 4);
12
example of apply()
multiply.apply(window, [3, 4]);
12
Great! Note that the first argument in both call()
and apply()
is still window
(i.e., the object to bind the value of this to).
We can borrow on object's method to be used with a different object:
// object with a method
const mockingbird = {
title: 'To Kill a Mockingbird',
describe: function () {
console.log(`${this.title} is a classic novel`);
}
};
// object without method
const pride = {
title: 'Pride and Prejudice'
};
mockingbird.describe();
// call mockingbird object's describe method for pride object
// both objects has title attribute, necessary for the describe method
mockingbird.describe.call(pride);
mockingbird.describe.apply(pride);
To Kill a Mockingbird is a classic novel Pride and Prejudice is a classic novel Pride and Prejudice is a classic novel
Note that the first argument passed into both call()
and apply()
is the same: pride
. Since the describe()
method doesn't take any arguments, the only difference between mockingbird.describe.call(pride)
and mockingbird.describe.apply(pride)
is just the method! Both approaches produce the same result.
If a function uses this
in the code, similarly to a constructor function, then we can use it on different objects, only have to make sure, that all objects has the attributes used inside the function.
In the next example, the sayHello()
function uses this.name
property, the following cat
and dog
objects both has the name
attribute.
This time, the sayHello()
function also requires a parameter.
function sayHello(message) {
console.log(`${message}, ${this.name}`);
}
const cat = {
name: 'Bailey'
};
const dog = {
name: 'Bernie'
};
sayHello.call(cat, 'Nice to see you');
sayHello.apply(dog, ['Hello']);
Nice to see you, Bailey Hello, Bernie
call()
or apply()
?¶call()
may be limited if you don't know ahead of time the number of arguments that the function needs. In this case, apply()
would be a better option, since it simply takes an array of arguments, then unpacks them to pass along to the function. Keep in mind that the unpacking comes at a minor performance cost, but it shouldn't be much of an issue.
The value of this
has some potential scope issues when callback functions are involved, and things can get a bit tricky. Let's check it out below.
// function takes an other function as parameter
function invokeTwice(cb) {
cb();
cb();
}
// object with method
const canary = {
age: 5,
growOneYear: function () {
this.age += 1;
}
};
First, invoking growOneYear()
works as expected, updating the value of the canary
object's age
property from 5
to 6
:
canary.growOneYear();
canary.age;
6
However, passing canary.growOneYear
method (function) as an argument into invokeTwice()
produces an odd result:
invokeTwice(canary.growOneYear);
canary.age;
6
We kind of expected that the invokeTwice()
will call canary.growOneYear
method 2 times, so the canary.age
will cange from 6
to 8
. But our expectation was unfunded, because using canary.growOneYear
method out of context results in a it gets treated as a normal function, and so this
gets assigned to the global window
object (as we learned before), not to the object canary
.
this
with an Anonymous Closure¶Recall that simply invoking a normal function will set the value of this
to the global object (i.e., window). This is an issue, because we want this to be the canary
object!
So how can we make sure that this
is preserved?
One way to resolve this issue is to use an anonymous closure to close over the canary
object:
invokeTwice(function () {
canary.growOneYear();
});
canary.age;
8
Using this approach, invoking invokeTwice()
still sets the value of this
to window
. However, this
has no effect on the closure; within the anonymous function, the growOneYear()
method will still be directly called onto the canary
object! As a result, the value of canary
's age
property increases from 6
to 8
.
Since this is such a common pattern, JavaScript provides an alternate and less verbose approach: the bind()
method.
this
with bind()
¶Similar to call()
and apply()
, the bind()
method allows us to directly define a value for this
. bind()
is a method that is also called on a function, but unlike call()
or apply()
, which both invoke the function right away -- bind()
returns a new function that, when called, has this
set to the value we give it.
// create an object with an age attribute
const crow = { age: 3 };
// save the canary's method with crow object under new name
let crowGrowOneYear = canary.growOneYear.bind(crow);
// call new function
crowGrowOneYear();
// check crow object's age attribute
crow.age;
4
Now, having new function crowGrowOneYear()
pointing to crow
object, we can pass to the invokeTwice() function:
invokeTwice(crowGrowOneYear);
crow.age;
6
crow.age
has the expected value of 6
!
We can leave out saving the new function, same effect:
invokeTwice(canary.growOneYear.bind(crow));
crow.age;
8
Further Research
Inheritance in JavaScript is when an object is based on another object. Inheritance allows us to reuse existing code, having objects take on properties of other objects.
Recall that objects contain data (i.e., properties), as well as the means to manipulate that data (i.e., methods). Earlier we simply added methods directly into the constructor function itself:
function Cat(name) {
this.lives = 9;
this.name = name;
this.sayName = function () {
console.log(`Meow! My name is ${this.name}`);
};
}
This way, a sayName
method gets added to all Cat objects by saving a function to the sayName
attribute of newly-created Cat
objects.
This works just fine, but what if we want to instantiate more and more Cat
objects with this constructor? You'll create a new function every single time for that Cat
object's sayName
! What's more: if you ever want to make changes to the method, you'll have to update all objects individually. In this situation, it makes sense to have all objects created by the same Cat
constructor function just share a single sayName
method.
To save memory and keep things DRY, we can add methods to the constructor function's prototype
property. The prototype
is just an object, and all objects created by a constructor function keep a reference to the prototype. Those objects can even use the prototype
's properties as their own!
// declare a constructor function
function Dalmatian(name) {
this.name = name;
};
// place a method into the constructor function's prototype object
Dalmatian.prototype.sayName = function () {
console.log(`Woof! My name is ${this.name}`);
};
[Function]
// create a new Dalmatian object
dalmatian99 = new Dalmatian('Spot');
// invoke the prototype sayName method
dalmatian99.sayName();
Woof! My name is Spot
JavaScript leverages this secret link -- between an object and its prototype -- to implement inheritance. Consider the following prototype chain:
The Cat()
constructor function is invoked using the new
operator, which creates the bailey
instance (object). Note that the meow()
method is defined in the prototype of the bailey object's constructor function. The prototype
is just an object, and all objects created by that constructor are secretly linked to the prototype
. As such, we can execute bailey.meow()
as if it were bailey
's own method!
Recall that each function has a prototype
property, which is really just an object. When this function is invoked as a constructor using the new
operator, it creates and returns a new object. This object is secretly linked to its constructor's prototype, and this secret link allows the object to access the prototype's properties and methods as if it were its own!
Since we know that the prototype
property just points to a regular object, that object itself also has a secret link to its prototype. And that prototype object also has reference to its own prototype -- and so on. This is how the prototype chain is formed.
Whether you're accessing a property (e.g., bailey.lives
) or invoking a method (e.g., bailey.meow()
), the JavaScript interpreter looks for them along the prototype chain in a very particular order:
Object()
object, or the top-level parent. If the property still cannot be found, the property is undefined
.While both approaches work just fine (i.e., any instances created by the constructor function will be able to invoke a sayName()
method), the second approach is more ideal. By adding methods to the prototype
, memory is saved as more Dalmatian
objects are instantiated. Along with being more efficient, we also don't have to update all objects individually should we decide to change a method for all.
What happens if you completely replace a function's prototype
object? How does this affect objects created by that function? Let's look at a simple Hamster
constructor function and instantiate a few objects:
function Hamster() {
this.hasFur = true;
}
let waffle = new Hamster();
let pancake = new Hamster();
First, note that even after we made the new objects, waffle
and pancake
, we can still add properties to Hamster
's prototype
and it will still be able to access those new properties.
Hamster.prototype.eat = function () {
console.log('Chomp chomp chomp!');
};
waffle.eat();
pancake.eat();
Chomp chomp chomp! Chomp chomp chomp!
Now, let's replace Hamster
's prototype
object with something else entirely:
Hamster.prototype = {
isHungry: false,
color: 'brown'
};
{ isHungry: false, color: 'brown' }
The previous objects don't have access to the updated prototype
's properties; they just retain their secret link to the old prototype
:
console.log(waffle.color); // undefined
waffle.eat(); // 'Chomp chomp chomp!'
console.log(pancake.isHungry); // undefined
undefined Chomp chomp chomp! undefined
As it turns out, any new Hamster
objects created moving forward will use the updated prototype:
const muffin = new Hamster();
muffin.eat(); // TypeError: muffin.eat is not a function
evalmachine.<anonymous>:2 muffin.eat(); // TypeError: muffin.eat is not a function ^ TypeError: muffin.eat is not a function at evalmachine.<anonymous>:2:8 at Script.runInThisContext (vm.js:96:20) at Object.runInThisContext (vm.js:303:38) at run ([eval]:1054:15) at onRunRequest ([eval]:888:18) at onMessage ([eval]:848:13) at process.emit (events.js:182:13) at emit (internal/child_process.js:812:12) at process._tickCallback (internal/process/next_tick.js:63:19)
console.log(muffin.isHungry); // false
console.log(muffin.color); // 'brown'
false brown
As we've just seen, if an object doesn't have a particular property of its own, it can access one somewhere along the prototype chain (assuming it exists, of course). With so many options, it can sometimes get tricky to tell just where a particular property is coming from! Here are a few useful methods to help you along the way.
hasOwnProperty()
¶hasOwnProperty()
allows you to find the origin of a particular property. Upon passing in a string of the property name you're looking for, the method will return a boolean indicating whether or not the property belongs to the object itself (i.e., that property was not inherited). Consider the Phone
constructor with a single property defined directly in the function, and another property on its prototype
object:
function Phone() {
this.operatingSystem = 'Android';
}
// declare a prototype attribute
Phone.prototype.screenSize = 6;
6
Let's now create a new object, myPhone
, and check whether operatingSystem
is its own property, meaning that it was not inherited from its prototype (or somewhere else along the prototype chain):
const myPhone = new Phone();
myPhone.hasOwnProperty('operatingSystem');
true
Indeed it returns true
! What about the screenSize
property, which exists on Phone
objects' prototype
? Using hasOwnProperty()
, we gain insight a certain property's origins.
myPhone.hasOwnProperty('screenSize');
false
isPrototypeOf()
¶Objects also have access to the isPrototypeOf()
method, which checks whether or not an object exists in another object's prototype chain. Using this method, you can confirm if a particular object serves as the prototype of another object. Check out the following rodent
object:
const rodent = {
favoriteFood: 'cheese',
hasTail: true
};
Let's now build a Mouse()
constructor function, and assign its prototype to rodent
:
function Mouse() {
this.favoriteFood = 'cheese';
}
Mouse.prototype = rodent;
{ favoriteFood: 'cheese', hasTail: true }
If we create a new Mouse
object, its prototype should be the rodent
object. Let's confirm:
const ralph = new Mouse();
rodent.isPrototypeOf(ralph);
true
Great! isPrototypeOf()
is a great way to confirm if an object exists in another object's prototype chain.
Object.getPrototypeOf()
¶isPrototypeOf()
works well, but keep in mind that in order to use it, you must have that prototype object at hand in the first place! What if you're not sure what a certain object's prototype is? Object.getPrototypeOf()
can help with just that!
Using the previous example, let's store the return value of Object.getPrototypeOf()
in a variable, myPrototype
, then check what it is:
const myPrototype = Object.getPrototypeOf(ralph);
console.log(myPrototype);
console.log(rodent);
{ favoriteFood: 'cheese', hasTail: true } { favoriteFood: 'cheese', hasTail: true }
Great! The prototype of ralph
has the same properties as rodent
because they are the same object. Object.getPrototypeOf()
is great for retrieving the prototype of a given object.
For a closer look at isPrototypeOf()
and getPrototypeOf
, feel free to check out their MDN pages (linked).
.constructor
Property¶Each time an object is created, a special property is assigned to it under the hood: constructor
. Accessing an object's constructor
property returns a reference to the constructor function that created that object in the first place! Here's a simple Longboard
constructor function. We'll also go ahead and make a new object, then save it to a board
variable:
function Longboard() {
this.material = 'bamboo';
}
const board = new Longboard();
If we access board
's constructor
property, we should see the original constructor function itself:
board.constructor;
[Function: Longboard]
Excellent! Keep in mind that if an object was created using literal notation, its constructor is the built-in Object()
constructor function!
rodent.constructor;
[Function: Object]
All objects have a constructor
property. For a closer look, feel free to check out its article on MDN (linked).
Further Research
One of the benefits of implementing inheritance is that it allows you to reuse existing code. By establishing inheritance, we can subclass, that is, have a "child" object take on most or all of a "parent" object's properties while retaining unique properties of its own.
Let's say we have a parent Animal
object, which contains properties like age
and weight
. That same Animal
object can also access methods like eat
and sleep
.
Now, let's also say that we want to create a Cat
child object. Just like you can with other animals, you can also describe a cat by its age or weight, and you can also be certain that the cat eats and sleeps as well. When creating that Cat
object, then, we can simply re-write and re-implement all those methods and properties from Animal
-- or, we can save some time and prevent repeated code by having Cat
inherit those existing properties and methods from Animal
!
Not only can Cat
take on properties and methods of Animal
, we can also give Cat
its own unique properties and methods as well! Perhaps a Cat
has a unique lives
property of 9
, or it has a specialized meow()
method that no Animal
has.
By using prototypal inheritance, Cat
only needs to implement Cat
-specific functionality, and just reuse Animal
's existing functionality.
As you know, an object's constructor function's prototype is first place searched when the JavaScript engine tries to access a property that doesn't exist in the object itself. Consider the following bear
object with two properties, claws
and diet
:
const bear = {
claws: true,
diet: 'carnivore'
};
We'll assign the following PolarBear()
constructor function's prototype property to bear
:
function PolarBear() {
// ...
}
PolarBear.prototype = bear;
{ claws: true, diet: 'carnivore' }
Let's now call the PolarBear()
constructor to create a new object, then give it two properties:
const snowball = new PolarBear();
snowball.color = 'white';
snowball.favoriteDrink = 'cola';
snowball;
{ color: 'white', favoriteDrink: 'cola' }
Note that snowball
has just two properties of its own: color
and favoriteDrink
. However, snowball
also has access to properties that don't exist inside it: claws
and diet
:
console.log(snowball.claws);
console.log(snowball.diet);
true carnivore
Since claws
and diet
both exist as properties in the prototype object, they are looked up because objects are secretly linked to their constructor's prototype property.
Great! But you may be wondering: just what is this secret link that leads to the prototype object? Right after objects are made from the PolarBear()
constructor (such as snowball
), they have immediate access to properties in PolarBear()
's prototype. How exactly is this possible?
As it turns out, the secret link is snowball
's __proto__
property (note the two underscores on each end). __proto__
is a property of all objects (i.e., instances) made by a constructor function, and points directly to that constructor's prototype object. Let's check out what it looks like!
snowball.__proto__;
{ claws: true, diet: 'carnivore' }
Since the __proto__
property refers to the same object as PolarBear
's prototype, bear
, comparing them returns true:
snowball.__proto__ === bear
true
snowball.__proto__ === PolarBear.prototype;
true
When the new instance of PolarBear
is created, the special property snowball.__proto__
is set to PolarBear.prototype
. This secret link allows instances of the PolarBear
constructor to access properties of PolarBear.prototype
.
It is highly discouraged to reassign the __proto__
property, or even use it in any code you write. First, there are compatibility issues across browsers. What's more: since the JavaScript engine searches and accesses properties along the prototype chain, mutating an object's prototype can lead to performance issues. The MDN article for proto even warns against using this property in red text at the very top of the page!
It's great to know the secret link for learning how functions and objects are interconnected, but you should not use __proto__
to manage inheritance. If you ever just need to review an object's prototype, you can still use Object.getPrototypeOf()
.
Let's say we want a Child
object to inherit from a Parent
object. Why shouldn't we just set Child.prototype = Parent.prototype
?
First, recall that objects are passed by reference. This means that since the Child.prototype
object and the Parent.prototype
object refer to the same object -- any changes you make to Child's prototype will also be made to Parent's prototype! We don't want children being able to modify properties of their parents!
On top of all this, no prototype chain will be set up. What if we want an object to inherit from any object we want, not just its prototype?
We still need a way to efficiently manage inheritance without mutating the prototype at all.
Consider the following:
function Car(color, year) {
this.color = color;
this.year = year;
}
Car.prototype.drive = function () {
console.log('Vroom vroom!');
};
const car = new Car('silver', 1988);
What happens when car.drive()
is executed?
car
object for a property named drive
.drive
within the car
object.car.__proto__
property.car.__proto__
property points to Car.prototype
, the JavaScript engine searches for drive
in the prototype.Car.prototype.drive
is a defined property, it is returned.drive
is invoked as a method on car
, the value of this is set to car
.At this point, we've reached a few roadblocks when it comes to inheritance. First, even though __proto__
can access the prototype of the object it is called on, using it in any code you write is not good practice.
What's more: we also shouldn't inherit only the prototype; this doesn't set up the prototype chain, and any changes that we made to a child object will also be reflected in a parent object.
So how should we move forward?
There's actually a way for us to set up the prototype of an object ourselves: using Object.create()
. And best of all, this approach lets us manage inheritance without altering the prototype!
Object.create()
takes in a single object as an argument, and returns a new object with its __proto__
property set to what argument is passed into it. From that point, you simply set the returned object to be the prototype of the child object's constructor function.
function Parent() {
// ...
}
function Child() {
// ...
}
Child.prototype = Object.create(Parent.prototype);
const child = new Child();
child instanceof Parent;
true
Above, Parent.prototype
was the argument passed into Object.create()
. The return value of the expression Object.create(Parent.prototype)
was then set to the value of the Child
constructor's prototype property. After that, we instantiate a new object: child
.
The expression child instanceof Parent
returns a boolean indicating whether the Parent
constructor exists in the child
object's prototype chain. Since we know this is true after executing the first expression (i.e., Child.prototype = Object.create(Parent.prototype)
), the console outputs true
.
Let's check out an example! First, let's say we have a mammal
object with two properties: vertebrate
and earBones
:
const mammal = {
vertebrate: true,
earBones: 3
};
Recall that Object.create()
takes in a single object as an argument, and returns a new object. That new object's __proto__
property is set to whatever was originally passed into Object.create()
. Let's save that returned value to a variable, rabbit
:
const rabbit = Object.create(mammal);
We expect the new rabbit
object to be blank, with no properties of its own:
rabbit;
{}
However, rabbit
should now be secretly linked to mammal
. That is, its __proto__
property should point to mammal
:
rabbit.__proto__ === mammal
true
Great! This means that now, rabbit
extends mammal
(i.e., rabbit
inherits from mammal
). As a result, rabbit
can access mammal
's properties as if it were its own!
rabbit.vertebrate
true
rabbit.earBones
3
function Animal(name) {
this.name = name;
}
Animal.prototype.walk = function () {
console.log(`${this.name} walks!`);
};
function Cat(name) {
Animal.call(this, name);
this.lives = 9;
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Cat.prototype.meow = function () {
console.log('Meow!');
};
const bambi = new Cat('Bambi');
bambi.meow();
bambi.walk();
bambi.name;
Meow! Bambi walks!
'Bambi'
Object.create()
gives us a clean method of establishing prototypal inheritance in JavaScript. We can easily extend the prototype chain this way, and we can have objects inherit from just about any object we want!
Further Research
Recall that an object's .prototype
property points to just one object. This is because JavaScript only supports single inheritance. If there is an object A and an object B, object C can only be prototype-linked to either A or B.
If a JavaScript object can only be prototype-linked to a single object, how can we go about extending properties and methods from multiple different sources? A mixin allows us to just that!
A mixin is a technique that takes the properties and methods from one object and copies them over to another object. In other words: a mixin is an technique that provides some useful functionality, but is not meant to be added to the prototype chain.
The simplest way to implement the mixin pattern is to use Object.assign()
. It is a method that copies an object's own (non-inherited) properties from one or more source objects into a target object, then returns the updated target object. In other words, Object.assign()
adds to the target object by merging in the source object(s). Consider the following:
let target1 = {};
let source = { number: 7 };
Object.assign(target1, source);
console.log(target1);
{ number: 7 }
The first argument passed in, target
, is the destination that receives the properties copied from the source
object. Note that Object.assign()
does not create and return a new object; it directly modifies then returns the same target object that was passed in! As such, values of existing properties will be overwritten, while properties that don't exist in the source
object will remain intact. In the belowe example, the value of target
's number
property was overwritten, while its letter
property was ignored.
let target2 = { letter: 'a', number: 11 };
Object.assign(target2, source);
console.log(target2);
{ letter: 'a', number: 7 }
Object.assign()
can even take in multiple different source objects. Let's create a platypus
object by mixing in properties from other animals:
const duck = { hasBill: true,
feet: 'orange'};
const beaver = { hasTail: true };
const otter = { hasFur: true,
feet: 'webbed'};
const platypus = Object.assign({}, duck, beaver, otter);
console.log(platypus);
{ hasBill: true, feet: 'webbed', hasTail: true, hasFur: true }
After merging an empty target object (i.e., an object without properties of its own) with the properties from duck
, beaver
, and otter
, the target object is returned with all four properties.
Observe, that the feet
attribut contains only webbed
from otter
and not 'orange'
from duck
! The reason is that the order of the source objects in Object.assign()
matters!
It is important to note that the platypus object is not prototype-linked to the three other objects! That is, platypus
doesn't exist in any of the three source objects' prototype chains, and vice versa:
platypus.constructor;
[Function: Object]
platypus.isPrototypeOf(duck)
|| duck.isPrototypeOf(platypus)
|| platypus.isPrototypeOf(beaver)
|| beaver.isPrototypeOf(duck)
|| platypus.isPrototypeOf(otter)
|| otter.isPrototypeOf(platypus)
false
We previously used a constructor function to create a new object:
function City(name, population) {
this.name = name;
this.population = population;
this.identify = function () {
console.log(`${this.name}'s population is ${this.population}.`);
};
}
To instantiate, we invoke the function with the new
operator:
const sanFrancisco = new City('San Francisco', 870000);
console.log(sanFrancisco);
City { name: 'San Francisco', population: 870000, identify: [Function] }
We can use the same constructor to create multiple objects:
const mountainView = new City('Mountain View', 78000);
console.log(mountainView);
City { name: 'Mountain View', population: 78000, identify: [Function] }
Again, note that we used the new
keyword each time to create a new object. Let's now shift gears a bit to factory functions which produce object instances without the use of the new
operator!
A factory function is a function that returns an object, but isn't itself a class or constructor. As such, we invoke a factory function as a normal function without using the new operator. Using a factory function, we can easily create object instances without the complexity of classes and constructors!
Check out the following Basketball()
factory function:
function Basketball(color) {
return {
color: color,
numDots: 35000
};
}
What's important to note here is that Basketball()
returns an object directly. This is different from a constructor function which returns its object automatically.
Let's invoke Basketball()
and check out its output:
const orangeBasketball = Basketball('orange');
console.log(orangeBasketball);
{ color: 'orange', numDots: 35000 }
A factory function has its name because, just like a chair factory can produce chair after chair after chair, a factory function can be used over and over to create any number of objects:
const myBB = Basketball('blue and green');
const yourBB = Basketball('purple');
const bouncy = Basketball('neon pink');
Great! Invoking the factory function allows us to compose a single object -- all without the use of the new
operator. Before we take a look at a more complex example, let's summarize the differences between a factory function and a constructor function:
The following Radio
function returns an object withch references the on
local variable, but on
is not an attribute of the returned object. The returned object packages the on
variable into its closure.
function Radio(mode) {
let on = false;
return {
mode: mode,
turnOn: function () {
on = true;
},
isOn: function () {
return on;
}
};
}
// create a new object
let fmRadio = Radio('fm');
// .on is not an attribute of the object
fmRadio.on; //undefined
// but we have access to variable on in the closure
// read the status
fmRadio.isOn(); // false
false
// change value of variable on in the closure
fmRadio.turnOn();
// read the status
fmRadio.isOn(); // true
true
In the previous section, we used mixins to add features into a composite object. We also just leveraged factory functions to create objects without using the new
operator or messing with prototypal inheritance. Let's combine what we've learned from mixins and factory functions and take things a step further with functional mixins!
A functional mixin is a composable factory function that receives a mixin as an argument, copies properties and methods from that mixin, and returns a new object. Check out the following example: CoffeeMaker()
:
function CoffeeMaker(object) {
let needsRefill = false;
return Object.assign({}, object, {
pourAll: function () {
needsRefill = true;
},
isEmpty: function () {
return needsRefill;
}
});
}
Note that unlike a standard factory function, which takes in individual property values as arguments -- the functional mixin actually takes in an object itself! Whichever object is passed in to the function, is merged with other objects passed into Object.assign()
.
Let's pass the following percolator
object into CoffeeMaker()
and view the results:
const mixedCoffeeMaker = CoffeeMaker({ style: 'percolator' });
mixedCoffeeMaker
{ style: 'percolator', pourAll: [Function: pourAll], isEmpty: [Function: isEmpty] }
Now, one of the great things about functional mixins is that they are composable; we can use them as individual pieces of code that add specific properties like an assembly line. Let's take a closer look!
function ConeFactory(obj) {
let dry = true;
return Object.assign({}, obj, {
soggy: function () {
dry = false;
},
isDry: function () {
return dry;
}
});
}
function IceCreamFactory(obj) {
let cold = true;
return Object.assign({}, obj, {
melt: function () {
cold = false;
},
isCold: function () {
return cold;
}
});
}
let iceCream = IceCreamFactory(ConeFactory({}));
iceCream
{ soggy: [Function: soggy], isDry: [Function: isDry], melt: [Function: melt], isCold: [Function: isCold] }
// cone is dry
iceCream.isDry()
true
// cream is cold
iceCream.isCold()
true
// melt the cream
iceCream.melt()
// cream is not cold anymore
iceCream.isCold()
false
// cone becomes soggy
iceCream.soggy()
// cone is not dry anymore
iceCream.isDry()
false
By default, most things are publicly accessible in JavaScript. We can use closure to make certain parts of an app private, but what if we want to prevent access to a property directly? That is, how would we make a property or method private so it's inaccessible from the outside world? To lend a bit more context, check out how a plain object literal handles privacy:
let manager = {
name: 'Veronika',
getName: function () {
return this.name;
}
};
We can access the string value 'Veronika'
with the getName
method, as well as directly by accessing the developer object's name
property:
console.log(manager.getName(), manager.name);
Veronika Veronika
However, what happens when we reassign the object's name
property?
manager.name = 'Not Veronika';
console.log(manager.getName(), manager.name);
Not Veronika Not Veronika
This sort of open access makes developers uncomfortable. Since we can directly access and mutate an object's properties, we would like a way to implement private properties.
💡Privacy with Underscores? 💡
You may have seen object properties and method names prefixed with an underscore _
, especially in library code. While an underscore is added by the code's author to distinguish privacy, it is privacy by convention only. JavaScript does not give special functionality or meaning to properties prefixed with an underscore!
Let's look into another option: using a function. What if we create a basic function that just returns an object? Does this give the object an adequate level of protection?
Let's look a bit more closely. Check out the following instantiateManager()
function. Nothing too surprising -- just a basic function that returns an object with two properties: name
and getName
:
function instantiateManager() {
return {
name: 'Veronika',
getName: function () {
return this.name;
}
};
}
let boss = instantiateManager();
Along with direct access, we can mutate and reassign the value of the name
property as well:
manager.name = 'Not Veronika';
console.log(manager.getName(), manager.name);
Not Veronika Not Veronika
Wrapping an object within a function doesn't seem too effective either. So, how can we go about making an object's properties private?
Since JavaScript has no concept of private properties out-of-the-box, there is no special syntax or keyword we can use to protect certain properties from being accessed.
However, there is hope! Recall from earlier lessons that we can use scope and closures to create a private state. How can we leverage these techniques to create private properties and methods in an object?
let woman = (function () {
let _name = 'Veronika';
return {
getName: function () {
return _name;
},
setName: function (myName){
_name = myName;
}
};
})();
woman;
{ getName: [Function: getName], setName: [Function: setName] }
console.log(woman._name);
console.log(woman.getName());
undefined Veronika
Recall that one of the key ingredients here is the IIFE! Not only does it prevent pollution of the global scope (which hinders the chance of variable name collisions) -- the IIFE helps prevent access to the name
variable.
And because the returned object's getName()
and setName()
methods retain access to its parent function's scope, we are given a public interface to interact with name
.
woman.setName('Not Veronika');
woman.getName();
'Not Veronika'
The Module Pattern is commonly used to create private properties in JavaScript, but there are quite a few other benefits of incorporating the Module Pattern in code that you write as well. For one: organization. Modules are a larger unit of organization than, say, functions or objects. This helps partition code and provide structure as an application scales.
Keep in mind, however, that you generally use the Module Pattern when you just want one "version" of an object. If you're looking to instantiate unique objects that follow a certain blueprint, you can always still write and invoke a constructor function!
Further Research
The Revealing Module Pattern is a slight variation on the Module Pattern. IIFE's, local variables/functions, and a returned object literal with revealed data make up the structure and syntax of the Revealing Module Pattern. While it still maintains encapsulation of data, certain variables and functions are returned in an object literal.
The underlying philosophy of the Revealing Module Pattern is that, while we still maintain encapsulation (as in the Module Pattern), we also reveal certain properties (and methods). The key ingredients to the Revealing Module Pattern are:
let myModule1 = (function (){
function privateMethod (message) { console.log(message); }
function publicMethod (message) { privateMethod(message); }
return { publicMethod: publicMethod };
})();
myModule1;
{ publicMethod: [Function: publicMethod] }
let myModule2 = (function () {
function privateMethod (message) { console.log(message); }
return {
publicMethod: function (message) {
privateMethod(message);
}
};
})();
myModule2;
{ publicMethod: [Function: publicMethod] }
To bring it all home, let's check out a more complex example:
let pet = (function () {
// private
let _age = 0;
let _name = 'Cooper';
function _ageOneYear() {
_age += 1;
console.log(`One year has passed! Current age is ${_age}`);
}
// public
function displayName() { console.log(`Name: ${_name}`); }
function ageOneYear() { _ageOneYear(); }
return {
name: displayName,
age: ageOneYear
};
})();
In the above snippet, the IIFE has some private data: _age
, _name
, and _ageOneYear()
. The returned object is stored in pet
and provides a public interface through which we can access this data!
Let's first check out what the returned pet
looks like:
pet;
{ name: [Function: displayName], age: [Function: ageOneYear] }
Note that the name()
method reveals the otherwise private displayName()
:
pet.name();
Name: Cooper
However, what happens if we try to access and mutate _name
?
pet._name = 'Sherlock';
console.log(pet);
pet.name();
{ name: [Function: displayName], age: [Function: ageOneYear], _name: 'Sherlock' } Name: Cooper
pet.name()
still produces the string Name: Cooper
Why don't we see the string Sherlock
in the returned string?
Pay close attention to what the first line of code is actually doing: it simply adds a _name
property to the pet object. It has no effect on the _name
variable that exists inside the IIFE itself! If we look at the pet.name() function, it is using the _name
variable that exists inside the IIFE. So even if we add a pet._name
property, the pet.name()
method doesn't ever try to access it.
Note that accessing displayName()
directly won't be effective, either! This should come as now surprise, since displayName()
is just a function defined inside the IIFE (i.e., displayName()
is not a property in the returned object).
console.log(pet.displayName());
// undefined
Likewise, the Revealing Module Pattern also gives us access to the captured _age
variable, via the returned object literal's age()
method:
pet.age();
pet.age();
One year has passed! Current age is 1 One year has passed! Current age is 2
When writing your modules, there are a few key advantages of using the Revealing Module Pattern. For one, there is clarity at the end of the module (i.e., the return
statement) as to which variables or methods may be accessed publicly. Modules may grow large, and this eases readability for other developers who read your code.
Along with clear intent of public or private data, the Revealing Module Pattern lends itself to consistent syntax as well. In contrast, the normal Module Pattern may contain variables and functions spread throughout the entire function body.
While you can't go wrong with either approach to create private properties in your code, be sure to take the time and choose which makes the most sense for your project!
Further Research
// had to install Node.js package
//> npm i xmlhttprequest
// the following one line only necessary in jupyter notebook, so it behaves as in the the browser
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
// create an XMLHttpRequest object
const xhr = new XMLHttpRequest();
// set up what to do when the request successfully complete
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
}
};
// open request
xhr.open("GET", "https://ci-swapi.herokuapp.com/api/");
// send request
xhr.send();
Response (raw):
{"people":"https://ci-swapi.herokuapp.com/api/people/","planets":"https://ci-swapi.herokuapp.com/api/planets/","films":"https://ci-swapi.herokuapp.com/api/films/","species":"https://ci-swapi.herokuapp.com/api/species/","vehicles":"https://ci-swapi.herokuapp.com/api/vehicles/","starships":"https://ci-swapi.herokuapp.com/api/starships/"}
Response (formatted):
{
"people": "https://ci-swapi.herokuapp.com/api/people/",
"planets": "https://ci-swapi.herokuapp.com/api/planets/",
"films": "https://ci-swapi.herokuapp.com/api/films/",
"species": "https://ci-swapi.herokuapp.com/api/species/",
"vehicles": "https://ci-swapi.herokuapp.com/api/vehicles/",
"starships": "https://ci-swapi.herokuapp.com/api/starships/"
}
The XMLHttpRequest.readyState
property returns the state an XMLHttpRequest (XHR) client is in. An XHR client exists in one of the following states:
Value | State | Description |
---|---|---|
0 | UNSENT | Client has been created. open() not called yet. |
1 | OPENED | open() has been called. |
2 | HEADERS_RECEIVED | send() has been called, and headers and status are available. |
3 | LOADING | Downloading; responseText holds partial data. |
4 | DONE | The operation is complete. |
The read-only XMLHttpRequest.status
property returns the numerical HTTP status code of the XMLHttpRequest
's response.
Before the request completes, the value of status is 0
. Browsers also report a status of 0
in case of XMLHttpRequest
errors.
HTTP response status codes indicate whether a specific HTTP request has been successfully completed. Responses are grouped in five classes:
Informational responses (100–199)
Successful responses (200–299)
Redirects (300–399)
Client errors (400–499)
Server errors (500–599)
Code | Description | |
---|---|---|
200 | OK. The request has succeeded. The meaning of the success depends on the HTTP method: GET: The resource has been fetched and is transmitted in the message body. HEAD: The entity headers are in the message body. * PUT or POST: The resource describing the result of the action is transmitted in the message body. |
|
301 | Moved Permanently. The URL of the requested resource has been changed permanently. The new URL is given in the response. |
|
400 | Bad Request The server could not understand the request due to invalid syntax. |
|
401 | Unauthorized. Although the HTTP standard specifies "unauthorized", semantically this response means unauthenticated". That is, the client must authenticate itself to get the requested response. |
|
403 | Forbidden. The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401, the client's identity is known to the server. |
|
404 | Not Found. |
The server can not find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurrence on the web. |
500 | Internal Server Error. The server has encountered a situation it doesn't know how to handle. |
The JSON.parse()
method parses a JSON string, constructing the JavaScript value or object described by the string.
const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);
console.log(obj.count); // expected output: 42
console.log(obj.result); // expected output: true
42 true
JSON.parse(xhr.responseText);
{ people: 'https://ci-swapi.herokuapp.com/api/people/', planets: 'https://ci-swapi.herokuapp.com/api/planets/', films: 'https://ci-swapi.herokuapp.com/api/films/', species: 'https://ci-swapi.herokuapp.com/api/species/', vehicles: 'https://ci-swapi.herokuapp.com/api/vehicles/', starships: 'https://ci-swapi.herokuapp.com/api/starships/' }
//const baseURL = "https://ci-swapi.herokuapp.com/api/";
function getData(dataType, callBackFN){
// create an XMLHttpRequest object
const xhr = new XMLHttpRequest();
// set up what to do when the request successfully complete
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
callBackFN(JSON.parse(this.responseText));
}
};
// open request
xhr.open("GET", baseURL + dataType + '/');
// send request
xhr.send();
}
function executeOnDataReceipt(data){
for (let key in data){
console.log(key, data[key]);
}
}
getData('planets', executeOnDataReceipt);
count 60 next https://ci-swapi.herokuapp.com/api/planets/?page=2 previous null results [ { name: 'Tatooine', rotation_period: '23', orbital_period: '304', diameter: '10465', climate: 'arid', gravity: '1 standard', terrain: 'desert', surface_water: '1', population: '200000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/1/', 'https://ci-swapi.herokuapp.com/api/people/2/', 'https://ci-swapi.herokuapp.com/api/people/4/', 'https://ci-swapi.herokuapp.com/api/people/6/', 'https://ci-swapi.herokuapp.com/api/people/7/', 'https://ci-swapi.herokuapp.com/api/people/8/', 'https://ci-swapi.herokuapp.com/api/people/9/', 'https://ci-swapi.herokuapp.com/api/people/11/', 'https://ci-swapi.herokuapp.com/api/people/43/', 'https://ci-swapi.herokuapp.com/api/people/62/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/1/', 'https://ci-swapi.herokuapp.com/api/films/3/', 'https://ci-swapi.herokuapp.com/api/films/4/', 'https://ci-swapi.herokuapp.com/api/films/5/', 'https://ci-swapi.herokuapp.com/api/films/6/' ], created: '2014-12-09T13:50:49.641000Z', edited: '2014-12-20T20:58:18.411000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/1/' }, { name: 'Alderaan', rotation_period: '24', orbital_period: '364', diameter: '12500', climate: 'temperate', gravity: '1 standard', terrain: 'grasslands, mountains', surface_water: '40', population: '2000000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/5/', 'https://ci-swapi.herokuapp.com/api/people/68/', 'https://ci-swapi.herokuapp.com/api/people/81/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/1/', 'https://ci-swapi.herokuapp.com/api/films/6/' ], created: '2014-12-10T11:35:48.479000Z', edited: '2014-12-20T20:58:18.420000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/2/' }, { name: 'Yavin IV', rotation_period: '24', orbital_period: '4818', diameter: '10200', climate: 'temperate, tropical', gravity: '1 standard', terrain: 'jungle, rainforests', surface_water: '8', population: '1000', residents: [], films: [ 'https://ci-swapi.herokuapp.com/api/films/1/' ], created: '2014-12-10T11:37:19.144000Z', edited: '2014-12-20T20:58:18.421000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/3/' }, { name: 'Hoth', rotation_period: '23', orbital_period: '549', diameter: '7200', climate: 'frozen', gravity: '1.1 standard', terrain: 'tundra, ice caves, mountain ranges', surface_water: '100', population: 'unknown', residents: [], films: [ 'https://ci-swapi.herokuapp.com/api/films/2/' ], created: '2014-12-10T11:39:13.934000Z', edited: '2014-12-20T20:58:18.423000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/4/' }, { name: 'Dagobah', rotation_period: '23', orbital_period: '341', diameter: '8900', climate: 'murky', gravity: 'N/A', terrain: 'swamp, jungles', surface_water: '8', population: 'unknown', residents: [], films: [ 'https://ci-swapi.herokuapp.com/api/films/2/', 'https://ci-swapi.herokuapp.com/api/films/3/', 'https://ci-swapi.herokuapp.com/api/films/6/' ], created: '2014-12-10T11:42:22.590000Z', edited: '2014-12-20T20:58:18.425000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/5/' }, { name: 'Bespin', rotation_period: '12', orbital_period: '5110', diameter: '118000', climate: 'temperate', gravity: '1.5 (surface), 1 standard (Cloud City)', terrain: 'gas giant', surface_water: '0', population: '6000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/26/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/2/' ], created: '2014-12-10T11:43:55.240000Z', edited: '2014-12-20T20:58:18.427000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/6/' }, { name: 'Endor', rotation_period: '18', orbital_period: '402', diameter: '4900', climate: 'temperate', gravity: '0.85 standard', terrain: 'forests, mountains, lakes', surface_water: '8', population: '30000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/30/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/3/' ], created: '2014-12-10T11:50:29.349000Z', edited: '2014-12-20T20:58:18.429000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/7/' }, { name: 'Naboo', rotation_period: '26', orbital_period: '312', diameter: '12120', climate: 'temperate', gravity: '1 standard', terrain: 'grassy hills, swamps, forests, mountains', surface_water: '12', population: '4500000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/3/', 'https://ci-swapi.herokuapp.com/api/people/21/', 'https://ci-swapi.herokuapp.com/api/people/35/', 'https://ci-swapi.herokuapp.com/api/people/36/', 'https://ci-swapi.herokuapp.com/api/people/37/', 'https://ci-swapi.herokuapp.com/api/people/38/', 'https://ci-swapi.herokuapp.com/api/people/39/', 'https://ci-swapi.herokuapp.com/api/people/42/', 'https://ci-swapi.herokuapp.com/api/people/60/', 'https://ci-swapi.herokuapp.com/api/people/61/', 'https://ci-swapi.herokuapp.com/api/people/66/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/3/', 'https://ci-swapi.herokuapp.com/api/films/4/', 'https://ci-swapi.herokuapp.com/api/films/5/', 'https://ci-swapi.herokuapp.com/api/films/6/' ], created: '2014-12-10T11:52:31.066000Z', edited: '2014-12-20T20:58:18.430000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/8/' }, { name: 'Coruscant', rotation_period: '24', orbital_period: '368', diameter: '12240', climate: 'temperate', gravity: '1 standard', terrain: 'cityscape, mountains', surface_water: 'unknown', population: '1000000000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/34/', 'https://ci-swapi.herokuapp.com/api/people/55/', 'https://ci-swapi.herokuapp.com/api/people/74/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/3/', 'https://ci-swapi.herokuapp.com/api/films/4/', 'https://ci-swapi.herokuapp.com/api/films/5/', 'https://ci-swapi.herokuapp.com/api/films/6/' ], created: '2014-12-10T11:54:13.921000Z', edited: '2014-12-20T20:58:18.432000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/9/' }, { name: 'Kamino', rotation_period: '27', orbital_period: '463', diameter: '19720', climate: 'temperate', gravity: '1 standard', terrain: 'ocean', surface_water: '100', population: '1000000000', residents: [ 'https://ci-swapi.herokuapp.com/api/people/22/', 'https://ci-swapi.herokuapp.com/api/people/72/', 'https://ci-swapi.herokuapp.com/api/people/73/' ], films: [ 'https://ci-swapi.herokuapp.com/api/films/5/' ], created: '2014-12-10T12:45:06.577000Z', edited: '2014-12-20T20:58:18.434000Z', url: 'https://ci-swapi.herokuapp.com/api/planets/10/' } ]